1.SQL 映射文件、缓存


CREATE TABLE `money` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `basic` int DEFAULT NULL COMMENT '基本工资',
  `reward` int DEFAULT NULL COMMENT '奖金',
  `punishment` int DEFAULT NULL COMMENT '惩罚金',
  `version` int DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `did` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `dept` (
  `id` int NOT NULL AUTO_INCREMENT,
  `dept_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

增删改

MoneyServiceImpl 定义增删改方法
    @Override
    public void addMoney(Money money) {
        moneyMapper.insertMoney(money);
    }

    @Override
    public int updateMoney(Money money) {
        return moneyMapper.update(money);
    }

    @Override
    public boolean deleteMoneyById(Integer id) {
        return moneyMapper.deleteMoneyById(id);
    }
MoneyMapper 接口
//    @Insert("insert into money ( basic, reward,punishment) values ( #{basic}, #{reward},#{punishment})")
    void insertMoney(Money money);

    @Update("update money set basic = #{basic}, reward = #{reward}, punishment = #{punishment} where id = #{id}")
    int update(Money money);

    @Delete("delete from money where id = #{id}")
    boolean deleteMoneyById(Integer id);

MoneyMapper.xml 映射文件

定义 SQL 语句:

  • parameterType:参数类型,可以省略。
  • useGeneratedKeys="true":使用自增主键获取主键值策略(mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGeneratedKeys();)
  • keyProperty:指定 MyBatis 获取到主键值以后,将这个值封装给 JavaBean 的哪个属性。
<insert id="insertMoney" parameterType="org.example.pojo.Money" keyProperty="id" useGeneratedKeys="true">
insert into money (basic, reward, punishment)
values (#{basic}, #{reward}, #{punishment})
</insert>

测试方法:

 @Test
    public void testInser() {
        Money money = new Money();

        money.setBasic(1000);
        money.setReward(1000);
        money.setPunishment(1000);
        moneyService.addMoney(money);

    }

    @Test
    public void testUpdate() {
        Money money = new Money();
        money.setId(4);
        money.setBasic(1000);
        money.setReward(1000);
        money.setPunishment(1000);
        System.out.println("moneyService.updateMoney(money) = " + moneyService.updateMoney(money));

    }

    @Test
    public void testDel() {
        System.out.println("moneyService.deleteMoneyById(10) = " + moneyService.deleteMoneyById(11));
    }

mapper中如何传递多个参数?

单个参数

MyBatis 不会做特殊处理, #{参数名/任意名}:取出参数值。

多个参数

MyBatis 会做特殊处理,多个参数会被封装成 一个map

  • key:param1…paramN,或者参数的索引也可以。
  • value:传入的参数值。
  • #{}就是从 map 中获取指定的 key 的值。
  • #{}里面的名称对应的是注解@Param括号里面修饰的名称。
  • 这种方法在参数不多的情况还是比较直观的,(推荐使用)。如:
 public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

   <select id="selectUser" resultMap="UserResultMap">
       select * from user
       where user_name = #{userName} and dept_id = #{deptId}
   </select>

Map传参法

  • #{}里面的名称对应的是Map里面的key名称。
  • 这种方法适合传递多个参数,且参数易变能灵活传递的情况。如:
  public User selectUser(Map<String, Object> params);

    <select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
        select * from user
        where user_name = #{userName} and dept_id = #{deptId}
    </select>

Java Bean传参法

  • #{}里面的名称对应的是User类里面的成员属性。
  • 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。如:
 public User selectUser(User user);

   <select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
       select * from user
       where user_name = #{userName} and dept_id = #{deptId}
   </select>

#{}和${}的区别?

  • #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
  • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
  • #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
  • #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外

模糊查询like语句该怎么写?

  • ’%${question}%’ 可能引起SQL注入,不推荐
  • “%”#{question}”%” 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ‘,所以这里 % 需要使用双引号” “,不能使用单引号 ’ ‘,不然会查不到任何结果。
  • CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,(推荐✨)
  • 使用bind标签(不推荐)

返回集合类型 resultType

    // 返回List集合   
    List<Money> selectMoneyList();

    // @MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
    @MapKey("id")
    Map<String, Object> selectMoneyMap();

    // 返回一条记录的map;key就是列名,值就是对应的值
    Map<Integer, Object> selectMoneyMapOne(@Param("id") int id);
 <select id="selectMoneyList" resultType="org.example.pojo.Money">
        select *
        from money
    </select>

    <select id="selectMoneyMap" resultType="money">
        select *
        from money
    </select>
    <select id="selectMoneyMapOne" resultType="map">
        select *
        from money
        where id = #{id}
    </select>

实体类属性名和表中字段名不一样 ,怎么办?

第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

 <select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
           select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>

第2种: 通过resultMap  中的<result>来映射字段名和实体类属性名的一一对应的关系。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.EmployeeMapperPlus">

    <!--
    自定义某个javaBean的封装规则,type:自定义规则的Java类型,id:唯一id方便引用
    -->
    <resultMap id="MyEmp" type="com.atguigu.mybatis.bean.Employee">
        <!--
        指定主键列的封装规则,id 定义主键会底层有优化,
				column 指定哪一列,property 指定对应的 JavaBean 属性
        -->
        <id column="id" property="id"/>
      
        <!-- 定义普通列封装规则 -->
        <result column="last_name" property="lastName"/>
      
        <!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
    </resultMap>

    <!-- resultMap:自定义结果集映射规则 -->
    <select id="getEmpById" resultMap="MyEmp">
        select * from tbl_employee where id = #{id}
    </select>

</mapper>

联合查询:一对一、一对多的关联查询

1、可以使用级联属性封装结果集

2、可以使用association标签指定联合的javaBean对象

          <!-- 联合查询:级联属性封装结果集 -->
<!--方法1-->
    <resultMap id="money" type="money">
        <id property="id" column="id"/>
        <result property="basic" column="basic"/>
        <result property="reward" column="reward"/>
        <result property="punishment" column="punishment"/>
        <result property="version" column="version"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="did" column="did"/>
        <result property="department.id" column="id"/>
        <result property="department.deptName" column="dept_name"/>
    </resultMap>

    <!--方法2-->
    <resultMap id="money1" type="money">
        <id property="id" column="id"/>
        <result property="basic" column="basic"/>
        <result property="reward" column="reward"/>
        <result property="punishment" column="punishment"/>
        <result property="version" column="version"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
        <result property="did" column="did"/>
        <association property="department" javaType="department">
            <id property="id" column="id"/>
            <result property="deptName" column="dept_name"/>
        </association>
    </resultMap>

    <select id="selectMoneyOrDept" resultMap="money1">
        select m.id,
               m.basic,
               m.reward,
               m.punishment,
               m.version,
               m.create_time,
               m.update_time,
               d.id        as did,
               d.dept_name as dept_name
        from money m,
             dept d
        where m.did = d.id
          and m.id = #{id}
    </select>
    Money selectMoneyOrDept(@Param("id") int id);  


    @Test
    public void MoneyOrDept() {

        Money money = moneyService.selectMoneyOrDept(1);
        System.out.println("money = " + money);

        System.out.println("money.getDepartment() = " + money.getDepartment().getDeptName());

    }

3、Colletion 标签可以定义关联集合类型的属性的封装

public interface DeptMapper {

    Department getDeptById(@Param("id") int id);

}
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.DeptMapper">

    <resultMap id="dept" type="department">
        <id column="did" property="id"/>
        <result column="dept_name" property="deptName"/>

        <!--
        collection 定义关联集合类型的属性的封装
        ofType 指定集合里面元素的类型
        -->
        <collection property="moneyList" ofType="org.example.pojo.Money">
            <id column="mid" property="id"/>
            <result column="basic" property="basic"/>
            <result column="reward" property="reward"/>
            <result column="punishment" property="punishment"/>
            <result column="version" property="version"/>
        </collection>
    </resultMap>

    <select id="getDeptById" resultMap="dept">
        select  d.id as did,d.dept_name,m.id as mid,m.basic,m.reward,m.punishment,m.version
            from dept d left join money m on d.id = m.did where d.id = #{id}
    </select>
</mapper>
  @Test
    public void testDept() {
        Department deptById = deptMapper.getDeptById(2);
        System.out.println("deptById = " + deptById);
        List<Money> moneyList =
                deptById.getMoneyList();
        System.out.println("moneyList.size() = " + moneyList.size());
    }

关于延迟加载

<settings>
  <!--开启驼峰命名映射-->
  <setting name="mapUnderscoreToCamelCase" value="true"/>
  
  <!--默认值是OTHER,Oracle需要下面的设置,MySQL不需要-->
  <setting name="jdbcTypeForNull" value="NULL"/>
  
  <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 -->
  <setting name="lazyLoadingEnabled" value="true"/>
  
  <!--开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 
	否则,每个延迟加载属性会按需加载-->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • Mybatis支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
  • 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
  • 当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

动态SQL

MyBatis中有一些支持动态SQL的标签,它们的原理是使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.mybatis.mapper.EmployeeMapper">
    
  	<!--主要测试 if、choose、trim、foreach 标签的使用-->
    <!--
    这里面涉及 OGNL 表达式的使用:
    ''会被识别为空串,想用""可以写&quot;&quot;
    OGNL 会进行字符串与数字的转换判断,比如 "0"==0
    -->

    <!--where 标签只会去掉前面多出来的 and or-->
    <select id="getEmpsByConditionIf" resultType="employee">
        select * from tbl_employee
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            <if test="lastName != null and lastName != ''">
                and last_name like #{lastName}
            </if>
            <if test="email != null and email.trim() != ''">
                and email = #{email}
            </if>
            <if test="gender == 0 or gender == 1">
                and gender = #{gender}
            </if>
        </where>
    </select>
  

    <!--
    trim 标签可以解决后面多出来的 and or,有下面四个属性:
    prefix:给拼接后的字符串加一个前缀
    prefixOverrides:去掉整个字符串前面多余的字符
    suffix:给拼接后的字符串加一个后缀
    suffixOverrides:去掉整个字符串后面多余的字符串
    -->
    <select id="getEmpsByConditionTrim" resultType="employee">
        select * from tbl_employee
        <trim prefix="where" suffixOverrides="and">
            <if test="id != null">
                id = #{id} and
            </if>
            <if test="lastName != null and lastName != ''">
                last_name like #{lastName} and
            </if>
            <if test="email != null and email.trim() != ''">
                email = #{email} and
            </if>
            <if test="gender == 0 or gender == 1">
                gender = #{gender}
            </if>
        </trim>
    </select>

  
    <!--choose 标签只会选择一列,如果传入多个列,就选择第一列-->
    <select id="getEmpsByConditionChoose" resultType="employee">
        select *
        from tbl_employee
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <when test="lastName != null">
                    last_name like #{lastName}
                </when>
                <when test="email != null">
                    email = #{email}
                </when>
                <otherwise>
                    gender=0
                </otherwise>
            </choose>
        </where>
    </select>


    <!--更新操作,使用 set 标签或者 trim 标签都可以处理","的问题-->
    <update id="updateEmp">
        update tbl_employee
        <trim prefix="set" suffixOverrides=",">
            <if test="lastName != null">
                last_name = #{lastName},
            </if>
            <if test="email != null">
                email = #{email},
            </if>
            <if test="gender != null">
                gender = #{gender}
            </if>
        </trim>
        <where>
            id = #{id}
        </where>
    </update>


    <!--
    foreach 标签的使用:
    collection:指定要遍历的集合。
    item:将当前遍历出的元素赋值给指定的变量。
    separator:每个元素之间的分隔符。
		open:遍历出所有结果拼接一个开始的字符。
		close:遍历出所有结果拼接一个结束的字符。
		index:索引。遍历list的时候是index就是索引,item就是当前值;遍历map的时候index表示的就是map的key,item就是map的值
    -->
    <select id="getEmpsByConditionForeach" resultType="employee">
        select * from tbl_employee where id in
        <foreach collection="ids" item="item_id" separator="," open="(" close=")">
            #{item_id}
        </foreach>
    </select>

  
    <!--批量添加 方式1-->
    <insert id="addEmps">
        insert into tbl_employee(last_name, gender, email, d_id)
        values
        <foreach collection="emps" item="emp" separator=",">
            (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
        </foreach>
    </insert>

  
    <!--
    批量添加 方式2,发送多条SQL语句的形式
    需要 mysql 数据库连接的 url 加上 allowMultiQueries=true
    -->
    <insert id="addEmpsMultiSql">
        <foreach collection="emps" item="emp" separator=";">
            insert into tbl_employee (last_name, gender, email, d_id)
            values (#{emp.lastName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
        </foreach>
    </insert>
</mapper>

缓存

一级缓存(本地缓存):sqlSession级别的缓存,一级缓存默认是一直开启的。

 /**
     * 一级缓存:(本地缓存):sqlSession级别的缓存。一级缓存默认是一直开启的;SqlSession级别的一个Map
     * 与数据库同一次会话期间查询到的数据会放在本地缓存中。
     * 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
     * <p>
     * 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询):
     * 1、sqlSession不同。
     * 2、sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
     * 3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
     * 4、sqlSession相同,手动清除了一级缓存(缓存清空)
     */
    @Test
    public void testFirstLevelCache() {

        SqlSession sqlSession = sqlSessionFactory.openSession();
        MoneyMapper mapper = sqlSession.getMapper(MoneyMapper.class);
        Money money = mapper.selectMoneyById(1);
        System.out.println("money = " + money);


        /**
         * 这一次查询没有数据库查询,没有日志输出,走的是一级缓存
         */
        Money money1 = mapper.selectMoneyById(1);
        System.out.println("money1 = " + money1);
        System.out.println(money == money1);//true


        System.out.println("1.====================================================");
        //1sqlSession不同。
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        Object o = sqlSession1.selectOne("org.example.mapper.MoneyMapper.selectMoneyById", 1);
        System.out.println("o = " + o);

        System.out.println("2.====================================================");
        //sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
        mapper.selectMoneyById(2);


        System.out.println("3.====================================================");
        //sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
        mapper.deleteMoneyById(15);
        Money money2 = mapper.selectMoneyById(2);

        System.out.println("4.====================================================");
        //sqlSession相同,手动清除了一级缓存(缓存清空)
        sqlSession.clearCache();
        //如果上面的缓存部清空,我们这次的查询是不走数据库的
        Money money3 = mapper.selectMoneyById(2);
        System.out.println("money3 = " + money3);
        System.out.println(money2==money3);

        sqlSession1.close();
    }

二级缓存:需要先在 MyBatis 全局配置文件(mybatis-config.xml) 文件中手动开启:

<setting name="cacheEnabled" value="true"/>

需要在SQL映射文件中做如下配置:

二级缓存(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存。,在xml映射文件中添加:

 <cache eviction="FIFO" readOnly="true" size="1024"/>
    <!--
    eviction:缓存的回收策略:
        • LRU – 最近最少使用的:移除最长时间不被使用的对象。
        • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
        • 默认的是 LRU。
    flushInterval:缓存刷新间隔
        缓存多长时间清空一次,默认不清空,设置一个毫秒值
    readOnly:是否只读:
        true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
        false:非只读:mybatis觉得获取的数据可能会被修改。
                mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
    size:缓存存放多少元素;
    type="":指定自定义缓存的全类名;
            实现Cache接口即可;
    -->
 /**
     * 二级缓存:(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存:
     * 工作机制:
     * 1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
     * 2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
     * 3、sqlSession===EmployeeMapper==>Employee
     * DepartmentMapper===>Department
     * 不同namespace查出的数据会放在自己对应的缓存中(map)
     * 效果:数据会从二级缓存中获取
     * 查出的数据都会被默认先放在一级缓存中。
     * 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
     * 使用:
     * 1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
     * 2)、去mapper.xml中配置使用二级缓存:
     * <cache></cache>
     * 3)、我们的POJO需要实现序列化接口
     */

    <select id="selectMoneyById" resultType="org.example.pojo.Money" useCache="true">
        select * from money where id = #{id} 
    </select>

    @Test
    public void testSecondLevelCache() throws Exception {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();


        MoneyMapper mapper1 = sqlSession1.getMapper(MoneyMapper.class);
        MoneyMapper mapper2 = sqlSession2.getMapper(MoneyMapper.class);

        Money money1 = mapper1.selectMoneyById(1);
        System.out.println(money1);
        sqlSession1.close();

        Money money2 = mapper2.selectMoneyById(1);
        System.out.println(money1 == money2);//true
        sqlSession2.close();
    }

和缓存有关的属性或设置

1、全局配置文件中的setting标签中的

  • cacheEnabled 属性
  • false:表示关闭二级缓存关闭,一级缓存仍可使用。
  • true:表示开启二级缓存。
  • localCacheScope 属性,表示本地缓存作用域。
  • SESSION:一级缓存,也是默认值,当前会话的所有数据保存在会话缓存中。
  • STATEMENT:可以禁用一级缓存。

2、每个select标签都有useCache属性

  • true:表示使用二级缓存。
  • false:表示不使用二级缓存,但是一级缓存依然可以使用。

3、每个insertupdatedelete标签的:flushCache="true":表示一级二级都会清除。

增删改执行完成后就会清楚缓存;

测试:flushCache=”true”:一级缓存就清空了;二级也会被清除;

4、每个select标签的:flushCache="false":表示不会清除缓存,但是如果设置flushCache=true,那么每次查询之后都会清空缓存,缓存是没有被使用的。

5、如果程序中调用:sqlSession.clearCache();表示只是清除当前session的一级缓存,二级缓存不受影响。


MyBatis的功能架构是什么样的?

我们一般把Mybatis的功能架构分为三层:

  • API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

为什么Mapper接口不需要实现类?

四个字回答:动态代理,我们来看一下获取Mapper的过程:

  • 获取Mapper我们都知道定义的Mapper接口是没有实现类的,Mapper映射其实是通过动态代理实现的。
BlogMapper mapper = session.getMapper(BlogMapper.class);
  • 七拐八绕地进去看一下,发现获取Mapper的过程,需要先获取MapperProxyFactory——Mapper代理工厂。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}
  • MapperProxyFactory的作用是生成MapperProxy(Mapper代理对象)。
    public class MapperProxyFactory<T> {
        private final Class<T> mapperInterface;
        ……
        protected T newInstance(MapperProxy<T> mapperProxy) {
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
    
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }

这里可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象(占位),而代理的方法被放到了MapperProxy中。

  • MapperProxy里,通常会生成一个MapperMethod对象,它是通过cachedMapperMethod方法对其进行初始化的,然后执行excute方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}
  • MapperMethod里的excute方法,会真正去执行sql。这里用到了命令模式,其实绕一圈,最终它还是通过SqlSession的实例去运行对象的sql。

Mybatis都有哪些Executor执行器?

Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

Mybatis中如何指定使用哪一种Executor执行器?

  • 在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
  • 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。

MyBatis的工作原理吗?

我们已经大概知道了MyBatis的工作流程,按工作原理,可以分为两大步:生成会话工厂会话运行

构造会话工厂也可以分为两步:

获取配置

获取配置这一步经过了几步转化,最终由生成了一个配置类Configuration实例,这个配置类实例非常重要,主要作用包括:

  • 读取配置文件,包括基础配置文件和映射文件
  • 初始化基础配置,比如MyBatis的别名,还有其它的一些重要的类对象,像插件、映射器、ObjectFactory等等
  • 提供一个单例,作为会话工厂构建的重要参数
  • 它的构建过程也会初始化一些环境变量,比如数据源
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
   SqlSessionFactory var5;
   //省略异常处理
       //xml配置构建器
       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
       //通过转化的Configuration构建SqlSessionFactory
       var5 = this.build(parser.parse());
}

构建SqlSessionFactory

SqlSessionFactory只是一个接口,构建出来的实际上是它的实现类的实例,一般我们用的都是它的实现类DefaultSqlSessionFactory,

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

会话运行

会话运行是MyBatis最复杂的部分,它的运行离不开四大组件的配合:

Executor(执行器)

Executor起到了至关重要的作用,SqlSession只是一个门面,相当于客服,真正干活的是是Executor,就像是默默无闻的工程师。它提供了相应的查询和更新方法,以及事务方法。

Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//通过Configuration创建executor
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

StatementHandler(数据库会话器)

StatementHandler,顾名思义,处理数据库会话的。我们以SimpleExecutor为例,看一下它的查询方法,先生成了一个StatementHandler实例,再拿这个handler去执行query。

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     Statement stmt = null;

     List var9;
     try {
         Configuration configuration = ms.getConfiguration();
         StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
         stmt = this.prepareStatement(handler, ms.getStatementLog());
         var9 = handler.query(stmt, resultHandler);
     } finally {
         this.closeStatement(stmt);
     }

     return var9;
 }

ParameterHandler (参数处理器)

PreparedStatementHandler里对sql进行了预编译处理

public void parameterize(Statement statement) throws SQLException {
    this.parameterHandler.setParameters((PreparedStatement)statement);
}

这里用的就是ParameterHandler,setParameters的作用就是设置预编译SQL语句的参数。

里面还会用到typeHandler类型处理器,对类型进行处理。

  public interface ParameterHandler {
        Object getParameterObject();
    
        void setParameters(PreparedStatement var1) throws SQLException;
    }

ResultSetHandler(结果处理器)

我们前面也看到了,最后的结果要通过ResultSetHandler来进行处理,handleResultSets这个方法就是用来包装结果集的。Mybatis为我们提供了一个DefaultResultSetHandler,通常都是用这个实现类去进行结果的处理的。

 public interface ResultSetHandler {
        <E> List<E> handleResultSets(Statement var1) throws SQLException;
    
        <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
    
        void handleOutputParameters(CallableStatement var1) throws SQLException;
    }

它会使用typeHandle处理类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。

整体上总结一下会话运行:

我们最后把整个的工作流程串联起来,简单总结一下

  1. 读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。
  2. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  3. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
  4. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  5. StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。
  6. 参数处理:对输入参数的类型进行处理,并预编译。
  7. 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。

整合 Spring、逆向工程

MyBatis Generator (MBG) 是 MyBatis MyBatis 的代码生成器。 它将为所有版本的 MyBatis 生成代码。 它将内省数据库 表(或许多表),并将生成可用于 访问表。这减少了设置对象和配置的初始麻烦 文件与数据库表交互。MBG 寻求对大型 属于简单 CRUD(Create、Retrieve、Update、Delete)的数据库作的百分比。您将 仍然需要为连接查询或存储过程手动编写 SQL 和对象代码。

<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.2</version>
</dependency>

创建mbg.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "https://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    
<!--    targetRuntime:
        1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
        2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
-->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 生成的代码中将不会包含生成日期的注释 -->
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!-- 配置数据库连接 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/erp1?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=GMT%2B8"
                        userId="root"
                        password="0000">
        </jdbcConnection>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- 指定javaBean生成的位置 -->
        <javaModelGenerator targetPackage="com.atguigu.crud.bean"
                            targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!--指定sql映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="crudmapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>

        <!-- 指定dao接口生成的位置,mapper接口 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.atguigu.crud.dao" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>


        <!-- table指定每个表的生成策略 -->
        <table tableName="dept" domainObjectName="Dept"></table>
        <table tableName="money" domainObjectName="Money"></table>
    </context>
</generatorConfiguration>

测试代码生成

package test;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class MBGTest {

    //这里main方法没法用,在测试类中@Test完成的
    public static void main(String[] args) throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                callback, warnings);
        myBatisGenerator.generate(null);
    }


}

分类: MyBatis

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注