mybatis

298 阅读9分钟

参考:

blog.csdn.net/u014745069/…

www.jianshu.com/p/b7c591494…

https://www.jianshu.com/p/06b73e8d9f56 

https://juejin.cn/post/6844903585298251789

https://juejin.cn/post/6844903582639063054


一、JDBC的执行过程

  (1).注册驱动 

 Class.forName("oracle.jdbc.OracleDriver");

 (2).建立数据库连接 

String url = "jdbc:oracle:thin:@localhost:1521:xe";
Connection conn = DriverManager.getConnection(url,name,password); 

 (3).创建操作数据库的对象Statement

statement Statement state = conn.createStatement(); 

 (4).执行SQL语句 

String sql = "select id,name from s_emp"; 
ResultSet rs = state.executeQuery(sql); 

 (5).处理结果集 

 while( rs.next() ){ 
      int id = rs.getInt("id"); 
      String name = rs.getString(2); 
      System.out.println(id+" "+name);
 } 

 (6).关闭连接 

 rs.close(); 
 state.close(); 
 conn.close(); 


二、mybatis

1、MyBatis 编程步骤

  1. 创建 SqlSessionFactory 对象。
  2. 通过 SqlSessionFactory 获取 SqlSession 对象。
  3. 通过 SqlSession 获得 Mapper 代理对象。
  4. 通过 Mapper 代理对象,执行数据库操作。
  5. 执行成功,则使用 SqlSession 提交事务。
  6. 执行失败,则使用 SqlSession 回滚事务。
  7. 最终,关闭会话。


private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;

private static void init() throws IOException {
    String resource = "mybatis-config.xml";
    Reader reader = Resources.getResourceAsReader(resource);
    sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}
SqlSession session= sqlSessionFactory.openSession();  
UserDao userDao = session.getMapper(UserDao.class);  
UserDto user = new UserDto();  
user.setUsername("iMybatis");  
List<UserDto> users = userDao.queryUsers(user);  

public interface UserDao {
    public List<UserDto> queryUsers(UserDto user) throws Exception;
}

<?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="org.iMybatis.abc.dao.UserDao">  
    <select id="queryUsers" parameterType="UserDto" resultType="UserDto"  
        useCache="false">  
        <![CDATA[ 
        select * from t_user t where t.username = #{username} 
        ]]>  
    </select>  
</mapper>

详情:https://www.jianshu.com/p/15781ec742f2


2、重要组件

核心组件主要包括以下几个:

  • SqlSessionFactoryBuilder:会根据配置信息或代码来生成SqlSessionFactory;
  • SqlSessionFactory:依靠工厂来生成SqlSession;
  • SqlSession:是一个既可以发送SQL去执行并返回结果,也可以获取Mapper的接口;
  • SQL Mapper:是MyBatis新设计的组件,由一个Java接口和XML文件构成,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果。

映射器是由Java接口和XML文件(或注解)共同组成的,作用如下:

  • 定义参数类型
  • 描述缓存
  • 描述SQL语句
  • 定义查询结果和POJO的映射关系


3、映射器

       映射器是MyBatis最复杂、最核心的配置,包括参数类型、动态SQL、定义SQL、延迟加载、缓存信息等功能。通过映射器,可以很容易的进行数据的增删改查操作。

3.1 延迟加载

级联的优势是能够方便地获取数据,但有时不需要获取所有数据,这样会多执行几条SQL,性能下降,为了解决这个问题,需要使用延迟加载,只要使用相关级联数据时,才会发送SQL去取回数据。

在MyBatis的配置中有2个全局的参数 lazyLoadingEnabled 和 aggressiveLazyLoading ,

第一个的含义是是否开启延迟加载功能,
第二个的含义是对任意延迟加载属性的调用,会使延迟加载的对象完整加载,否则只会按需加载。

3.2 Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载。其中,association 指的就是一对一,collection 指的就是一对多查询

在 Mybatis 配置文件中,可以配置 <setting name="lazyLoadingEnabled" value="true" /> 来启用延迟加载的功能。默认情况下,延迟加载的功能是关闭的。

它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的 invoke(...) 方法,发现a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调用a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() 方法的调用。这就是延迟加载的基本原理。

参考:http://svip.iocoder.cn/MyBatis/Interview/

3.3 缓存

1、一级缓存

在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。


每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。


2、二级缓存

在一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。


二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

总结:

一级缓存作用域:sqlsession 范围的

二级缓存作用域:是多个sqlsesson共享,其作用域是同一个namespace,so是跨sqlsession的

参考:https://tech.meituan.com/2018/01/19/mybatis-cache.html


4、其他  

下面的博客总结的很详细,推荐: http://svip.iocoder.cn/MyBatis/Interview/

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

  •  ${} 实际场景下,不推荐用。因为,可能有 SQL 注入的风险。
${} 是 Properties 文件中的变量占位符,它可以用于 XML 标签属性值和 SQL 内部,属于字符串替换。例如将${driver} 会被静态替换为 com.mysql.jdbc.Driver
${} 也可以对传递进来的参数原样拼接在 SQL 中

  • #{} 是预编译处理,可以有效防止 SQL 注入,提高系统安全性。

#{} 是 SQL 的参数占位符,Mybatis 会将 SQL 中的 #{} 替换为 ? 号,在 SQL 执行前会使用 PreparedStatement 的参数设置方法,按序给 SQL 的 ? 号占位符设置参数值 。 


4.2  最佳实践中,通常一个 XML 映射文件,都会写一个 Mapper 接口与之对应。请问,这个 Mapper 接口的工作原理是什么?Mapper 接口里的方法,参数不同时,方法能重载吗?

Mapper 接口,对应的关系如下:

  • 接口的全限名,就是映射文件中的 "namespace" 的值。
  • 接口的方法名,就是映射文件中 MappedStatement 的 "id" 值。
  • 接口方法内的参数,就是传递给 SQL 的参数。

Mapper 接口是没有实现类的,当调用接口方法时,接口全限名 + 方法名拼接字符串作为 key 值,可唯一定位一个对应的 MappedStatement 。举例:com.mybatis3.mappers.StudentDao.findStudentById ,可以唯一找到"namespace"com.mybatis3.mappers.StudentDao 下面 "id"findStudentById 的 MappedStatement 。

总结来说,在 Mybatis 中,每一个 <select /><insert /><update /><delete /> 标签,都会被解析为一个 MappedStatement 对象。

另外,Mapper 接口的实现类,通过 MyBatis 使用 JDK Proxy 自动生成其代理对象 Proxy ,而代理对象 Proxy 会拦截接口方法,从而“调用”对应的 MappedStatement 方法,最终执行 SQL ,返回执行结果。整体流程如下图:


  • 其中,SqlSession 在调用 Executor 之前,会获得对应的 MappedStatement 方法。


4.3  Mapper 接口绑定有几种实现方式

  1. 第一种,通过 XML Mapper 里面写 SQL 来绑定。在这种情况下,要指定 XML 映射文件里面的 "namespace" 必须为接口的全路径名。
  2. 第二种,通过注解绑定,就是在接口的方法上面加上 @Select@Update@Insert@Delete 注解,里面包含 SQL 语句来绑定。


4.4 在 Mapper 中如何传递多个参数

第一种,使用 Map 集合,装载多个参数进行传递

// Mapper 接口
List<Student> selectStudents(Map<String, Object> map);

// Mapper XML 代码
<select id="selectStudents" parameterType="Map" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{start}, #{end}
</select>

第二种,保持传递多个参数,使用 @Param 注解

// 调用方法
return studentMapper.selectStudents(0, 10);

// Mapper 接口
List<Student> selectStudents(@Param("start") Integer start, @Param("end") Integer end);

// Mapper XML 代码
<select id="selectStudents" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{start}, #{end}
</select>

第三种,保持传递多个参数,不使用 @Param 注解

按照参数在方法中的位置,从 1 开始,逐个为 #{param1}#{param2}#{param3} 不断向下。

// 调用方法
return studentMapper.selectStudents(0, 10);

// Mapper 接口
List<Student> selectStudents(Integer start, Integer end);

// Mapper XML 代码
<select id="selectStudents" resultType="Student">
    SELECT * 
    FROM students 
    LIMIT #{param1}, #{param2}
</select>

 

4.5 MyBatis 如何执行批量插入? 在面试中被问到过

方法一:单条sql插入,然后循环  ;我只回答了这个方法

@Test
public void testBatch() {
    // 创建要插入的用户的名字的数组
    List<String> names = new ArrayList<>();
    names.add("小红");
    names.add("小兰");

    // 获得执行器类型为 Batch 的 SqlSession 对象,并且 autoCommit = false ,禁止事务自动提交
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
        // 获得 Mapper 对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 循环插入
        for (String name : names) {
            mapper.insertUser(name);
        }
        // 提交批量操作
        session.commit();
    }
}

方法二:对于这种方式,需要保证单条 SQL 不超过语句的最大限制 max_allowed_packet 大小,默认为 1 M 。

INSERT INTO [表名]([列名],[列名]) 
VALUES
([列值],[列值])),
([列值],[列值])),
([列值],[列值]));


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

  1. Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非数据库分页。                                                                    === 不推荐使用
  2. 在 SQL 内直接书写带有数据库分页的参数来完成数据库分页功能   === 推荐
  3. 也可以使用分页插件来完成数据库分页。                                     === 推荐

后两个2、3都是基于数据库分页,差别在于前者是工程师手动编写分页条件,后者是插件自动添加分页条件。

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

举例:SELECT * FROM student ,拦截 SQL 后重写为:select * FROM student LIMI 0,10

分页插件有:  MyBatis-Plus    Mybatis-PageHelper


4.7  mybatis中<![CDATA[]]>的作用   面试被问到

mybatis 中 SQL 写在mapper.xml文件中,而xml解析 < 、>、<=、>= 时会出错,这时应该使用转义写法。

第一种写法(1):
原符号    <    <=    >     >=    &     '     " 
替换符号 &lt; &lt;= &gt; &gt;= &amp; &apos; &quot; 
例如:sql如下: create_time &gt;= #{startTime} and create_time &lt;= #{endTime} 

第二种写法(2): 
大于等于 <![CDATA[ >= ]]> 
小于等于 <![CDATA[ <= ]]> 

例如:sql如下:create_time <![CDATA[ >= ]]> #{startTime} and create_time <![CDATA[ <= ]]> #{endTime} 

在使用mybatis 时,sql是写在xml 映射文件中,如果写的sql中有一些特殊字符(如 < > & 等),在解析xml文件的时候会被转义,但我们不希望他被转义,所以要使用<![CDATA[ ]]>来解决。

<![CDATA[ ]]> 是什么,这是XML语法。在CDATA内部的所有内容都会被解析器忽略。