SpringBoot3.x入门到精通系列:2.5 整合 MyBatis 详解
SpringBoot 3.x 整合 MyBatis 详解
🎯 MyBatis简介
MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
核心特性
- SQL映射: 支持XML和注解两种方式
- 动态SQL: 强大的动态SQL构建能力
- 结果映射: 灵活的结果集映射
- 缓存机制: 一级缓存和二级缓存
- 插件扩展: 支持插件机制扩展功能
🚀 快速开始
1. 添加依赖
<dependencies><!-- SpringBoot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis SpringBoot Starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- MySQL驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- 连接池 --><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId></dependency><!-- 分页插件 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>2.1.0</version></dependency>
</dependencies>
2. 配置数据源
spring:datasource:url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver# HikariCP连接池配置hikari:maximum-pool-size: 20minimum-idle: 5idle-timeout: 300000connection-timeout: 20000max-lifetime: 1200000pool-name: HikariCP# MyBatis配置
mybatis:# 指定mapper xml文件位置mapper-locations: classpath:mapper/*.xml# 指定实体类包名type-aliases-package: com.example.demo.entity# 配置文件位置config-location: classpath:mybatis-config.xmlconfiguration:# 开启驼峰命名转换map-underscore-to-camel-case: true# 开启二级缓存cache-enabled: true# 延迟加载lazy-loading-enabled: true# 积极延迟加载aggressive-lazy-loading: false# 是否允许单一语句返回多结果集multiple-result-sets-enabled: true# 使用列标签代替列名use-column-label: true# 允许JDBC支持自动生成主键use-generated-keys: true# 指定默认执行器default-executor-type: reuse# 指定默认超时时间default-statement-timeout: 25000# 分页插件配置
pagehelper:helper-dialect: mysqlreasonable: truesupport-methods-arguments: trueparams: count=countSql
📊 实体类定义
1. 用户实体类
package com.example.demo.entity;import java.time.LocalDateTime;public class User {private Long id;private String username;private String email;private String password;private Integer age;private String phone;private Integer status; // 0-禁用 1-启用private LocalDateTime createTime;private LocalDateTime updateTime;// 构造函数public User() {}public User(String username, String email, String password) {this.username = username;this.email = email;this.password = password;}// Getter和Setter方法public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }public String getPhone() { return phone; }public void setPhone(String phone) { this.phone = phone; }public Integer getStatus() { return status; }public void setStatus(Integer status) { this.status = status; }public LocalDateTime getCreateTime() { return createTime; }public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }public LocalDateTime getUpdateTime() { return updateTime; }public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", email='" + email + '\'' +", age=" + age +", phone='" + phone + '\'' +", status=" + status +", createTime=" + createTime +", updateTime=" + updateTime +'}';}
}
2. 数据库表结构
CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',`username` varchar(50) NOT NULL COMMENT '用户名',`email` varchar(100) NOT NULL COMMENT '邮箱',`password` varchar(255) NOT NULL COMMENT '密码',`age` int DEFAULT NULL COMMENT '年龄',`phone` varchar(20) DEFAULT NULL COMMENT '手机号',`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`),UNIQUE KEY `uk_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
🔧 Mapper接口
1. 注解方式
package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;@Mapper
public interface UserMapper {// 查询所有用户@Select("SELECT * FROM users WHERE status = 1 ORDER BY create_time DESC")List<User> findAll();// 根据ID查询用户@Select("SELECT * FROM users WHERE id = #{id}")User findById(@Param("id") Long id);// 根据用户名查询用户@Select("SELECT * FROM users WHERE username = #{username}")User findByUsername(@Param("username") String username);// 插入用户@Insert("INSERT INTO users(username, email, password, age, phone) " +"VALUES(#{username}, #{email}, #{password}, #{age}, #{phone})")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(User user);// 更新用户@Update("UPDATE users SET username=#{username}, email=#{email}, " +"age=#{age}, phone=#{phone} WHERE id=#{id}")int update(User user);// 删除用户(逻辑删除)@Update("UPDATE users SET status = 0 WHERE id = #{id}")int deleteById(@Param("id") Long id);// 动态查询@SelectProvider(type = UserSqlProvider.class, method = "findByCondition")List<User> findByCondition(User user);// 批量插入@Insert({"<script>","INSERT INTO users(username, email, password, age, phone) VALUES ","<foreach collection='users' item='user' separator=','>","(#{user.username}, #{user.email}, #{user.password}, #{user.age}, #{user.phone})","</foreach>","</script>"})int batchInsert(@Param("users") List<User> users);
}
2. SQL提供者类
package com.example.demo.mapper;import com.example.demo.entity.User;
import org.apache.ibatis.jdbc.SQL;public class UserSqlProvider {public String findByCondition(User user) {return new SQL() {{SELECT("*");FROM("users");WHERE("status = 1");if (user.getUsername() != null && !user.getUsername().isEmpty()) {WHERE("username LIKE CONCAT('%', #{username}, '%')");}if (user.getEmail() != null && !user.getEmail().isEmpty()) {WHERE("email = #{email}");}if (user.getAge() != null) {WHERE("age = #{age}");}ORDER_BY("create_time DESC");}}.toString();}
}
📄 XML映射文件
1. UserMapper.xml
<?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"><!-- 结果映射 --><resultMap id="UserResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><result property="email" column="email"/><result property="password" column="password"/><result property="age" column="age"/><result property="phone" column="phone"/><result property="status" column="status"/><result property="createTime" column="create_time"/><result property="updateTime" column="update_time"/></resultMap><!-- 基础字段 --><sql id="Base_Column_List">id, username, email, password, age, phone, status, create_time, update_time</sql><!-- 查询所有用户 --><select id="findAllUsers" resultMap="UserResultMap">SELECT <include refid="Base_Column_List"/>FROM users WHERE status = 1ORDER BY create_time DESC</select><!-- 根据条件查询用户 --><select id="findUsersByCondition" parameterType="User" resultMap="UserResultMap">SELECT <include refid="Base_Column_List"/>FROM users<where>status = 1<if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="email != null and email != ''">AND email = #{email}</if><if test="age != null">AND age = #{age}</if><if test="phone != null and phone != ''">AND phone = #{phone}</if></where>ORDER BY create_time DESC</select><!-- 插入用户 --><insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">INSERT INTO users<trim prefix="(" suffix=")" suffixOverrides=","><if test="username != null">username,</if><if test="email != null">email,</if><if test="password != null">password,</if><if test="age != null">age,</if><if test="phone != null">phone,</if></trim><trim prefix="VALUES (" suffix=")" suffixOverrides=","><if test="username != null">#{username},</if><if test="email != null">#{email},</if><if test="password != null">#{password},</if><if test="age != null">#{age},</if><if test="phone != null">#{phone},</if></trim></insert><!-- 更新用户 --><update id="updateUser" parameterType="User">UPDATE users<set><if test="username != null">username = #{username},</if><if test="email != null">email = #{email},</if><if test="age != null">age = #{age},</if><if test="phone != null">phone = #{phone},</if></set>WHERE id = #{id}</update><!-- 批量更新用户状态 --><update id="batchUpdateStatus">UPDATE users SET status = #{status}WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach></update><!-- 复杂查询:用户统计 --><select id="getUserStatistics" resultType="map">SELECT COUNT(*) as totalUsers,COUNT(CASE WHEN status = 1 THEN 1 END) as activeUsers,COUNT(CASE WHEN status = 0 THEN 1 END) as inactiveUsers,AVG(age) as avgAgeFROM users</select>
</mapper>
🏗️ Service层实现
package com.example.demo.service;import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Map;@Service
@Transactional
public class UserService {@Autowiredprivate UserMapper userMapper;/*** 查询所有用户*/@Transactional(readOnly = true)public List<User> findAll() {return userMapper.findAll();}/*** 分页查询用户*/@Transactional(readOnly = true)public PageInfo<User> findByPage(int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);List<User> users = userMapper.findAll();return new PageInfo<>(users);}/*** 根据ID查询用户*/@Transactional(readOnly = true)public User findById(Long id) {return userMapper.findById(id);}/*** 根据用户名查询用户*/@Transactional(readOnly = true)public User findByUsername(String username) {return userMapper.findByUsername(username);}/*** 创建用户*/public User createUser(User user) {int result = userMapper.insert(user);if (result > 0) {return user; // MyBatis会自动设置生成的ID}throw new RuntimeException("创建用户失败");}/*** 更新用户*/public User updateUser(User user) {int result = userMapper.update(user);if (result > 0) {return userMapper.findById(user.getId());}throw new RuntimeException("更新用户失败");}/*** 删除用户*/public boolean deleteUser(Long id) {int result = userMapper.deleteById(id);return result > 0;}/*** 条件查询用户*/@Transactional(readOnly = true)public List<User> findByCondition(User condition) {return userMapper.findByCondition(condition);}/*** 批量创建用户*/public int batchCreateUsers(List<User> users) {return userMapper.batchInsert(users);}
}
🎮 Controller层
package com.example.demo.controller;import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {@Autowiredprivate UserService userService;/*** 获取所有用户*/@GetMappingpublic ResponseEntity<List<User>> getAllUsers() {List<User> users = userService.findAll();return ResponseEntity.ok(users);}/*** 分页获取用户*/@GetMapping("/page")public ResponseEntity<PageInfo<User>> getUsersByPage(@RequestParam(defaultValue = "1") int pageNum,@RequestParam(defaultValue = "10") int pageSize) {PageInfo<User> pageInfo = userService.findByPage(pageNum, pageSize);return ResponseEntity.ok(pageInfo);}/*** 根据ID获取用户*/@GetMapping("/{id}")public ResponseEntity<User> getUserById(@PathVariable Long id) {User user = userService.findById(id);if (user != null) {return ResponseEntity.ok(user);}return ResponseEntity.notFound().build();}/*** 创建用户*/@PostMappingpublic ResponseEntity<User> createUser(@RequestBody User user) {User createdUser = userService.createUser(user);return ResponseEntity.ok(createdUser);}/*** 更新用户*/@PutMapping("/{id}")public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {user.setId(id);User updatedUser = userService.updateUser(user);return ResponseEntity.ok(updatedUser);}/*** 删除用户*/@DeleteMapping("/{id}")public ResponseEntity<Void> deleteUser(@PathVariable Long id) {boolean deleted = userService.deleteUser(id);if (deleted) {return ResponseEntity.noContent().build();}return ResponseEntity.notFound().build();}/*** 条件查询用户*/@PostMapping("/search")public ResponseEntity<List<User>> searchUsers(@RequestBody User condition) {List<User> users = userService.findByCondition(condition);return ResponseEntity.ok(users);}
}
🔧 MyBatis配置文件
mybatis-config.xml
<?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><settings><!-- 开启驼峰命名转换 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启二级缓存 --><setting name="cacheEnabled" value="true"/><!-- 延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 积极延迟加载 --><setting name="aggressiveLazyLoading" value="false"/><!-- 开启自动生成主键 --><setting name="useGeneratedKeys" value="true"/><!-- 设置超时时间 --><setting name="defaultStatementTimeout" value="25000"/><!-- 设置默认执行器类型 --><setting name="defaultExecutorType" value="REUSE"/></settings><!-- 类型别名 --><typeAliases><package name="com.example.demo.entity"/></typeAliases><!-- 插件配置 --><plugins><!-- 分页插件 --><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/><property name="reasonable" value="true"/><property name="supportMethodsArguments" value="true"/><property name="params" value="count=countSql"/></plugin></plugins>
</configuration>
🧪 测试类
package com.example.demo.service;import com.example.demo.entity.User;
import com.github.pagehelper.PageInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;import java.util.List;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@Transactional
class UserServiceTest {@Autowiredprivate UserService userService;@Testvoid testCreateUser() {User user = new User("testuser", "test@example.com", "password123");user.setAge(25);user.setPhone("13800138000");User createdUser = userService.createUser(user);assertNotNull(createdUser.getId());assertEquals("testuser", createdUser.getUsername());assertEquals("test@example.com", createdUser.getEmail());}@Testvoid testFindById() {// 先创建用户User user = new User("findtest", "findtest@example.com", "password123");User createdUser = userService.createUser(user);// 查询用户User foundUser = userService.findById(createdUser.getId());assertNotNull(foundUser);assertEquals("findtest", foundUser.getUsername());}@Testvoid testFindByPage() {PageInfo<User> pageInfo = userService.findByPage(1, 5);assertNotNull(pageInfo);assertTrue(pageInfo.getSize() <= 5);}@Testvoid testUpdateUser() {// 创建用户User user = new User("updatetest", "updatetest@example.com", "password123");User createdUser = userService.createUser(user);// 更新用户createdUser.setAge(30);createdUser.setPhone("13900139000");User updatedUser = userService.updateUser(createdUser);assertEquals(30, updatedUser.getAge());assertEquals("13900139000", updatedUser.getPhone());}@Testvoid testDeleteUser() {// 创建用户User user = new User("deletetest", "deletetest@example.com", "password123");User createdUser = userService.createUser(user);// 删除用户boolean deleted = userService.deleteUser(createdUser.getId());assertTrue(deleted);// 验证用户已被删除(逻辑删除,状态变为0)User deletedUser = userService.findById(createdUser.getId());assertNull(deletedUser); // 因为查询时过滤了status=0的记录}
}
📊 最佳实践
1. 性能优化
- 合理使用索引
- 避免N+1查询问题
- 使用批量操作
- 开启二级缓存
2. 安全考虑
- 防止SQL注入
- 敏感信息加密
- 参数验证
3. 代码规范
- 统一命名规范
- 合理的事务管理
- 异常处理机制
🔗 下一篇
在下一篇文章中,我们将学习SpringBoot整合Redis缓存的详细用法。
本文关键词: MyBatis, 数据持久化, SQL映射, 动态SQL, 分页查询, 事务管理