不断的学习,我们才能不断的前进
一个好的程序员是那种过单行线马路都要往两边看的人

Mybatis

1. 什么是Mybatis?

  1. Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
  2. MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  3. MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2. Mybatis的优缺点

  • 优点
    • 基于SQL语句编程,相当灵活。SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
    • 跟JDBC相比,减少了80%的代码量,消除了大量的冗余代码,而且不需要手动关闭连接。
    • 在spring上面有很好的集成
  • 缺点:
    • 当sql语句关联表比较多时,sql比较复杂。
    • sql语句依赖于数据库,对数据库的依赖比较大。

3. 如何使用

  1. Maven导入mybatis依赖
  2. 新建mybatis-config.xml配置文件,然后配置jdbc连接
  3. 新建一个工具类,封装获取sqlsession对象。
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            // 获取sqlsessionfactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = null;
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        //注意这里是线程不安全的,参数true表示自动提交事务
        return sqlSessionFactory.openSession();

    }

SqlSessionFactoryBuilder 工厂方法用来创建SqlSessionFactory,一旦创建就不需要它了
SqlSessionFactory使用的是单例模式(用静态类的创建方式)一旦创建就一直存在
每个线程都有自己的SqlSession,但是他是线程不安全的,不能被共享,最佳是放在方法域里面,绝对不能将SqlSession实例的引用放在一个类的静态域。 SqlSession使用完必须关闭。 sqlSessionFactory.openssion()会产生sqlsession对象,参数true会自动提交事务,默认为false;

  1. 创建一个mapper接口,和对应的mapper.xml配置文件;注意记得在mybatis中注册这个配置文件。(也可以使用注解来完成)

4. mybatis 的配置

基本结构:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
  • databaseIdProvider(数据库厂商标识)
  • mappers(映射器)

properties(属性)

用于导入外部的配置属性 或者在property子元素里面进行配置。也可在SqlSessionFactoryBuilder.build() 方法中传入属性值。

<!--导入外部(db.properties)依赖配置-->
    <properties resource="db.properties">
        <property name="username" value="dev_user"/>
    </properties>

mybatis属性读取顺序 :

  1. 首先读取在 properties 元素体内指定的属性
  2. 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,并覆盖之前读取过的同名属性
  3. 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性

优先级:方法中传入的属性 >> 配置文件resource中的属性 >> property子元素里面的属性

settings(设置)

用于设置mybatis的行为方式

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

类型别名(typeAliases)

可以用于给java类型设置一个别名,它仅用于 XML 配置,意在降低冗余的全限定类名书写。

    <!--设置别名,支持注解设置别名-->
    <typeAliases>
        <typeAlias type="com.laijinhan.dto.UserDTO" alias="UserDTO"/>
        <!--给包下的实体类设置别名-->
        <package name="com.laijinhan.dto"/>
    </typeAliases>

这样配置后UserDTO 就可以用在任何使用com.laijinhan.dto.UserDTO的地方来。
package 是给指定包下所有的类设置别名;比如com.laijinhan.dto.UserDTO别名为userDTO(首字母小写)
也可以在类上面写@Alias("author") 设置注解别名
mybatis 也包含了默认的别名,具体参考官方文档。

类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
例如:数据库中的NUMERIC 或 INTEGER 转换成java中的java.lang.Integer, int

  1. 自定义类型处理器
    实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如 处理枚举类型,把数据库中的bool,映射为枚举字符串:
**
 * @Author laijinhan
 * @date 2020/9/16 9:22 下午
 */
public class EnabledTypeHandler implements TypeHandler<Enabled> {
    private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>();
    public EnabledTypeHandler(){
        for(Enabled enabled:Enabled.values()){
            enabledMap.put(enabled.getValue(),enabled);
        }
    }

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, Enabled enabled, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i,enabled.getValue());
    }

    @Override
    public Enabled getResult(ResultSet resultSet, String s) throws SQLException {
        Integer anInt = resultSet.getInt(s);
        return enabledMap.get(anInt);
    }

    @Override
    public Enabled getResult(ResultSet resultSet, int i) throws SQLException {
        Integer value=resultSet.getInt(i);
        return enabledMap.get(value);
    }

    @Override
    public Enabled getResult(CallableStatement callableStatement, int i) throws SQLException {
        Integer value=callableStatement.getInt(i);
        return enabledMap.get(value);
    }
}

定义的枚举类:

**
 * @Author laijinhan
 * @date 2020/9/16 9:16 下午
 */
public enum Enabled {
    disabled(0),
    enabled(1);
    private  final  int value;
    private  Enabled(int value){
        this.value=value;
    }
    public  int getValue(){
        return this.value;
    }
}

mybatis-config.xml 进行映射配置

    <!--类型处理器,把数据库的bool类型映射到java的enum类型-->
    <typeHandlers>
        <typeHandler handler="com.laijinhan.typehandler.EnabledTypeHandler" javaType="com.laijinhan.enums.Enabled"/>
    </typeHandlers>

对象工厂objectFactory

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现;例如

插件(plugins)

环境配置(environments)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<environments default="test">
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

默认使用的环境 ID(比如:default="development")。
每个 environment 元素定义的环境 ID(比如:id="development")。
事务管理器的配置(比如:type="JDBC")。 还有transactionManager
数据源的配置(比如:type="POOLED")。

type="JDBC" 支持回滚和提交事务

映射器(mappers)

就是告诉 MyBatis 到哪里去找到这些配置好的sql语句(mapper.xml文件)

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <package name="org.mybatis.builder"/>
</mappers>

resource使用相对于类路径的资源引用
url 使用完全限定资源定位符(URL)
class 使用映射器接口实现类的完全限定类名 注意mapper类和mapper.xml文件必须在一个包下
将包内的映射器接口实现全部注册为映射器 注意mapper类和mapper.xml文件必须在一个包下

5. xml映射文件

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

使用缓存只需要添加

结果映射 resultMap

用来处理实体类属性和查询出的结果集字段不一致问题;也可通过设置别名来解决这个问题

  1. association 用来处理一对一的问题(处理单个对象)
<resultMap id="StudentTeacher1" type="StudentDTO">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="TeacherDTO">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
    <select id="getStudent1" resultMap="StudentTeacher1">
        select s.id sid ,s.name sname ,t.name tname  from student s,teacher t
        where s.tid=t.id;
    </select>

这里一个学生对象里面有个字段是老师,需要使用association来处理
2. collection 处理一对多的问题

<select id="getTeacherAllStudents" resultMap="TeacherStudent">
        select s.id sid,s.name sname, t.name tname ,t.id tid from mybatis.teacher t,student s where s.tid=t.id and t.id = #{id};
    </select>
    <resultMap id="TeacherStudent" type="TeacherDTO">
        <result property="tid" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="StudentDTO">
            <result property="id"  column="sid"/>
            <result property="name"  column="sname"/>
        </collection>
    </resultMap>

这里一个老师对应多个学生,就是一个学生集合,用collection来处理

property对应实体类中的属性
column对应数据库结果集中的字段名

缓存

mybatis 默认存在两种缓存方式:
一级缓存(默认情况):sqlsession级别的缓存,也称为本地缓存
二级缓存: 基于namespace的缓存

可以自定义缓存接口Cache来实现二级缓存。

缓存策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  1. 一级缓存
    只在 sqlsession 开启和关闭之间进行缓存。
    映射语句文件中的所有 select 语句的结果将会被缓存。
    映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    缓存不会定时进行刷新(也就是说,没有刷新间隔)。

sqlsession.clearCache() 可以手动清理缓存

  1. 二级缓存
    在mybatis配置文件的setting中开启缓存,然后在sql的映射文件中添加 <cache/>标签即可 使用二级缓存

基于namespace(sql映射文件里面哪一个namespace)级别的缓存

只要开启了二级缓存,在一个mapper内就有效
所有的数据都先存储在一级缓存中,当会话提交或者关闭后才提交到二级缓存中。
顺序:先查询二级缓存,在查看一级缓存。

二级缓存需要序列化返回的实体对象

  1. 自定义缓存
    ehcache

参考文档

mybatis二级缓存源码分析

6. 动态sql语句

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

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

choose

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

where ,set

  1. where
<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>

会自动删除 where标签 或者 where标签后的and or

  1. 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>

foreach

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

bind

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

7. Mybatis的拦截器

待补充。。。

8. 常见的面试问题

1. 模糊查询like怎么写?

<select id=”selectlike”>
select * from foo where bar like #{value}
</select>

在java里面写模糊匹配;eg:String s=“%smi%”;

<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
</select>

在sql语句里面写模糊匹配,但是存在注入攻击

2. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。
Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。
在 Mybatis 中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MapperStatement 对象。
Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略.
Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法.转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

3. Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?

使用<resultMap>标签 或者 别名 逐一定义数据库列名和对象属性名之间的映射关系。
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

4. 如何获取自动生成的(主)键值?

<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
insert into names (name) values (#{name})
</insert>

insert 方法总是返回一个 int 值 ,这个值代表的是插入的行数.
如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中

5. MyBatis 实现一对一有几种方式?具体怎么操作的?

有联合查询和嵌套查询
联合查询是几个表联合查询,只查询一次, 通过在resultMap 里面配置 association 节点配置一对一的类就可以完成;

嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。

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

7. 使用 MyBatis 的 mapper 接口调用时有哪些要求?

Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的parameterType 的类型相同;
Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的resultType 的类型相同;
Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径

参考文献

mybatis官网
github地址

#{}和${}的区别是什么?

  • ${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver,变量会直接显示在上面,容易被sql注入攻击。
  • #{}是 sql 的参数占位符,MyBatis 会将 sql 中的#{}替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()

# 可以避免sql注入 攻击

XMl文件中常用的标签

Select、Insert、Delete、Update、<resultMap><parameterMap><sql><include><selectKey>,加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中为 sql 片段标签,通过<include>标签引入 sql 片段,<selectKey>为不支持自增的主键生成策略标签。

XMl映射文件一般跟DAO接口对应,这个Dao接口的工作原理?支持方法重载吗?

Dao 接口,就是人们常说的 Mapper接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中MappedStatement的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个MappedStatement

Dao 接口里的方法可以重载,但是Mybatis的XML里面的ID不允许重复。Mybatis 的 Dao 接口可以有多个重载方法,但是多个接口对应的映射必须只有一个,否则启动会报错。

Dao 接口的工作原理是 JDK 动态代理,MyBatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行MappedStatement所代表的 sql,然后将 sql 执行结果返回。

Mybatis 如何进行分页?分页插件的原理是什么?

MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页,可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

MyBatis 的拦截器使用的是 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandlerinvoke()方法,当然,只会拦截那些你指定需要拦截的方法。

Mybatis 如何将sql结果封装成目标对象返回的?

第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。

第二种是使用 sql 列的别名功能,将列别名书写为对象属性名。

有了列名与属性名的映射关系后,MyBatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

Mybatis 支持一对多、一对一的关联查询吗?

支持。关联对象查询,有两种实现方式,一种是单独发送一个 sql 去查询关联对象,赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用 join 查询,一部分列是 A 对象的属性值,另外一部分列是关联对象 B 的属性值,好处是只发一个 sql 查询,就可以把主对象和其关联对象查出来。

association 一对一查询;Collection 一对多查询

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()方法的调用。这就是延迟加载的基本原理。

不同XML映射文件,id是否可以重复

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;

原因就是 namespace+id 是作为 Map<String, MappedStatement>的 key 使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同

Mybatis 如何进行批处理

使用BatchExecutor完成批处理

Mybatis 有哪些Exector处理器?区别是什么?

MyBatis 有三种基本的 Executor 执行器,SimpleExecutorReuseExecutorBatchExecutor

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 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。

Mybatis是否可以映射枚举类?

MyBatis 可以映射枚举类,不单可以映射枚举类,MyBatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandlersetParameter()getResult()接口方法。TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter()getResult()两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果。

Mybatis映射文件中,A标签include引用了B标签,B标签可以定义在A标签后面,还是必须在前面?

虽然 MyBatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,MyBatis 都可以正确识别

原理是,MyBatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,MyBatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,MyBatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。

Mybatis的XMl映射文件 和 Mybatis内部数据结构之间的映射关系?

MyBatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 Xml 映射文件中,<parameterMap>标签会被解析为 ParameterMap 对象,其每个子元素会被解析为 ParameterMapping 对象。<resultMap>标签会被解析为 ResultMap 对象,其每个子元素会被解析为 ResultMapping 对象。每一个<select>、<insert>、<update>、<delete>标签均会被解析为 MappedStatement 对象,标签内的 sql 会被解析为 BoundSql 对象。

缓存

mybatis 默认存在两种缓存方式:
一级缓存(默认情况):sqlsession级别的缓存,也称为本地缓存
二级缓存: 基于namespace的缓存

可以自定义缓存接口Cache来实现二级缓存。

缓存策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

1. 一级缓存

只在 sqlsession 开启和关闭之间进行缓存。
映射语句文件中的所有 select 语句的结果将会被缓存。
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
缓存不会定时进行刷新(也就是说,没有刷新间隔)。

sqlsession.clearCache() 可以手动清理缓存

2. 二级缓存

在mybatis配置文件的setting中开启缓存,然后在sql的映射文件中添加 <cache/>标签即可 使用二级缓存

基于namespace(sql映射文件里面哪一个namespace)级别的缓存
只要开启了二级缓存,在一个mapper内就有效
所有的数据都先存储在一级缓存中,当会话提交或者关闭后才提交到二级缓存中。
顺序:先查询二级缓存,在查看一级缓存。

二级缓存需要序列化返回的实体对象

  1. 自定义缓存
    ehcache

使用 MyBatis 的 mapper 接口调用时有哪些要求?

Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同;
Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的parameterType 的类型相同;
Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的resultType 的类型相同;
Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径

typeHandlers和ResultMap 结果集映射

typeHandlers

typeHandlers 类型处理器可以映射字段类型;比如数据库里面是boolean 类型,映射到java的枚举类型

  1. 先建立一个枚举类型,实现构造器和get方法
  2. 建立一个EnabledTypeHandler类,实现TypeHandler接口
  3. 在mybatis配置文件注册
public enum Enabled {
    disabled(0),
    enabled(1);
    private  final  int value;
    private  Enabled(int value){
        this.value=value;
    }
    public  int getValue(){
        return this.value;
    }
}

public class EnabledTypeHandler implements TypeHandler<Enabled> {
    // Enabled 是设置的枚举类 
    private final Map<Integer, Enabled> enabledMap = new HashMap<Integer, Enabled>();
    public EnabledTypeHandler(){
        for(Enabled enabled:Enabled.values()){
            enabledMap.put(enabled.getValue(),enabled);
        }
    }

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, Enabled enabled, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i,enabled.getValue());
    }

    @Override
    public Enabled getResult(ResultSet resultSet, String s) throws SQLException {
        Integer anInt = resultSet.getInt(s);
        return enabledMap.get(anInt);
    }

    @Override
    public Enabled getResult(ResultSet resultSet, int i) throws SQLException {
        Integer value=resultSet.getInt(i);
        return enabledMap.get(value);
    }

    @Override
    public Enabled getResult(CallableStatement callableStatement, int i) throws SQLException {
        Integer value=callableStatement.getInt(i);
        return enabledMap.get(value);
    }
}
<!--类型处理器,把数据库的bool类型映射到java的enum类型-->
    <typeHandlers>
        <typeHandler handler="com.laijinhan.typehandler.EnabledTypeHandler" javaType="com.laijinhan.enums.Enabled"/>
    </typeHandlers>

ResultMap

用来处理数据库中的字段,跟实体类中的属性不一致的问题,
它可以将从数据库结果集中查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中。
当涉及多个类的时候很有用。

  1. 在mapper.xml文件中写resultMap id为map名,type为对应的实体类
  2. result 里面column为数据库查询出的列名,property为实体类中的属性名

如果实体类的数据库中的字段一致可以不用写result进行映射。

Mybatis 的执行步骤?

  1. Resource获取加载全局配置文件
  2. 实例化sqlsessionFactoryBuilder构造器,下面的步骤是使用SqlSessionFactoryBuilder().build() 获取sqlsessionFactory对象
  3. 配置文件流 XMLConfigBuilder,同来解析配置文件, 从/configuration 顶层结点开始解析,最后会解析mapper配置,会实现Mapper接口和xml映射文件的绑定。
  4. Configuration所有的配置
  5. sqlsession实例化,通过 SqlSessionFactory.openSession 方法获取session对象
  6. 事务管理器
  7. 创建executor执行器
  8. 创建sqlsession
  9. 实现CURD 使用getMapper 获取接口的代理对象
  10. 提交事务

SqlSessionFactoryBuilder 工厂方法用来创建SqlSessionFactory,一旦创建就不需要它了。
SqlSessionFactory使用的是单例模式(用静态类的创建方式)一旦创建就一直存在
SqlSession:每个线程都有自己的SqlSession,但是他是线程不安全的,不能被共享,最佳是放在方法域里面,绝对不能将SqlSession实例的引用放在一个类的静态域。
SqlSession使用完必须关闭。
sqlSessionFactory.openssion()会产生sqlsession对象,参数true会自动提交事务,默认为false;

getsqlsession

java 代码测试

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            // 获取sqlsessionfactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = null;
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 构建sqlsessionfactory对象
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        //注意这里是线程不安全的
        return sqlSessionFactory.openSession(); // 获取SqlSession
    }
}
// 测试类
    @Test
    public  void getStudentTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<StudentDTO> studentList = mapper.getStudent();
        for(StudentDTO student:studentList){
            System.out.println(student);
        }
        sqlSession.close();
    }
    @Test
    public  void getStudentTest1(){
        // DefaultSqlSession 里面有Configuration 和 Executor
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 通过动态代理,获取Mapper接口的代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        // 执行代理对象的代理方法
        List<StudentDTO> studentList = mapper.getStudent1();
        for(StudentDTO student:studentList){
            System.out.println(student);
        }
        sqlSession.close();
    }

源码分析

    // inputStream是输入的配置文件,构建出来的
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());// 开始解析
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  // Configuration 设置所有的配置,并返回SqlSessionFactory对象 
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
  
  // 执行sqlSessionFactory.openSession() 获取session对象
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

目录