Spring Boot整合MyBatis案例:从零搭建高效数据访问层(附完整代码)
目录导读
- 整合前的准备:环境要求与项目初始化
- 核心依赖配置:Maven坐标与yml配置详解
- 实体类与Mapper接口:领域模型与数据访问层设计
- XML映射文件:SQL语句与结果映射最佳实践
- Service与Controller:业务逻辑与接口暴露
- 分页与动态SQL:实战中高频使用的进阶技巧
- 事务管理:@Transactional的正确打开方式
- 常见问题排查:报错解决与性能优化建议
- QA问答:整合过程中的高频疑问解答
整合前的准备:环境要求与项目初始化
在开始整合案例之前,请确保你的开发环境满足以下条件:

- JDK 8+
- Maven 3.6+
- MySQL 5.7+(或MariaDB)
- IDEA/Eclipse(推荐使用IDEA,社区版即可)
项目初始化:使用Spring Initializr(https://start.spring.io)快速创建基础工程,选择Spring Web、MySQL Driver依赖,这里我们以Maven项目为例,最终目录结构如图1所示。
注意:案例中的域名统一替换为
https://example.com,实际开发中请替换为你的服务地址。
核心依赖配置:Maven坐标与yml配置详解
在pom.xml中必需添加以下依赖:
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- 可选:分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
application.yml配置如下(关键位置已做注释):
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml # XML映射文件位置
type-aliases-package: com.example.demo.entity # 实体类包名
configuration:
map-underscore-to-camel-case: true # 驼峰命名自动映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
# 分页插件配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
实体类与Mapper接口:领域模型与数据访问层设计
以用户表t_user为例,实体类User.java代码如下:
public class User {
private Long id;
private String username;
private String email;
private Integer age;
// getter/setter 省略
}
Mapper接口(核心方法示例):
public interface UserMapper {
// 根据ID查询用户
User selectById(@Param("id") Long id);
// 查询所有用户
List<User> selectAll();
// 新增用户,返回受影响行数
int insert(User user);
// 更新用户信息
int update(User user);
// 删除用户
int deleteById(@Param("id") Long id);
}
XML映射文件:SQL语句与结果映射最佳实践
在resources/mapper/UserMapper.xml中编写SQL映射:
<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="age" column="age"/>
</resultMap>
<!-- 查询:按主键 -->
<select id="selectById" resultMap="UserResultMap">
SELECT * FROM t_user WHERE id = #{id}
</select>
<!-- 动态SQL示例:条件查询 -->
<select id="selectByCondition" resultMap="UserResultMap">
SELECT * FROM t_user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age >= #{age}
</if>
</where>
</select>
<!-- 新增 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username, email, age)
VALUES(#{username}, #{email}, #{age})
</insert>
</mapper>
最佳实践:useGeneratedKeys="true" 可以自动回填自增主键,避免二次查询。
Service与Controller:业务逻辑与接口暴露
Service层(事务边界放在这里):
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 查询用户
public User getUserById(Long id) {
return userMapper.selectById(id);
}
// 新增用户(示例中演示事务回滚)
@Transactional(rollbackFor = Exception.class)
public int createUser(User user) {
int result = userMapper.insert(user);
// 模拟异常,验证事务是否回滚
if (user.getAge() != null && user.getAge() > 120) {
throw new RuntimeException("年龄不合法,事务回滚");
}
return result;
}
}
Controller层:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@PostMapping
public ResponseEntity<Integer> addUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
}
分页与动态SQL:实战中高频使用的进阶技巧
分页查询(使用PageHelper):
public PageInfo<User> getUserPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
动态SQL除了<if>、<where>,还有<choose>、<foreach>等,遍历集合是极高频率场景:
<select id="selectByIds" resultMap="UserResultMap">
SELECT * FROM t_user WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
事务管理:@Transactional的正确打开方式
Spring Boot中,只需在方法上添加@Transactional即可,重要注意点:
- 默认只回滚
RuntimeException,若需捕获受检异常,请设置rollbackFor = Exception.class - 事务应放在Service实现类方法上,不要放在Controller层
- 同一类内部调用事务方法,事务会失效(代理机制),应通过注入自身或拆分类解决
常见问题排查:报错解决与性能优化建议
问题1:Mapper接口无法注入(No qualifying bean of type)
解决:在启动类上添加@MapperScan("com.example.demo.mapper"),或者为每个Mapper接口加@Mapper注解。
问题2:数据库列名和实体属性不对应,查询结果为null
解决:开启map-underscore-to-camel-case: true,或使用@Results注解/XML中的resultMap手动映射。
问题3:SQL日志不打印
解决:检查log-impl配置是否被注释;确认mapper-locations路径是否正确(如classpath*:mapper/**/*.xml)。
性能优化建议:
- 避免
SELECT *,只查必要字段 - 大表查询务必使用分页,永远不要用
LIMIT 100000,10,改用游标或延迟关联 - 批量插入使用
<foreach>拼接,或使用ExecutorType.BATCH
QA问答:整合过程中的高频疑问解答
Q1:MyBatis-Plus与MyBatis如何选择?
答:MyBatis-Plus是在MyBatis基础上的增强工具,内置CRUD方法、分页插件、乐观锁等,适合快速开发,但如果你需要高度定制SQL映射,原生MyBatis更灵活,本项目以原生MyBatis为例,展示清晰的数据访问层原理。
Q2:为什么使用@Param注解?
答:当Mapper方法有多个参数时,MyBatis需要绑定参数名称,使用@Param("id")显式指定,避免参数顺序错误,也方便在XML中用#{id}引用。
Q3:整合后,启动报错“Invalid bound statement (not found)”
答:说明项目未加载到XML文件,检查mapper-locations路径,且保证target/classes/mapper下确实编译出了XML文件,若使用Kotlin/多模块结构,请使用classpath*通配。
Q4:事务回滚不生效,为什么?
答:最常见的两个原因:一是Spring Boot 2.x默认不使用CGLIB代理,如果ServiceImpl方法被final修饰,事务失效;二是内部调用(this调用)时,代理实例并不会拦截方法,请确保在外部调用Service方法。
Q5:分页插件PageHelper失效?
答:确认pagehelper依赖正确,且没有同时引入多个版本,在Spring Boot项目中,如果有多数据源,需要单独为每个数据源配置分页拦截器。
写在最后:本文通过一个完整的用户管理功能,展示了Spring Boot集成MyBatis的标准流程,从依赖配置、实体映射、动态SQL、事务处理到分页方案,覆盖了开发中90%以上的场景,建议读者跟随代码自己动手敲一遍,遇到问题优先查看控制台SQL日志,如果本教程对你有帮助,欢迎收藏转发,你的支持是我持续输出的动力。