面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)

HoverInTheSky
发布于 2022-6-28 17:38
浏览
0收藏

10.Mybatis是否支持延迟加载?原理?


 •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,支持延迟加载的原理都是一样的。


11.如何获取生成的主键?


 •新增标签中添加:keyProperty=" ID "  即可

<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
    insert into user( 
    user_name, user_password, create_time) 
    values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>

 •这时候就可以完成回填主键

mapper.insert(user);
user.getId;

12.MyBatis支持动态SQL吗?

 

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

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区

 ![

 

 •if

 

根据条件来组成where子句

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

•choose (when, otherwise)

 

这个和Java 中的 switch 语句有点像

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

•trim (where, set)

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

◆<set> 可以用在动态更新的时候

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

◆<where>可以用在所有的查询条件都是动态的情况


 • foreach

 

看到名字就知道了,这个是用来循环的,可以对集合进行遍历

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  <where>
    <foreach item="item" index="index" collection="list"
        open="ID in (" separator="," close=")" nullable="true">
          #{item}
    </foreach>
  </where>
</select>

13.MyBatis如何执行批量操作?

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区 MyBatis批量操作


第一种方法:使用foreach标签

 

foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator,close。

 

 • item      表示集合中每一个元素进行迭代时的别名,随便起的变量名;
 • index    指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
 • open    表示该语句以什么开始,常用“(”;
 • separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
 • close    表示以什么结束,常用“)”。


在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下3种情况:

 

1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的, map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key

 

看看批量保存的两种用法:

<!-- MySQL下批量保存,可以foreach遍历 mysql支持values(),(),()语法 --> //推荐使用
<insert id="addEmpsBatch">
    INSERT INTO emp(ename,gender,email,did)
    VALUES
    <foreach collection="emps" item="emp" separator=",">
        (#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
    </foreach>
</insert>
<!-- 这种方式需要数据库连接属性allowMutiQueries=true的支持
 如jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->  
<insert id="addEmpsBatch">
    <foreach collection="emps" item="emp" separator=";">                                 
        INSERT INTO emp(ename,gender,email,did)
        VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})
    </foreach>
</insert>

第二种方法:使用ExecutorType.BATCH

 

 • Mybatis内置的ExecutorType有3种,默认为simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优;但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,在某些情况下不符合业务的需求。

 

具体用法如下:

//批量保存方法测试
@Test  
public void testBatch() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    //可以执行批量操作的sqlSession
    SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

    //批量保存执行前时间
    long start = System.currentTimeMillis();
    try {
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        for (int i = 0; i < 1000; i++) {
            mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
        }

        openSession.commit();
        long end = System.currentTimeMillis();
        //批量保存执行后的时间
        System.out.println("执行时长" + (end - start));
        //批量 预编译sql一次==》设置参数==》10000次==》执行1次   677
        //非批量  (预编译=设置参数=执行 )==》10000次   1121

    } finally {
        openSession.close();
    }
}

 •mapper和mapper.xml如下

public interface EmployeeMapper {   
    //批量保存员工
    Long addEmp(Employee employee);
}
<mapper namespace="com.jourwon.mapper.EmployeeMapper"
     <!--批量保存员工 -->
    <insert id="addEmp">
        insert into employee(lastName,email,gender)
        values(#{lastName},#{email},#{gender})
    </insert>
</mapper>

14.说说Mybatis的一级、二级缓存?


1.一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开一级缓存。

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区

 Mybatis一级缓存


2.二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同之处在于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置。

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区
Mybatis二级缓存示意图


原理


15.能说说MyBatis的工作原理吗?


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

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区

 MyBatis的工作流程


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最复杂的部分,它的运行离不开四大组件的配合:

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区

 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;
    }

再以最常用的PreparedStatementHandler看一下它的query方法,其实在上面的prepareStatement已经对参数进行了预编译处理,到了这里,就直接执行sql,使用ResultHandler处理返回结果。

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
    }

•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提供的规则组装对象,返回给调用者。


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

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区

 会话运行的简单示意图


PS:以上源码分析比较简单,在真正的源码大佬面前可能过不了关,有条件的建议Debug一下MyBatis的源码。

 

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

面渣逆袭:二十二图、八千字、二十问,彻底搞定MyBatis!(二)-鸿蒙开发者社区 MyBatis整体工作原理图


1.读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。

 

2.构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

 

3.创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

 

4.Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。


5.StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。

 

6.参数处理:对输入参数的类型进行处理,并预编译。

 

7.结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。

标签
已于2022-6-28 17:38:19修改
收藏
回复
举报
回复
    相关推荐