1. 概念

Mybatis 基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等复杂的过程。

Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。

采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交道,就能完成对数据的持久化操作。

解决的问题:

  • 数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。
  • SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。
  • 使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。
  • 对结果集的解析中也存在硬编码。

    2. 入门案例

  • 创建数据可和表

  • 创建 Maven 项目,添加相关依赖
  • 编写 mybatis 配置文件
  • 编写实体类
  • 编写 ORM 映射文件
  • 映射文件注册到配置文件中
  • 配置映射文件的扫描位置
  • 测试
    1. @Test
    2. public void test01(){
    3. SqlSession sqlSession = null;
    4. try {
    5. // 1. 读取 mybatis 配置文件
    6. Reader reader = Resources.getResourceAsReader(resource);
    7. // 2. 根据图纸创建工厂
    8. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    9. // 3. 创建可以执行 sql 语句的 SqlSession 产品
    10. sqlSession = sqlSessionFactory.openSession();
    11. // 4. 执行 sql 语句
    12. List<Team> list = sqlSession.selectList("com.zmh.pojo.Team.queryAll");
    13. list.forEach(team -> System.out.println(team));
    14. } catch (IOException e) {
    15. e.printStackTrace();
    16. } finally {
    17. sqlSession.close();
    18. }
    19. }

增删改需要手动提交事务

3. Mybatis 对象分析

3.1 Resources

加载资源,读取资源文件

3.2 SqlSessionFactoryBuilder

为了创建 SqlSessionFactory ,使用 SqlSessionFactoryBuilderbuild 方法
使用到了建造者设计模式。

建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节。
在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行一种分步骤进行的建造过程.

3.3 SqlSessionFactory

SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。

3.4 SqlSession

SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。
SqlSession 特性:

  1. 会开启一个事务(默认不自动提交)
  2. 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
  3. 预处理语句不会被复用,也不会批量处理更新。

SqlSession 有超过 20 个方法

3.5 Mybatis 架构

框架图:

4. 原有的 Dao 方式开发

我们知道 SqlSession 是线程不安全的,采用原始的方式进行开发可能会产生线程冲突,所以我们引入 ThreadLocal的概念

4.1 ThreadLocal

ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

线程之间不会冲突,因为有不同的副本。

5. 使用 Mapper 接口编写 Mybatis 项目

5.1 为什么使用 Mapper 接口

使用原始的 dao 层方式开发时我们发现, dao 的实现类并没有做什么实质性的工作,仅仅是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以,MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对DB 进行操作。这种对 Dao 的实现方式称为 Mapper接口 的动态代理方式。

5.2 实现

  1. 编写 TeamMapper 接口
  2. 创建 TeamMapper.xml 文件
  3. 配置映射文件
  4. 使用 getMapper 方法动态获取代理对象

5.3 原理

6. 增删改相关细节

6.1 获取自增的 ID

在 sql 语句执行前(相当于赋随机值)

  1. 编写接口
  2. 映射文件中加入 selectKey

在 sql 语句执行后(相当于查询)

  1. 编写接口
  2. 映射文件中加入 selectKey
    1. <insert id="addGame" parameterType="com.zmh.pojo.GameRecord">
    2. <selectKey order="BEFORE" keyProperty="recordId" resultType="java.lang.String">
    3. SELECT UUID();
    4. </selectKey>
    5. INSERT INTO `mybatis01`.`gamerecord`(`recordId`, `homeTeamId`, `score`, `visitingTeamId`)
    6. VALUES (#{recordId},#{homeTeamId},#{score},#{visitingTeamId})
    7. </insert>

6.2 输入映射

不使用 parameterType 的几种方法

6.2.1 arg 或 param

两个细节:

  1. mybatis3.3版本之前:可以直接写#{0} #{1}
    从mybatis3.4开始:#{arg0} #{arg1}… 或者是 #{param1} #{param2}…
  2. sql语句中不能使用小于号,使用转移符号替换;大于号没有限制,也可以使用转义符号替换 >
    1. <select id="queryByRange" resultType="com.zmh.pojo.Team">
    2. select * from team where teamId>=#{param1} and teamId &lt;=#{param2};
    3. </select>

6.2.2 使用 @Param 注解

List<Team> queryByRange(@Param("min") Integer min, @Param("max") Integer max);

  1. <select id="queryByRange" resultType="com.zmh.pojo.Team">
  2. select * from team where teamId>=#{min} and teamId &lt;=#{max};
  3. </select>

6.2.3 通过 map 传递多个参数

  1. @Test
  2. public void testParam02(){
  3. Map<String, Object> map = new HashMap<>();
  4. map.put("min", 1005);
  5. map.put("max", 1007);
  6. List<Team> teams = dao.queryByRange1(map);
  7. teams.forEach(team -> System.out.println(team));
  8. }

6.2.4 通过 pojo 类传递多个参数

也就相当于 resultType 省略

6.3 #{} 和 ${} 的区别

#{}

表示占位符,相当于 sql 语句中的 ?

${}

表示字符串的原样替换, 使用 Statement 或者 PreparedStatement 把 sql 语句和 ${} 的内容连接起来。一般用在替换表名,列名,不同列排序等操作。

List<Team> queryByField(@Param("column") String column, @Param("columnValue") String columnValue);

  1. <select id="queryByField" resultType="com.zmh.pojo.Team">
  2. SELECT * FROM TEAM WHERE ${column}=#{columnValue};
  3. </select>

6.4 输出映射

6.4.1 resultType

  1. 简单类型
  2. pojo 类型
  3. Map 类型
    • 大于一条数据会抛出异常,应该使用 List>

6.4.2 resultMap


就是要自己去定义这个映射的关系,输出映射写为 resultMap ,值为其映射的 ID 值。

  1. <select id="queryAll2" resultMap="baseResultMap">
  2. select * from team;
  3. </select>
  4. <resultMap id="baseResultMap" type="com.kkb.pojo.Team">
  5. <!--一般主键列用id,其余列用result
  6. column:表示数据库表中的列名,不区分大小写
  7. property:表示实体类中的对应的属性名,区分大小写
  8. javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断
  9. jdbcType="数据库中的类型column的类型" 一般省略
  10. -->
  11. <id column="teamId" property="teamId" javaType="java.lang.Integer" ></id>
  12. <result column="teamName" property="teamName" javaType="java.lang.String"></result>
  13. <result column="location" property="location" javaType="java.lang.String"></result>
  14. <result column="createTime" property="createTime" javaType="java.util.Date"></result>
  15. </resultMap>

6.4.3 数据库表中列与实体类属性不一致的处理方式

  1. 在查询语句中通过别名的方式,将数据库中列名与实体类中的属性进行对应。
  2. 输出映射使用 ResultMap,然后自己去定义 ResultMap 去将数据库中列名与实体类中的属性进行对应。

7. Mybatis 的全局配置文件

相关内容

首先在头部使用约束文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">

7.1 配置的内容

  1. configuration(配置)
  2. properties--属性:加载外部的配置文件,例如加载数据库的连接信息
  3. Settings--全局配置参数:例如日志配置
  4. typeAliases--类型别名
  5. typeHandlers----类型处理器
  6. objectFactory-----对象工厂
  7. Plugins------插件:例如分页插件
  8. Environments----环境集合属性对象
  9. environment(环境变量)
  10. transactionManager(事务管理器)
  11. dataSource(数据源)
  12. Mappers---映射器:注册映射文件用

7.2 属性

  1. 属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource 节点中的 properties 节点),也可以在 Java 属性文件中配置这些属性。<br />

7.3 设置 settings

7.4 类型别名 typeAliases

  1. 默认的
  2. 自定义别名

    1. <!--自定义类型别名-->
    2. <typeAliases>
    3. <!--对单个的实体类定义别名-->
    4. <typeAlias type="com.kkb.pojo.Team" alias="Team"/>
    5. <!--推荐写法:批量定义别名:扫描指定包下的所有类,同时别名定义为类名,别名的首字母大小写都可以-->
    6. <package name="com.kkb.pojo"/>
    7. </typeAliases>

    7.5 映射器 Mappers

  3. 使用相对于类路径的资源引用

    1. <mapper resource="com/zmh/mapper/GameRecordMapper.xml"/>
  4. 使用映射器接口实现类的完全限定类名

    1. <mapper resource="com.zmh.mapper.GameRecordMapper"/>
  5. 将包内的映射器接口实现全部注册为映射器

    1. <mapper resource="com.zmh.mapper"/>

7.6 dataSource 标签

7.7 事务

7.7.1 默认需要手动提交事务

MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。

  1. JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
  2. MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。

7.7.2 自动提交事务

如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。

8. Mybatis 中的关系映射

8.1 对一关系的映射

8.1.1 关联对象打点调用属性方式

连接查询
一般单独定义 resultMap
extends=”表示继承的其他的 resultMap 的 id”

8.1.2 直接引用关联对象的 Mapper 映射

连接查询
引用 TeamMapper 中的 resultMap

8.1.3 直接引用关联对象单独查询的方法

方式3:直接引用关联对象的单独查询的方法:要求:关联对象的Maper中必须要求有单独的查询方法
property=”关联对象的属性名”
javaType=”关联对象的类型”
select=”关联对象的单独查询的语句”
column=”外键列”

  1. <select id="queryById1" resultMap="joinTeamResult1">
  2. SELECT * FROM PLAYER P INNER JOIN TEAM T ON P.teamId=T.teamId WHERE PLAYERID=#{id};
  3. </select>
  4. <select id="queryById2" resultMap="joinTeamResult2">
  5. SELECT * FROM PLAYER P INNER JOIN TEAM T ON P.teamId=T.teamId WHERE PLAYERID=#{id};
  6. </select>
  7. <select id="queryById3" resultMap="joinTeamResult3">
  8. SELECT * FROM PLAYER WHERE PLAYERID=#{ID};
  9. </select>
  10. <resultMap id="baseResultMap" type="Team">
  11. <id column="playerId" property="playerId"/>
  12. <result column="playerName" property="playerName"/>
  13. <result column="playerNum" property="playerNum"/>
  14. <result column="teamId" property="teamId"/>
  15. </resultMap>
  16. <!-- 方法一 -->
  17. <resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
  18. <result column="teamId" property="team1.teamId"/>
  19. <result column="teamName" property="team1.teamName"/>
  20. <result column="location" property="team1.location"/>
  21. <result column="createTime" property="team1.createTime"/>
  22. </resultMap>
  23. <!-- 方法二 -->
  24. <resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
  25. <association property="team2" javaType="Team" resultMap="com.zmh.mapper.TeamMapper.baseResultMapper"/>
  26. </resultMap>
  27. <!-- 方法三 -->
  28. <resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
  29. <association property="team3" javaType="Team" select="com.zmh.mapper.TeamMapper.queryById" column="teamId"/>
  30. </resultMap>

8.2 对多关系的映射

8.2.1 连接查询+引用关联对象的结果映射

  1. <select id="queryById02" resultMap="joinResult1">
  2. SELECT * FROM TEAM T INNER JOIN PLAYER P ON P.TEAMID = T.TEAMID WHERE T.TEAMID=#{teamId};
  3. </select>
  4. <resultMap id="joinResult1" type="Team" extends="baseResultMapper">
  5. <collection property="playerList" javaType="java.util.ArrayList" ofType="Player"
  6. resultMap="com.zmh.mapper.PlayerMapper.baseResultMap"/>
  7. </resultMap>

8.2.2 引用关联对象的单独查询的方法

  1. <select id="queryById03" resultMap="joinResult2">
  2. select * from team where teamId=#{id};
  3. </select>
  4. <resultMap id="joinResult2" type="Team" extends="baseResultMapper">
  5. <collection property="playerList" javaType="java.util.ArrayList"
  6. select="com.zmh.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
  7. </resultMap>

9. 动态 SQL

9.1 where 标签在 select 中的使用

  1. 原有的写法

字符串的条件拼接

  1. /*原有的多条件分析:都是通过java中的字符串拼接实现
  2. String sql="select * from team where 1 = 1 ";
  3. // 如果用户输入了名称,就模糊查询
  4. and teamName like '%?%'
  5. // 如果用户输入了日期,按照日期区间查询
  6. and createTime> ? and createTime< ?
  7. //如果输入了地区,按照地区查询
  8. and location =?";*/
  9. if(vo.getName()!=null && !"".equals(vo.getName().trim())){
  10. sql+=" and teamName like '%"+vo.getName().trim()+"%'";
  11. }
  12. if(vo.getBeginTime()!=null ){
  13. sql+=" and getEndTime>"+vo.getBeginTime();
  14. }
  15. if(vo.getBeginTime()!=null ){
  16. sql+=" and createTime<="+vo.getEndTime();
  17. }
  18. if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){
  19. sql+=" and location ="+vo.getLocation().trim();
  20. }
  1. 改进后
    1. <select id="queryByVo" parameterType="com.zmh.vo.QueryTeamVO" resultMap="baseResultMapper">
    2. select * from team
    3. <where>
    4. <if test="name != null">
    5. and teamName like concat(concat('%', #{name}), '%')
    6. </if>
    7. <if test="location != null">
    8. and location = #{location}
    9. </if>
    10. <if test="begin != null">
    11. and createTime >= #{begin}
    12. </if>
    13. <if test="end != null">
    14. and createTime &lt;= #{end}
    15. </if>
    16. </where>
    17. </select>

9.2 set 标签在 update 中的使用

  1. <!-- 动态 SQL set-->
  2. <update id="updateDynamic" parameterType="Team">
  3. update team
  4. <set>
  5. <if test="teamName != null">
  6. teamName = #{teamName},
  7. </if>
  8. <if test="location != null">
  9. location = #{location},
  10. </if>
  11. <if test="createTime != null">
  12. createTime = #{createTime},
  13. </if>
  14. </set>
  15. where teamId = #{teamId}

9.3 forEach 标签

添加和删除

  1. <insert id="addList" parameterType="arraylist">
  2. INSERT INTO TEAM (TEAMNAME, LOCATION) VALUES
  3. <foreach collection="list" item="l" separator=",">
  4. (#{l.teamName},#{l.location})
  5. </foreach>
  6. </insert>
  7. <delete id="deleteList">
  8. DELETE FROM TEAM WHERE TEAMID IN
  9. <foreach collection="list" item="teamId" separator="," open="(" close=")">
  10. #{teamId}
  11. </foreach>
  12. </delete>

10. 分页插件

11. Mybatis 缓存