uRick's PKM uRick's PKM
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
  • Mybatis基础实战
    • 1. 简单入门示例
    • 2. 创建SqlSession流程
    • 3. 配置文件解析
    • 4. 动态SQL
    • 5. 嵌套(关联)查询/N+1/延迟加载
    • 6. 主键生成策略
    • 7. 综述
  • Mybatis的运作机制探索
  • 深入探索XXL-Job
  • 深入Nacos
  • freemwork
uRick
2021-04-10
目录

Mybatis基础实战

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口、POJO对象为数据库中的记录。并支持XML 方式配置,以及硬编码方式配置;不过现在随着微服务的盛行,SpringBoot的流行,从XML向注解方式配置实现流行起来了。

它同其他类型ORM类似,目的都是将开发人员从编写复杂、重复的Java 连接数据库重复的工作中解放处理,促使我们提供效率关注业务接下来会逐步介绍相关特性以及功能点。

特性:

  • 使用连接池对连接进行管理;
  • SQL和代码分离,集中管理;
  • 自定义结果集映射
  • 参数映射和动态 SQL;
  • 重复 SQL 的提取;
  • 缓存管理;
  • 插件机制。

# 1. 简单入门示例

其实很多时候使用都是集成Spring框架来使用,那么它是怎么于Spring集成并能工作的,这些并不会很多人知晓。下面就是使用原生方式使用,需要加载配置文件mybatis-config.xml

@Test
public void TestExample() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 加载配置获取工厂
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 获取会话对象
    SqlSession session = sqlSessionFactory.openSession();
    try {
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        List<Blog> list = mapper.selectListById(1L);
        System.out.println(list);
    } finally {
        session.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2. 创建SqlSession流程

从上述的示例中,我们可以发现Mybatis有几个核心的对象:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper,这几个对象在Mybatis至关重要,在工作中集成Spring的时候,这些对象都被Spring管理了,一直用我们感觉不到它的存在,其实Mybatis能够运行工作都与4大对象密切相关。

SessionFlow

  1. SqlSessionFactoryBuilder 它是用来构建 SqlSessionFactory,而 SqlSessionFactory只需要一个,所以只要构建了这一个 SqlSessionFactory,它的使命就完成了,也就没有存在的意义了,所以它的生命周期只存在于方法的局部。

  2. SqlSessionFactory SqlSessionFactory 是用来创建 SqlSession 的,每次应用程序访问数据库,都需要创建一个会话。因为每次交互操作都需要会话,因此SqlSessionFactory 存在于应用的整个生命周期中。创建 SqlSession 只需要一个实例来做这件事就行了,否则会产生很多的混乱和浪费资源。所以要采用单例模式。

  3. SqlSession SqlSession 表示是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个 SqlSession 对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。

  4. Mapper Mapper(实际上是一个代理对象)是从 SqlSession 中获取的。它的作用是发送 SQL 来操作数据库的数据。它应该在一个SqlSession 事务方法之内。它是基于 MapperProxy 类实现代理策略的。

# 3. 配置文件解析

Mybatis启动时都需要加载所有的配置解析封装为 Configuration,后续的工作都需要它,下面简单解析主要的配置,详细可参考官方http://www.mybatis.org/mybatis-3/zh/configuration.html (opens new window)

<?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">
<configuration>
     <!-- 外部属性配置,在这里定义的数据源配置,当然可以直接定义属性,不用加载外部文件-->
     <!-- 可以用resource引用应用里面的相对路径,也可以用url指定本地服务器或者网络的绝对路径-->
    <properties resource="db.properties"></properties>
    <settings>
        <!-- 指定MyBatis所用日志的具体实现,未指定时将自动查找。 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 控制全局缓存(二级缓存)-->
        <setting name="cacheEnabled" value="true"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
        <!--<setting name="proxyFactory" value="CGLIB" />-->
        <!-- STATEMENT级别的缓存,只针对当前执行的这一statement有效,每次只需清理本地缓存 -->
        <!--<setting name="localCacheScope" value="STATEMENT"/>-->
        <!-- SESSION级别的缓存,默认值-->
        <setting name="localCacheScope" value="SESSION"/>
    </settings>

    <!-- 定义Mapper接口别名,这里定义好了,在具体的Mapper的XML文件中就不用使用完全限定名了,可以使用别名-->
    <typeAliases>
        <typeAlias alias="blog" type="com.bdr.Blog"/>
    </typeAliases>

    <!-- <typeHandlers>
            <typeHandler handler="com.bdr.type.JSONTypeHandler"></typeHandler>
        </typeHandlers>
     -->

    <!-- 对象工厂 -->
    <!-- <objectFactory type="com.bdr.objectfactory.GPObjectFactory">
            <property name="name" value="Rick"/>
        </objectFactory>
    -->
    <!-- 定义插件,可以通过插件来实现SQL拦截,可以拦截Executor、ParameterHandler、ResultSetHandler、StatementHandler 4大对象  -->
    <plugins>
        <plugin interceptor="com.bdr.interceptor.MyPageInterceptor">
        </plugin>
    </plugins>
    <!-- 环境配置,可以配置多个环境配置,default是指定使用哪个环境默认配置-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <!-- 数据源配置 -->
            <dataSource type="POOLED">
                <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>
    <!-- Mapper对应XML引入,它有多中使用方式,具体可以参考官方 -->
    <mappers>
        <mapper resource="BlogMapper.xml"/>
        <mapper resource="BlogMapperExt.xml"/>
    </mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 4. 动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用JDBC或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

可参考官方文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html (opens new window)

  1. 批量操作

在MyBatis里面是支持批量的操作的,包括批量的插入、更新、删除。我们可以直接传入一个 List、Set、Map 或者数组,配合动态 SQL 的标签,MyBatis 会自动帮我们生成语法正确的SQL语句。

注意

动态SQL批量操作数据量特别大的时候,拼接出来的 SQL 语句过大,MySQL 的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是4M,需要修改默认配置才可以解决这个问题;

Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (7188967 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.

  • 插入

在Mapper文件里面,使用foreach标签拼接values部分的语句;

!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true">
     <!-- 指定表ID字段,当插入后并将ID赋值给对象 -->
    <selectKey resultType="long" keyProperty="id" order="AFTER">
    SELECT LAST_INSERT_ID()
    </selectKey>
    insert into tbl_emp (emp_id, emp_name, gender,email, d_id)
    values
    <foreach collection="list" item="emps" index="index" separator=",">
    ( #{emps.empId},#{emps.empName},#{emps.gender},#{emps.email},#{emps.dId} )
    </foreach>
</insert>
1
2
3
4
5
6
7
8
9
10
11
12
  • 更新

批量更新可以使用 case when 实现。

update tbl_emp set
    emp_name =
        case emp_id
            when ? then ?
            when ? then ?
            when ? then ? end ,
    gender =
        case emp_id
            when ? then ?
            when ? then ?
            when ? then ? end ,
    email =
        case emp_id
            when ? then ?
            when ? then ?
            when ? then ? end
    where emp_id in ( ? , ? , ? )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<update id="updateBatch">
update tbl_emp set
    emp_name =
        <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end">
            when #{emps.empId} then #{emps.empName}
        </foreach>
    ,gender =
        <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end">
            when #{emps.empId} then #{emps.gender}
        </foreach>
    ,email =
        <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end">
            when #{emps.empId} then #{emps.email}
        </foreach>
    where emp_id in
        <foreach collection="list" item="emps" index="index" separator="," open="(" close=")">
            #{emps.empId}
        </foreach>
</update>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 删除
<delete id="deleteBatch">
delete from blog where bid in
    <foreach collection="list" item="item" index="index" open="(" separator="," close=")">
        #{item}
    </foreach>
</delete>
1
2
3
4
5
6

当然动态 SQL 也可通通过注解,硬编码的方式实现,无需 XML 定义;可查看注解API,都有对应替代实现。

@SelectProvider(type = SQLProvider.class, method = "buildEmployeeList")
List<Employee> getEmployeeList(Employee emp);

class SQLProvider {
    public String buildEmployeeList(Employee emp) {
            return new SQL() {{ 
                SELECT("emp_id, emp_name, gender,email, d_id");
                FROM("tbl_emp");
                WHERE("emp_id = #{empId}");
            }}.toString();
        }
}

@Select("SELECT emp_id, emp_name, gender,email, d_id FROM tbl_emp WHERE emp_id = #{empId}")
List<Employee> getEmployeeList(Employee emp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 5. 嵌套(关联)查询/N+1/延迟加载

在查询业务数据的时候经常会遇到跨表关联查询的情况,比如查询文章则关联文章评论(一对一),查询文章关联的评论用户(一对多)。

结果映射有两个标签, 分别是 resultType、resultMap。resultType是 select 标签的一个属性,适用于返回 JDK 类型(比如 Integer、String等等)和实体类,这种情况下结果集的列和实体类的属性可以直接映射。如果返回的字段无法直接映射,就要用 resultMap 来建立映射关系。

对于关联查询的这种情况,通常不能用 resultType 来映射,需要使用 resultMap 映射,要么就是修改 dto(Data Transfer Object),在里面增加字段,这个会导致增加很多无关的字段。要么就是引用关联的对象,比如Blog里面包含了一个 Author对象,这种情况下就要用到关联查询(association,或者嵌套查询),MyBatis 可以帮我们自动做结果的映射。

一对一的关联查询

  1. 嵌套结果
<!-- 根据文章查询作者,一对一查询的结果,嵌套查询 -->
<resultMap id="BlogWithAuthorResultMap" type="com.urick.mybatis.BlogAndAuthor">
    <id column="bid" property="bid" jdbcType="INTEGER"/>
    <result column="name" property="name" jdbcType="VARCHAR"/>
    <!-- 联合查询,将author的属性映射到ResultMap -->
    <association property="author" javaType="com.urick.mybatis.Author">
        <id column="author_id" property="authorId"/>
    <result column="author_name" property="authorName"/>
    </association>
</resultMap>
1
2
3
4
5
6
7
8
9
10
  1. 嵌套结果
<!-- 另一种联合查询 (一对一)的实现,但是这种方式有“N+1”的问题 -->
<resultMap id="BlogWithAuthorQueryMap" type="com.urick.mybatis.BlogAndAuthor">
    <id column="bid" property="bid" jdbcType="INTEGER"/>
    <result column="name" property="name" jdbcType="VARCHAR"/>
    <association property="author" javaType="com.urick.mybatis.Author" column="author_id" select="selectAuthor"/> 
    <!-- selectAuthor 定义在下面-->
</resultMap>
<!-- 嵌套查询 -->
<select id="selectAuthor" parameterType="int" resultType="com.urick.mybatis.Author">
    select author_id authorId, author_name authorName from author where author_id = #{authorId}
</select>
1
2
3
4
5
6
7
8
9
10
11

其中第2种方式:嵌套查询,由于是分两次查询,当我们查询了文章信息之后,会再发送一条SQL到数据库查询作者信息。我们只执行了一次查询文章信息的SQL(所谓的1),如果返回了N条记录,就会再发送N条到数据库查询作者信息(所谓的N),这个就是我们所说的N+1的问题。这样会白白地浪费我们的应用和数据库的性能。

如果我们用了嵌套查询的方式,怎么解决这个问题?能不能等到使用文章信息的时候再去查询?这个就是我们所说的延迟加载,或者叫懒加载。在MyBatis里面可以通过开启延迟加载的开关来解决这个问题。

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

# 6. 主键生成策略

public interface KeyGenerator {
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
1
2
3
4

KeyGenerator

基于自增主键返回:支持批量返回插入ID

<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="Student">
1

**SelectKeyGenerator **

<insert id="insertStudent" parameterType="Student" >
<!-- order="BEFORE"表示insert前执行,order="AFTER"表示insert之后执行 -->
<selectKey keyProperty="studId" resultType="int" order="BEFORE"> 
	SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL 
</selectKey>
INSERT INTO
STUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)
VALUES(#{studId}, #{name},
#{email}, #{dob}, #{phone})
</insert>
1
2
3
4
5
6
7
8
9
10

由于selectKey本身返回单个序列主键值,也就无法支持批量insert操作并返回主键id列表了。如果要执行批量insert,请选择使用for循环执行多次插入操作。

Mybatis批量插入,返回主键id列表为null

<insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="java.util.ArrayList">
		INSERT INTO
		STUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE)
		VALUES
	<foreach collection="list" item="item" index="index" separator=","> 
        	(#{item.studId},#{item.name},#{item.email},#{item.dob}, #{item.phone}) 
    	</foreach> 
</insert>
1
2
3
4
5
6
7
8

批量插入,主键返回为null问题解决 (opens new window)

# 7. 综述

由于Mybatis非常成熟,在市面上也能找到许多实战教程,也可以参考官方入门文档,资源非常丰富,本文不做过多阐述。

  1. Mybatis官方文档 (opens new window)
  2. Mybatis 高频面试题 (opens new window)
  3. 美团技术—聊聊MyBatis缓存机制 (opens new window)
#Mybatis
上次更新: 2024/03/02, 14:21:03
Mybatis的运作机制探索

Mybatis的运作机制探索→

最近更新
01
从0到1:开启商业与未来的秘密
11-26
02
如何阅读一本书: 读懂一本书,精于一件事
10-25
03
深入理解Lambda
06-27
更多文章>
Theme by Vdoing | Copyright © 2019-2024 uRick | CC BY 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式