MyBatis study notes and experience

1. Traditional JDBC method

Register JDBC driver: Class.forName("com.mysql.jdbc.Driver");

         Create connection: DriverManager.getConnection(DB_URL, USER, PASSWORD);

         Execute the query: conn.createStatement();

     Get the result set: stmt.executeQuery("sql") Finally close related resources

2.MyBatis--ORM framework

ORM: Object Relational Mapping object, mapping, relational database.

"Semi-automated" ORM framework: Separation of sql and code

Core features:

a. Use the connection pool to manage the connection

b.sql and code separation, centralized management

c. Result set mapping

d. Parameter mapping and dynamic sql

e. Repeat the extraction of sql

f. Plug-in mechanism

If we need to use more flexible sql in the project, mybatis is undoubtedly very suitable.

If the underlying coding or performance requirements are very high, you can use Spring JDBC

3. How to use MyBatis

1. First introduce the mybatis jar package

2. Create a global configuration file for Mybatis

3. Create a mapper.xml mapping file corresponding to the table, in which you can configure the SQL statements we add, delete, modify, and query, as well as the mapping relationship between parameters and returned result sets

4. Create a session and perform operations on the database. Simple example:

@Test
    public void testStatement() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new  
            SqlSessionFactoryBuilder().build(inputStream);
       
        try {
          SqlSession session = sqlSessionFactory.openSession();
          BlogEntityMapper mapper = session.getMapper(BlogEntityMapper.class);
          List<BlogEntity> list = mapper.selectBlogList();
          System.out.println(list);
        } finally {
            session.close();
        }
    }

4. MyBatis core object life cycle

Core objects SqlSessionFactoryBuilder, SqlSessionFactory, SqlSession and Mapper object life cycle

Understanding the life cycle of MyBatis core objects helps us write efficient code

4.1:SqlSessionFactoryBuilder

This object is used to build SqlSessionFactory, and only one SqlSessionFactory is required. So just build a SqlSessionFactory. Its mission is accomplished. Therefore, the life cycle of SqlSessionFactoryBuilder only exists locally in the method.

4.2:SqlSessionFactory

SqlSeesionFactory is used to create SqlSession. Whenever an application accesses a database, a session needs to be created. Because we always have the need to create a session. So SqlSessionFactory should exist throughout the life cycle of the application. So we need to use singleton mode to create SqlSessionFactory objects to avoid confusion and save resources.

4.3:SqlSession

sqlSession is a session because it is not thread safe. So it cannot be shared between threads. So we need to create a SqlSession object when the request starts, and close it when the request ends or the method is executed.

4.4:Mapper

mapper is actually a proxy object, obtained from SqlSession. The role is to send SQL to manipulate the data in the database. Should exist within a SqlSession transaction method

5. Core configuration

Reference: https://mybatis.org/mybatis-3/zh/configuration.html

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--MyBatis配置文件的跟标签,对应MyBatis源码中的Configuration配置类-->
<!--此标签下的一级标签需要全部掌握-->
<configuration>
    <!--设置数据连接配置文件-->
    <properties resource="jdbc.properties"/>
    
    <settings>
        <!-- 配置日志打印  start -->
        <setting name="cacheEnabled" value="true" />
        <setting name="useGeneratedKeys" value="true" />
        <setting name="defaultExecutorType" value="REUSE" />
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!-- 配置日志打印  end -->

        <!-- 配置延迟加载开关 解决查询可能遇到的 N+1 问题 -->
        <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
        <setting name="lazyLoadingEnabled" value="false" />
        <!--当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过 select 标签的fetchType 来覆盖-->
        <setting name="aggressiveLazyLoading" value ="false" />
        <!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认 JAVASSIST -->
        <!--<setting  name="proxyFactory"  value="CGLIB" />-->

        <!-- 缓存 -->
        <!-- localCacheScope 设置为 session 缓存一个会话中执行的所有查询  -->
        <!--<setting name="localCacheScope" value="SESSION"/>-->
        <!--<setting name="localCacheScope" value="STATEMENT"/>-->

        <!-- 开启二级缓存 step one : 配置cacheEnabled 为 true  step two : 在 mapper.xml 中配置 <cache/> 标签 -->
        <!-- 控制全局缓存(二级缓存) 默认开启-->
        <!--<setting name="cacheEnabled" value="true"/>-->

    </settings>

    <!-- 用来配置类型的别名 -->
    <typeAliases>
        <!-- 表名实体配置别名 -->
        <typeAlias type="com.zh.entity.StudentEntity" alias="Student" />
        <typeAlias type="com.zh.entity.TeamEntity" alias="Team" />
        <typeAlias type="com.zh.entity.AuthorEntity" alias="Author" />
        <typeAlias type="com.zh.entity.BlogEntity" alias="Blog" />
        <typeAlias type="com.zh.entity.CommentEntity" alias="Comment" />
        <typeAlias type="com.zh.entity.UserEntity" alias="User" />

        <!-- 关联查询对应vo -->
        <typeAlias type="com.zh.entity.vo.BlogAndAuthorVo" alias="BlogAndAuthor" />
        <typeAlias type="com.zh.entity.vo.BlogAuthorVo" alias="BlogAndAuthorVo" />

        <!--<typeAlias type="com.config.handler.MyTypeHandler" alias="MyHandler" />-->
    </typeAliases>

    <!-- 配置自定义的类型转换 -->
    <!--<typeHandlers>-->
        <!--<typeHandler handler="com.config.handler.MyTypeHandler" />-->
    <!--</typeHandlers>-->

    <!-- 注册自定义插件 -->
    <!--<plugins>-->
        <!--<plugin interceptor="com.interceptor.CustomPageInterceptor">-->
            <!--<property name="customSql" value="mySelfSql" />-->
        <!--</plugin>-->
    <!--</plugins>-->


    <!--分页插件的注册-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 4.0.0以后版本可以不设置该参数 ,可以自动识别
            <property name="dialect" value="mysql"/>  -->
            <!-- 该参数默认为false -->
            <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="true"/>
            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
            <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
            <!-- 不理解该含义的前提下,不要随便复制该配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
            <!-- 支持通过Mapper接口参数来传递分页参数 -->
            <property name="supportMethodsArguments" value="true"/>
            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
            <property name="returnPageInfo" value="check"/>
        </plugin>
    </plugins>
    
    <environments default="development">
        <environment id="development">
            <!-- type=JDBC : 表示使用JDBC的事务 -->
            <!--
                 单独使用时配置成MANAGED没有事务,此时的事务会交给我们使用的容器去处理,没有容器,没有事务。
                 集成到Spring中的时候,会覆盖transactionManager的事务
           -->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 可以自定义配置连接池 c3p0 dbcp druid 等  -->
                <!-- 若是与 spring 集成,则在 application.xml 中配置数据源  -->
                <!--<dataSource type="com.config.datasource.DruidDatasource">-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>



        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/StudentEntityMapper.xml"/>
        <mapper resource="mapper/TeamEntityMapper.xml"/>
        <mapper resource="mapper/AuthorEntityMapper.xml"/>
        <mapper resource="mapper/BlogEntityMapper.xml"/>
        <mapper resource="mapper/CommentEntityMapper.xml"/>
        <mapper resource="mapper/UserEntityMapper.xml"/>
    </mappers>

</configuration>

6. MyBatis workflow analysis

MyBatis 工作流程分析
  基础架构:接口层、核心层、基础层
      { 接口层:核心对象 SqlSession 是上层应用和MyBatis打交道的桥梁 }
      { 核心层:1.解析接口入参。2.解析xml文件中的Sql语句。3.执行sql语句。4.处理结果集,并映射成Java对象 }
      { 基础层:数据源、缓存、日志、xml解析、反射、IO事务 }
  MyBatis 缓存
    { 种类:基本缓存、淘汰算法缓存、装饰器缓存 }
    { 一级缓存:也叫本地缓存,默认开启,不需要任何配置。
                |-一级缓存放在Executor中维护,存在于BaseExecutor中的query()-queryFromDatabase()存入一级缓存。
                  在queryFromDatabase()之前调用get()方法调用缓存
                |-一级缓存在同一个session中共享,在同一会话中update(delete)操作会导致一级缓存被清空
                |-一级缓存不能跨会话共享
    }
    { 二级缓存:用来解决一级缓存不能跨会话共享的问题,范围是 namespace 级别,可以被多个 sqlSession 共享
               |-若是启用二级缓存 MyBatis 在创建 Executor 对象的时候会对 Executor 进行装饰 即 CachingExecutor
               |-开启二级缓存的方法
               |-配置 <cache />  标签后,select()会被缓存。update() delete() insert() 会刷新缓存  可以在单个的 StatementID 上显式关闭二级缓存(默认true)
               |-事务不提交,二级缓存不生效
               |-使用 <cache-ref />  标签可以让多个 namespace 共享一个二级缓存
    }
    { 第三方缓存做二级缓存 : 实现 cache 接口来自定义二级缓存 }
  MyBatis 原理
    { mappers标签的解析:  首先会判断是不是接口和是否已经注册
                          XMLMapperBuilder.parse()方法--对mapper映射器的解析
                          bindMapperForNamespace()方法--把namespace(接口类型)和工厂类绑定起来
                          最终会调用MapperRegistry的addMapper()方法  最后调用build()方法,返回DefaultSqlSessionFactory }

    { 会话创建:openSession()方法来创建 }

    { 事务:配置 JDBC 则事务交给Connection对象的commit()、rollback()、close()来管理事务
           配置 MANAGED 会把事务交给容器来管理,比如JBoss,WebLogic
           Spring+MyBatis 则没有必要配置,在applicationContext.xml中有配置数据源和事务管理器,覆盖MyBatis配置 }

    { Executor:基本类型有三种 - SIMPLE、BATCH、REUSE 默认是SIMPLE
               继承抽象类BaseExecutor然后实现顶层接口Executor : 这里使用了模板方法模式
               (定义一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤)
               三种类型的区别: Simple -- 每执行一次 update 或 select , 就开启一个Statement对象,用完立刻关闭 Statement 对象
                               Reuse  -- 以sql作为key查找Statement对象,存在就使用,不存在就创建。用完后不关闭,放置于map中,供下一次使用。重复使用Statement对象
                               Batch  -- 执行 update (JDBC批处理不支持select) 将所有的SQL都添加到批处理中(通过addBatch()方法) 等待(executeBatch()方法执行) }

    { 如何获得Mapper对象-Mapper对象的本质 : 在解析mapper标签和mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个Map中。获取Mapper代理对象。
                                           最终通过代理模式获得代理对象 }

    { JDK动态代理和MyBatis动态代理 :  JDK动态代理,在实现了InvocationHandler的代理类里面,需要传入一个被代理对象实现的实现类
                                     MyBatis动态代理 根据接口类型和方法名称找到 StatementID 获取代理对象,直接在MapperProxy里面直接执行逻辑(Sql) }

    { Mapper接口没有实现类 : 怎么找到我们要执行的SQL的 -- 所有的Mapper都是 MapperProxy 代理对象  任意方法都是执行 MapperProxy 的 invoke 方法
                            Mapper对象通过DefaultSqlSession对象的getMapper()方法获得
                            getMapper()方法中通过 configuration 对象的 getMapper() 方法获得
                            在该方法中通过MapperRegistry对象的getMapper()方法中获取,MapperRegistry对象由Configuration对象持有,在配置加载的时候
                            把mapper接口和其对应的MapperProxyFactory存放在MapperRegistry对象的knownMappers对象中。
                            最终通过mapper接口获取其所对应的MapperProxyFactory对象,最终通过MapperProxy代理生成mapper对象。
                            调用 mapper 接口,本质上是调用其所生成的代理对象。通过反射调用最终实现对数据库的操作。
                            invoke方法所做的事情:
                               1.首先判断是否需要去执行SQL,还是直接执行方法
                               2.获取缓存(为了提升MapperMethod的获取速度)
                               3.接下来调用mapperMethod的execute()方法
                               4.调用mapperMethod的execute()方法
                               5.convertArgsToSqlCommandParam()方法将参数转化为SQL参数,根据类型调用sqlSession的insert()  update()  delete()...方法
                               6.以selectOne()为例,最终会走到DefaultSqlSession.selectOne(),调用selectList(),调用Executor的query()方法
                               7.Executor的类型是DefaultSqlSession初始化的时候赋值的。若开启二级缓存会先调用CachingExecutor的query()方法否则会先走到BaseExecutor的query()方法
                               8.BaseExecutor.query() 创建cacheKey--缓存的key--调用另一个query()方法
                               9.对本地缓存进行操作 如果没有缓存,会从数据库查询:queryFromDatabase()  执行Executor的doQuery()方法
                               -------查询流程------
                               a.创建StatementHandler - 包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler
                               b.由StatementHandler创建Statement对象 - prepareStatement() 方法对语句进行预编译,处理参数
                               c.执行StatementHandler对象的query()方法 delegate 委派,最终执行 PreparedStatementHandler 的 query()方法
                               d.执行 t PreparedStatement  的  execute() 方法  最后交给JDBC操作
                               e.ResultHandler处理结果集   }

    { MyBatis插件原理 : 自定义插件和PageHelper
                        编写插件类 CustomPageInterceptor.java   实现接口 添加注解
                        可拦截对象   :Executor  StatementHandler  ParameterHandler  ResultSetHandler
                        创建代理对象 :JDK动态代理,需要实现InvocationHandler的代理类  }

 

Guess you like

Origin blog.csdn.net/GoNewWay/article/details/103964395