引言
在 Java 开发中,Spring Boot 与 MyBatis 的结合是实现高效、灵活数据库操作的常用方案。本文将详细介绍如何在 Spring Boot 项目中整合 MyBatis,并涵盖 CRUD 操作、注解使用、分页插件集成、结果映射和自动映射等内容。
一、环境准备
开发工具:推荐使用 IntelliJ IDEA 或 Eclipse。
数据库:以 MySQL 为例,确保本地已安装 MySQL 数据库并创建好测试数据库。
项目结构:典型的 Spring Boot 项目结构包括 Controller、Service、Mapper 和 Entity 等层。
二、创建 Spring Boot 项目
-
使用 Spring Initializr:访问 Spring Initializr,选择以下依赖:
-
Spring Web
-
MyBatis Framework
-
MySQL Driver
-
Lombok(可选,用于简化实体类开发)
-
-
生成项目:点击“Generate”按钮下载项目压缩包,解压后导入到开发工具中。
三、添加 Maven 依赖
在 pom.xml 文件中添加以下依赖:
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- MyBatis Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
四、配置数据库连接
在 src/main/resources/application.yml 文件中配置数据库连接信息和 MyBatis 相关配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml # 映射文件路径
type-aliases-package: com.example.demo.model # 实体类包路径
-
url:数据库连接地址。 -
username和password:数据库的用户名和密码。 -
mapper-locations:指定 MyBatis 的 XML 映射文件路径。 -
type-aliases-package:指定实体类所在的包。
五、创建项目结构
1. 实体类(Entity)
创建 User 实体类,用于映射数据库表 user。
package com.example.demo.entity;
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String password;
private String email;
}
2. Mapper 接口
创建 UserMapper 接口,定义数据库操作方法。
package com.example.demo.mapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User getUserById(Long id);
void insertUser(User user);
void updateUser(User user);
void deleteUser(Long id);
}
3. Mapper XML 文件
在 src/main/resources/mapper 目录下创建 UserMapper.xml 文件,编写 SQL 映射。
<?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.demo.mapper.UserMapper">
<select id="getUserById" parameterType="long" resultType="com.example.demo.entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="com.example.demo.entity.User">
INSERT INTO user (username, password, email) VALUES (#{username}, #{password}, #{email})
</insert>
<update id="updateUser" parameterType="com.example.demo.entity.User">
UPDATE user SET username = #{username}, password = #{password}, email = #{email} WHERE id = #{id}
</update>
<delete id="deleteUser" parameterType="long">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
4. Service 层
创建 UserService 接口及其实现类 UserServiceImpl,用于业务逻辑处理。
package com.example.demo.service;
import com.example.demo.entity.User;
public interface UserService {
User getUserById(Long id);
void createUser(User user);
void updateUser(User user);
void deleteUser(Long id);
}
package com.example.demo.service.impl;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(Long id) {
return userMapper.getUserById(id);
}
@Override
public void createUser(User user) {
userMapper.insertUser(user);
}
@Override
public void updateUser(User user) {
userMapper.updateUser(user);
}
@Override
public void deleteUser(Long id) {
userMapper.deleteUser(id);
}
}
5. Controller 层
创建 UserController,提供 REST API 接口。
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public String createUser(@RequestBody User user) {
userService.createUser(user);
return "User created successfully!";
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PutMapping
public String updateUser(@RequestBody User user) {
userService.updateUser(user);
return "User updated successfully!";
}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return "User deleted successfully!";
}
}
六、MyBatis 的高级用法
1. 结果映射与自动映射
MyBatis 提供了强大的结果映射功能,用于将查询结果映射到 Java 对象。默认情况下,MyBatis 会自动映射字段名与实体类字段名一致的列。
例如,假设数据库表 user 的字段为 id、username 和 email,实体类字段也为 id、username 和 email,则 MyBatis 会自动完成映射。
如果字段名不一致,可以通过 resultMap 手动映射:
<resultMap id="UserResultMap" type="com.example.demo.entity.User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="email" column="user_email"/>
</resultMap>
<select id="getUserById" parameterType="long" resultMap="UserResultMap">
SELECT user_id, user_name, user_email FROM user WHERE user_id = #{id}
</select>
2.使用注解实现 MyBatis 的 resultMap
在 MyBatis 中,resultMap 是用于定义数据库字段与 Java 对象属性之间映射关系的工具。当数据库字段名与实体类属性名不一致时,可以通过注解来完成映射,而无需依赖 XML 文件。
2.1. 使用 @Results 和 @Result 注解
@Results 注解用于定义一个 resultMap,而 @Result 注解用于定义具体的字段映射关系。以下是示例:
示例 1:基本字段映射
假设数据库表 users 的字段为 user_id 和 user_name,而实体类 User 的属性为 id 和 name,可以通过以下方式定义映射关系:
@Select("SELECT user_id, user_name FROM users WHERE user_id = #{id}")
@Results(id = "UserResultMap", value = {
@Result(property = "id", column = "user_id", id = true),
@Result(property = "name", column = "user_name"),
@Result(property = "email", column = "user_email")
})
User getUserById(@Param("id") Integer id);
3. 多次引用 @Results 定义的 resultMap
在其他方法中,可以通过 @ResultMap 注解引用之前定义的 resultMap。例如:
在这个例子中:
-
@Select注解定义了查询语句。 -
@Results注解定义了resultMap。 -
@Result注解定义了字段映射关系,property表示实体类属性,column表示数据库字段。
@Select("SELECT * FROM users WHERE id = #{id}")
@ResultMap("UserResultMap")
User getUserById(@Param("id") Integer id);
@Select("SELECT * FROM users WHERE name = #{name}")
@ResultMap("UserResultMap")
User getUserByName(@Param("name") String name);
示例 2:一对一关联
如果需要处理一对一关联关系,例如用户和其配置信息(Profile),可以通过 @One 注解完成:
假设数据库中有两个表:
users 表:存储用户信息。
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
profile_id INT
);
profiles 表:存储用户配置信息。
CREATE TABLE profiles (
id INT PRIMARY KEY,
bio TEXT
);
实体类:(这里省略了,service,serviceImpl,mapper的内容,自己补上即可)
public class User {
private Integer id;
private String name;
private Profile profile; // 一对一关联
}
public class Profile {
private Integer id;
private String bio;
}
Mapper 接口:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "profile", column = "profile_id", one = @One(select = "com.example.mapper.ProfileMapper.getProfileById"))
})
User getUserWithProfile(@Param("id") Integer id);
}
@Mapper
public interface ProfileMapper {
@Select("SELECT * FROM profiles WHERE id = #{id}")
Profile getProfileById(@Param("id") Integer id);
}
在这个例子中:
执行流程
-
查询
users表,获取用户的基本信息。 -
使用
profile_id调用ProfileMapper.getProfileById方法,加载关联的Profile对象。 -
将
Profile对象设置到User对象的profile属性中。
-
解释
-
在
UserMapper中,@Results定义了用户信息的字段映射。 -
@One注解用于定义User和Profile的一对一关系:property = "profile":指定关联对象的属性名。column = "profile_id":指定用于关联的字段(profile_id是外键)。select = "com.example.mapper.ProfileMapper.getProfileById":指定关联对象的查询方法。 -
@One注解用于定义一对一关联,select属性指定了关联对象的查询方法,例如一个用户(User)对应一个配置(Profile)。 -
作用:
-
@One注解的作用是定义如何通过关联查询加载一对一关系中的另一个对象。它通常与@Results和@Result注解一起使用,指定关联对象的查询方法。 -
属性:
-
select:指定关联对象的查询方法,通常是另一个Mapper接口中的方法。 -
fetchType:(可选)指定是否立即加载关联对象。默认值为FetchType.DEFAULT,表示由 MyBatis 配置决定。可以设置为FetchType.EAGER(立即加载)或FetchType.LAZY(延迟加载)。 -
简单说就是先调用 在涉及到“Profile” 的值的时候,会去调用“getProfileById”这个方法,把查询结果的返回值给到“Profile”中,“getProfileById(@Param("id") Integer id)” 这个方法有一个“id”的参数,这个“id”的值来源于“getUserWithProfile”方法中的“@Select("SELECT * FROM users WHERE id = #{id}")” 这段SQL语句中的返回值。
-

-

-

示例 3:一对多关联
对于一对多关系,例如用户和其订单列表,可以通过 @Many 注解完成:
假设数据库中有两个表:
users 表:存储用户信息。
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
orders 表:存储订单信息。
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
amount DECIMAL(10, 2)
);
实体类:(这里省略了,service,serviceImpl,mapper的内容,自己补上即可)
public class User {
private Integer id;
private String name;
private List<Order> orders; // 一对多关联
}
public class Order {
private Integer id;
private Integer userId;
private BigDecimal amount;
}
Mapper 接口
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "orders", column = "id", many = @Many(select = "com.example.mapper.OrderMapper.getOrdersByUserId"))
})
User getUserWithOrders(@Param("id") Integer id);
}
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE user_id = #{userId}")
List<Order> getOrdersByUserId(@Param("userId") Integer userId);
}
在这个例子中:
执行流程
-
查询
users表,获取用户的基本信息。 -
使用
id调用OrderMapper.getOrdersByUserId方法,加载与该用户关联的所有订单。 -
将订单列表设置到
User对象的orders属性中。
-
解释:
-
在
UserMapper中,@Results定义了用户信息的字段映射。 -
@Many注解用于定义User和Order的一对多关系:property = "orders":指定关联对象的属性名。column = "id":指定用于关联的字段(id是用户的主键)。select = "com.example.mapper.OrderMapper.getOrdersByUserId":指定关联对象的查询方法。 -
@Many注解用于定义一对多关联,select属性指定了关联对象的查询方法,例如一个用户(User)对应多个订单(Order) -
@Many注解用于处理一对多关系,例如一个用户(User)对应多个订单(Order)。 -
作用
-
@Many注解的作用是定义如何通过关联查询加载一对多关系中的多个对象。它通常与@Results和@Result注解一起使用,指定关联对象的查询方法。 -
属性
-
select:指定关联对象的查询方法,通常是另一个Mapper接口中的方法。 -
fetchType:(可选)指定是否立即加载关联对象。默认值为FetchType.DEFAULT,表示由 MyBatis 配置决定。可以设置为FetchType.EAGER(立即加载)或FetchType.LAZY(延迟加载)。
4. 动态 SQL 和注解
MyBatis 注解还支持动态 SQL 的编写,例如 @InsertProvider、@SelectProvider 等注解,可以动态生成 SQL 语句。以下是一个动态插入的示例:
@InsertProvider(type = UserSqlProvider.class, method = "insertUserSql")
void insertUser(User user);
其中,UserSqlProvider 是一个工具类,用于动态生成 SQL:
public class UserSqlProvider {
public String insertUserSql(User user) {
return "INSERT INTO users (name, email) VALUES ('" + user.getName() + "', '" + user.getEmail() + "')";
}
}
MyBatis 的注解方式提供了强大的灵活性,可以替代 XML 文件完成复杂的映射关系定义。通过 @Results 和 @Result 注解,可以实现字段映射、一对一和一对多关联关系的定义。此外,动态 SQL 注解(如 @InsertProvider)进一步增强了 MyBatis 的功能
5. 分页插件集成
MyBatis 提供了分页插件 PageHelper,可以方便地实现分页功能。
-
添加依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.5</version>
</dependency>
2.配置分页插件:
在 application.yml 中添加配置:
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
6.使用分页功能:
在 Service 层中使用 PageHelper:
import com.github.pagehelper.PageHelper;
public List<User> getUsersWithPagination(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
return userMapper.getAllUsers();
}
7. 注解使用方法
除了 XML 映射文件,MyBatis 还支持注解方式。
例如,定义一个简单的 Mapper 接口:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(@Param("id") Long id);
@Insert("INSERT INTO user (username, password, email) VALUES (#{username}, #{password}, #{email})")
void insertUser(User user);
@Update("UPDATE user SET username = #{username}, password = #{password}, email = #{email} WHERE id = #{id}")
void updateUser(User user);
@Delete("DELETE FROM user WHERE id = #{id}")
void deleteUser(@Param("id") Long id);
}
七、连接池的设置
在生产环境中,为了提高数据库访问效率,通常会配置连接池。Spring Boot 默认使用 HikariCP 作为连接池实现,我们可以通过修改 application.yml 来调整连接池参数。
1. 配置 HikariCP
在 application.yml 中添加以下内容:
spring:
datasource:
hikari:
connection-timeout: 30000 # 连接超时时间(毫秒)
maximum-pool-size: 10 # 最大连接数
minimum-idle: 5 # 最小空闲连接数
idle-timeout: 600000 # 空闲连接超时时间(毫秒)
max-lifetime: 1800000 # 连接的最大生命周期(毫秒)
pool-name: MyHikariCP # 连接池名称
2. 使用其他连接池(可选)
如果想使用其他连接池(如 Druid),可以在 pom.xml 中引入相关依赖,并替换默认配置。
引入 Druid 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
配置 Druid:
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动类的全限定名。
initial-size: 5 #连接池初始化时创建的连接数
max-active: 20 #连接池中允许的最大活动连接数。
min-idle: 5 #连接池中保持的最小空闲连接数。
max-wait: 60000 #获取连接的最大等待时间(毫秒)。
八、MyBatis 缓存配置
1、一级缓存介绍
一级缓存,也称为会话缓存(Session Cache),是 MyBatis 默认提供的缓存机制。它在 SqlSession 的生命周期内有效,用于存储会话期间的查询结果。当 SqlSession 关闭时,一级缓存中的数据会被清除。
2、一级缓存的特点
-
默认开启:MyBatis 会自动使用一级缓存,无需额外配置。
-
会话级:一级缓存仅在当前 SqlSession 内有效,不同 SqlSession 之间不共享。
-
自动管理:MyBatis 会根据需要自动从一级缓存中读取或写入数据。
什么是同一个SqlSession?
如果你是直接定义了一个 UserMapper,那么是否是同一个 SqlSession 取决于 UserMapper 是如何注入的。通常情况下,Spring Boot 会自动管理 SqlSession,并且在同一个请求或事务中复用同一个 SqlSession。
你的代码可能长这样:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void someMethod() {
User user1 = userMapper.findById(1L); // 第一次查询
User user2 = userMapper.findById(1L); // 第二次查询
}
}
同一个SqlSession关键点:
-
同一个
UserMapper的调用:-
在同一个方法(如
someMethod)中,userMapper的两次调用userMapper中的方法那么属于同一个SqlSession。 -
Spring 会自动复用同一个
SqlSession,直到方法结束或事务提交。 -
如果
UserMapper和xxxMapper的调用发生在同一个事务内(例如,被同一个@Transactional注解的方法包裹),那么它们会共享同一个SqlSession。------------在同一个事务内,Spring 会确保所有数据库操作都使用同一个SqlSession,并且共享一级缓存。
-
-
不同的方法调用:
-
如果你在 不同的方法 中调用
userMapper,Spring 会根据事务管理器决定是否复用同一个SqlSession。 -
如果没有显式事务管理,每次方法调用可能使用不同的
SqlSession。 -
如果
UserMapper和xxxMapper的调用不在同一个事务内(例如,分别在不同的方法中调用,且这些方法没有被@Transactional注解包裹),那么它们会使用不同的SqlSession。----------------------------每次调用没有事务管理的方法时,Spring 会创建一个新的SqlSession,并且不会共享一级缓存。
-
3、二级缓存
在 application.yml 文件中配置数据源和 MyBatis 的基础配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database_name?useSSL=false&serverTimezone=UTC
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
cache-enabled: true # 全局开启缓存
default-executor-type: REUSE # 使用复用的执行器
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.example.demo.entity
4.二级缓存的默认行为
-
默认情况下,二级缓存会使用 LRU(最近最少使用) 策略回收数据。
-
缓存会存储 1024 个对象引用。
-
缓存不会定时刷新(即没有
flushInterval)。 -
缓存被视为 读/写缓存,这意味着缓存中的对象可以被修改。
5、Mapper 文件中的二级缓存配置
在具体的 Mapper XML 文件中,可以通过 <cache> 标签对二级缓存进行细粒度配置。以下是一个完整的示例:
<?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.demo.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache
eviction="FIFO" # 缓存回收策略:先进先出
flushInterval="60000" # 缓存刷新间隔(毫秒)
size="512" # 缓存的最大对象数
readOnly="true" # 是否只读
/>
<!-- 查询语句 -->
<select id="findById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
配置说明
-
eviction:缓存回收策略,可选值包括:-
LRU(最近最少使用,默认值)。 -
FIFO(先进先出)。 -
SOFT(软引用)。 -
WEAK(弱引用)。
-
-
flushInterval:缓存刷新间隔(单位:毫秒)。例如,60000表示每隔 60 秒刷新一次。 -
size:缓存的最大对象数。例如,512表示最多存储 512 个对象。 -
readOnly:-
true:缓存中的对象被视为只读,返回相同实例,适合查询操作。 -
false:缓存中的对象被视为可写,返回对象的拷贝。
-
-
useCache:在<select>标签中,可以通过useCache属性控制是否对某个查询启用缓存。
3、实体类要求
为了将缓存数据存储到二级缓存中,实体类需要实现 Serializable 接口。例如:
import java.io.Serializable;
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
// Getters and Setters
}
4、注意事项
-
二级缓存的作用范围:二级缓存是基于 Mapper 的,每个 Mapper 的二级缓存是独立的。
-
分布式环境下的缓存问题:在分布式系统中,MyBatis 的二级缓存可能会导致数据一致性问题。建议结合 Redis 等分布式缓存工具来解决。
-
禁用二级缓存:如果某些查询不适合缓存,可以在
<select>标签中设置useCache="false"。 -
刷新缓存:如果某些
insert、update或delete操作不需要刷新缓存,可以设置flushCache="false"。
通过 YAML 文件和 Mapper XML 文件的结合,可以灵活地配置 MyBatis 的二级缓存。全局配置提供了默认行为,而 Mapper 文件中的 <cache> 标签则允许对每个 Mapper 进行细粒度的控制。这种配置方式不仅清晰,还能满足复杂的业务需求。
总结
MyBatis 的注解方式提供了强大的灵活性,可以替代 XML 文件完成复杂的映射关系定义。通过 @Results 和 @Result 注解,可以实现字段映射、一对一和一对多关联关系的定义。此外,动态 SQL 注解(如 @InsertProvider)进一步增强了 MyBatis 的功能。
686

被折叠的 条评论
为什么被折叠?



