MyBatis

1. 概述

1.1 MyBatis的重要性

MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。

本文内容

  • MyBatis基础:核心概念和配置
  • SQL映射:Mapper接口和XML映射
  • 动态SQL:动态SQL语句构建
  • 结果映射:结果集映射配置
  • 缓存机制:一级缓存和二级缓存
  • 插件开发:MyBatis插件机制
  • MyBatis Plus:增强工具包

1.2 本文内容结构

本文将从以下几个方面深入探讨MyBatis:

  1. MyBatis基础:核心概念和快速开始
  2. SQL映射:Mapper接口和XML映射
  3. 动态SQL:动态SQL语句
  4. 结果映射:结果集映射
  5. 缓存机制:缓存配置和使用
  6. 插件开发:自定义插件
  7. MyBatis Plus:增强功能

2. MyBatis基础

2.1 MyBatis特性

2.1.1 核心特性

MyBatis核心特性

  1. SQL映射:将SQL语句映射到Java方法
  2. 参数映射:自动将Java对象映射到SQL参数
  3. 结果映射:自动将SQL结果映射到Java对象
  4. 动态SQL:根据条件动态构建SQL
  5. 缓存机制:一级缓存和二级缓存

MyBatis优势

  • 灵活性强:可以编写原生SQL
  • 性能优异:SQL可控,性能优化方便
  • 易于学习:学习曲线平缓
  • 功能丰富:支持复杂查询和映射

2.2 快速开始

2.2.1 项目配置

Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>

<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

MyBatis配置文件(mybatis-config.xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>

<!-- Mapper映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>

Java代码使用

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
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisExample {

public static void main(String[] args) throws Exception {
// 1. 读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

// 2. 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);

// 3. 获取SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 4. 获取Mapper接口
UserMapper mapper = session.getMapper(UserMapper.class);

// 5. 执行查询
User user = mapper.findById(1L);
System.out.println(user);
}
}
}

2.3 Spring集成

2.3.1 Spring Boot集成

Spring Boot配置

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

application.yml配置

1
2
3
4
5
6
7
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazy-loading-enabled: false

主应用类

1
2
3
4
5
6
7
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

3. SQL映射

3.1 Mapper接口

3.1.1 接口定义

Mapper接口示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Mapper
public interface UserMapper {

// 根据ID查询
User findById(Long id);

// 查询所有
List<User> findAll();

// 条件查询
List<User> findByName(String name);

// 插入
int insert(User user);

// 更新
int update(User user);

// 删除
int deleteById(Long id);
}

3.2 XML映射

3.2.1 XML映射文件

UserMapper.xml

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
<?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="com.example.mapper.UserMapper">

<!-- 结果映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<result property="createTime" column="create_time"/>
</resultMap>

<!-- 根据ID查询 -->
<select id="findById" parameterType="Long" resultMap="userResultMap">
SELECT * FROM users WHERE id = #{id}
</select>

<!-- 查询所有 -->
<select id="findAll" resultMap="userResultMap">
SELECT * FROM users
</select>

<!-- 条件查询 -->
<select id="findByName" parameterType="String" resultMap="userResultMap">
SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')
</select>

<!-- 插入 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(name, email, create_time)
VALUES(#{name}, #{email}, #{createTime})
</insert>

<!-- 更新 -->
<update id="update" parameterType="User">
UPDATE users
SET name = #{name}, email = #{email}
WHERE id = #{id}
</update>

<!-- 删除 -->
<delete id="deleteById" parameterType="Long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>

3.3 注解方式

3.3.1 注解映射

注解方式示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Mapper
public interface UserMapper {

@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);

@Select("SELECT * FROM users")
List<User> findAll();

@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

@Update("UPDATE users SET name = #{name} WHERE id = #{id}")
int update(User user);

@Delete("DELETE FROM users WHERE id = #{id}")
int deleteById(Long id);

// 复杂查询使用@SelectProvider
@SelectProvider(type = UserSqlProvider.class, method = "findByCondition")
List<User> findByCondition(@Param("name") String name, @Param("email") String email);
}

SQL提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserSqlProvider {

public String findByCondition(@Param("name") String name, @Param("email") String email) {
return new SQL() {{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name LIKE CONCAT('%', #{name}, '%')");
}
if (email != null) {
WHERE("email = #{email}");
}
}}.toString();
}
}

4. 动态SQL

4.1 if标签

4.1.1 条件判断

if标签示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findByCondition" resultMap="userResultMap">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>

4.2 choose标签

4.2.1 多条件选择

choose标签示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findByCondition" resultMap="userResultMap">
SELECT * FROM users
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="email != null">
AND email = #{email}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
</select>

4.3 foreach标签

4.3.1 循环处理

foreach标签示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- IN查询 -->
<select id="findByIds" resultMap="userResultMap">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users(name, email) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>

4.4 set标签

4.4.1 动态更新

set标签示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<update id="updateSelective" parameterType="User">
UPDATE users
<set>
<if test="name != null">
name = #{name},
</if>
<if test="email != null">
email = #{email},
</if>
<if test="status != null">
status = #{status},
</if>
</set>
WHERE id = #{id}
</update>

4.5 trim标签

4.5.1 字符串处理

trim标签示例

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateSelective" parameterType="User">
UPDATE users
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">
name = #{name},
</if>
<if test="email != null">
email = #{email},
</if>
</trim>
WHERE id = #{id}
</update>

5. 结果映射

5.1 简单映射

5.1.1 自动映射

自动映射

1
2
3
4
<!-- 字段名和属性名一致时,自动映射 -->
<select id="findById" resultType="User">
SELECT id, name, email FROM users WHERE id = #{id}
</select>

5.2 复杂映射

5.2.1 resultMap

resultMap示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 一对一映射 -->
<resultMap id="orderResultMap" type="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="createTime" column="create_time"/>
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
</association>
</resultMap>

<select id="findOrderWithUser" resultMap="orderResultMap">
SELECT
o.id as order_id,
o.order_no,
o.create_time,
u.id as user_id,
u.name as user_name,
u.email as user_email
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.id = #{id}
</select>

5.3 一对多映射

5.3.1 collection标签

一对多映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<resultMap id="userWithOrdersResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="createTime" column="order_create_time"/>
</collection>
</resultMap>

<select id="findUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT
u.id as user_id,
u.name as user_name,
o.id as order_id,
o.order_no,
o.create_time as order_create_time
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>

5.4 多对多映射

5.4.1 多对多关系

多对多映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="userWithRolesResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="roles" ofType="Role">
<id property="id" column="role_id"/>
<result property="name" column="role_name"/>
</collection>
</resultMap>

<select id="findUserWithRoles" resultMap="userWithRolesResultMap">
SELECT
u.id as user_id,
u.name as user_name,
r.id as role_id,
r.name as role_name
FROM users u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
WHERE u.id = #{id}
</select>

6. 缓存机制

6.1 一级缓存

6.1.1 SqlSession级别缓存

一级缓存

  • 默认开启
  • SqlSession级别
  • 同一个SqlSession中,相同查询只执行一次

一级缓存示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查询,执行SQL
User user1 = mapper.findById(1L);

// 第二次查询,从缓存获取,不执行SQL
User user2 = mapper.findById(1L);

// 清空缓存
session.clearCache();

// 第三次查询,执行SQL
User user3 = mapper.findById(1L);

session.close();

一级缓存失效情况

  1. 执行了增删改操作
  2. 调用了clearCache()方法
  3. 提交或回滚事务
  4. 关闭SqlSession

6.2 二级缓存

6.2.1 Mapper级别缓存

二级缓存配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- mybatis-config.xml -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

<select id="findById" resultType="User" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>

二级缓存特点

  • Mapper级别缓存
  • 跨SqlSession共享
  • 需要手动开启

缓存策略

  • LRU:最近最少使用
  • FIFO:先进先出
  • SOFT:软引用
  • WEAK:弱引用

6.3 自定义缓存

6.3.1 实现Cache接口

自定义缓存

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
public class RedisCache implements Cache {

private String id;
private RedisTemplate<String, Object> redisTemplate;

public RedisCache(String id) {
this.id = id;
// 初始化RedisTemplate
}

@Override
public String getId() {
return id;
}

@Override
public void putObject(Object key, Object value) {
redisTemplate.opsForValue().set(key.toString(), value);
}

@Override
public Object getObject(Object key) {
return redisTemplate.opsForValue().get(key.toString());
}

@Override
public Object removeObject(Object key) {
redisTemplate.delete(key.toString());
return null;
}

@Override
public void clear() {
// 清空缓存
}

@Override
public int getSize() {
return 0;
}
}

使用自定义缓存

1
<cache type="com.example.cache.RedisCache"/>

7. 插件开发

7.1 插件原理

7.1.1 拦截器机制

MyBatis插件:基于拦截器机制,可以拦截以下方法:

  • Executor:执行器
  • ParameterHandler:参数处理器
  • ResultSetHandler:结果集处理器
  • StatementHandler:SQL语句处理器

7.2 自定义插件

7.2.1 实现Interceptor接口

自定义插件示例

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
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyPlugin implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
System.out.println("Before execution");

// 执行原方法
Object result = invocation.proceed();

// 后置处理
System.out.println("After execution");

return result;
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
// 设置属性
}
}

注册插件

1
2
3
4
5
6
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.example.plugin.MyPlugin">
<property name="property1" value="value1"/>
</plugin>
</plugins>

7.3 分页插件

7.3.1 PageHelper

PageHelper使用

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

public PageInfo<User> findUsers(int pageNum, int pageSize) {
// 开启分页
PageHelper.startPage(pageNum, pageSize);

// 查询数据
List<User> users = userMapper.findAll();

// 封装分页信息
return new PageInfo<>(users);
}
}

8. MyBatis Plus

8.1 MyBatis Plus简介

8.1.1 核心特性

MyBatis Plus:MyBatis的增强工具包,简化CRUD操作。

核心特性

  • 无侵入:只做增强不做改变
  • 损耗小:启动即会自动注入基本CRUD
  • 强大的CRUD操作:内置通用Mapper
  • 支持Lambda形式调用
  • 支持主键自动生成

8.2 快速开始

8.2.1 配置和使用

Maven依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@TableName("users")
public class User {

@TableId(type = IdType.AUTO)
private Long id;

@TableField("name")
private String name;

@TableField("email")
private String email;

// getter和setter
}

Mapper接口

1
2
3
4
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper,自动拥有CRUD方法
}

使用示例

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
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

public void test() {
// 查询
User user = userMapper.selectById(1L);
List<User> users = userMapper.selectList(null);

// 插入
User newUser = new User();
newUser.setName("test");
userMapper.insert(newUser);

// 更新
user.setName("updated");
userMapper.updateById(user);

// 删除
userMapper.deleteById(1L);

// 条件查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "test");
List<User> result = userMapper.selectList(wrapper);
}
}

8.3 条件构造器

8.3.1 QueryWrapper

条件构造器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// QueryWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "test")
.like("email", "@")
.between("age", 18, 30)
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);

// LambdaQueryWrapper(类型安全)
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getName, "test")
.like(User::getEmail, "@");
List<User> users2 = userMapper.selectList(lambdaWrapper);

9. 实战案例

9.1 复杂查询

9.1.1 多表关联查询

多表关联查询

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
<resultMap id="orderDetailResultMap" type="OrderDetail">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
</association>
<collection property="items" ofType="OrderItem">
<id property="id" column="item_id"/>
<result property="productName" column="product_name"/>
<result property="quantity" column="quantity"/>
</collection>
</resultMap>

<select id="findOrderDetail" resultMap="orderDetailResultMap">
SELECT
o.id as order_id,
o.order_no,
u.id as user_id,
u.name as user_name,
oi.id as item_id,
oi.product_name,
oi.quantity
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE o.id = #{id}
</select>

9.2 批量操作

9.2.1 批量插入和更新

批量操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users(name, email) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>

<!-- 批量更新 -->
<update id="batchUpdate">
<foreach collection="users" item="user" separator=";">
UPDATE users
SET name = #{user.name}, email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>

10. 总结

10.1 核心要点

  1. SQL映射:灵活编写SQL,精确控制
  2. 动态SQL:根据条件动态构建SQL
  3. 结果映射:灵活映射查询结果
  4. 缓存机制:提高查询性能
  5. 插件机制:扩展MyBatis功能

10.2 关键理解

  1. 灵活性强:可以编写原生SQL
  2. 性能优异:SQL可控,易于优化
  3. 易于扩展:插件机制支持扩展

10.3 最佳实践

  1. 合理使用动态SQL:避免SQL注入
  2. 优化结果映射:减少N+1查询
  3. 合理使用缓存:提高查询性能
  4. 使用MyBatis Plus:简化CRUD操作

相关文章