1. 初识Spring Boot与MyBatis联姻
老张最近在重构项目的用户模块时,发现原来的Hibernate用起来总有点水土不服。比如说,复杂的多表联查要写HQL总觉得不够直观,调整SQL性能优化时又像戴着脚镣跳舞。在技术选型会上,架构师小王力荐MyBatis与Spring Boot的黄金组合,于是我们开启了这段探索之旅。
MyBatis这个数据持久层框架最大的魅力在于它既保持SQL灵活性,又能让Java代码和SQL优雅解耦。配合Spring Boot的自动化配置,就像咖啡遇上伴侣,让原本繁琐的ORM配置变得清爽可口。举个例子,原本需要手动配置的数据源、事务管理现在都可以通过几行配置搞定。
2. 环境搭建与基础配置
2.1 Maven依赖准备
首先用Spring Initializr初始化项目时,除了Web模块外,特别注意勾选这些依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2.2 配置文件之美
在application.yml中配置数据源和MyBatis参数时,我们推荐采用阿里云Druid数据源:
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db?useSSL=false
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.entity
configuration:
map-underscore-to-camel-case: true
这个配置实现了三个魔法效果:自动驼峰命名转换、实体类别名自动注册、XML映射文件自动扫描。
3. 领域模型与Mapper构建
3.1 实体类设计
先定义一个用户实体类:
public class User {
private Long id;
private String username;
private Integer age;
private LocalDateTime createTime;
// 此处省略getter/setter,推荐使用Lombok注解
}
3.2 Mapper接口的写法
方式1:XML映射法(推荐)
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User selectById(@Param("userId") Long id);
@Insert("INSERT INTO users(username,age) VALUES(#{username},#{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 复杂查询采用XML方式
List<User> selectByCondition(Map<String, Object> params);
}
对应的XML文件UserMapper.xml应该存放在resources/mapper目录:
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByCondition" resultType="User">
SELECT * FROM users
<where>
<if test="username != null">
AND username LIKE CONCAT('%',#{username},'%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</where>
ORDER BY create_time DESC
</select>
</mapper>
方式2:注解动态SQL(需@SelectProvider)
@Mapper
public interface UserAnnotationMapper {
@SelectProvider(type = UserSqlBuilder.class, method = "buildSelectByCondition")
List<User> selectByCondition(Map<String, Object> params);
class UserSqlBuilder {
public static String buildSelectByCondition(Map<String, Object> params) {
return new SQL() {{
SELECT("*");
FROM("users");
if (params.get("username") != null) {
WHERE("username LIKE CONCAT('%',#{username},'%')");
}
if (params.get("minAge") != null) {
WHERE("age >= #{minAge}");
}
if (params.get("maxAge") != null) {
WHERE("age <= #{maxAge}");
}
ORDER_BY("create_time DESC");
}}.toString();
}
}
}
这种写法把SQL构建逻辑封装在内部类中,适合不喜欢XML的开发团队。
4. 服务层实现技巧
在Service层进行事务管理时,要注意@Transactional注解的生效条件:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void batchInsert(List<User> users) {
users.forEach(user -> {
if (user.getUsername() == null) {
throw new IllegalArgumentException("用户名不能为空");
}
userMapper.insert(user);
});
}
public PageInfo<User> paginationQuery(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectByCondition(Collections.emptyMap());
return new PageInfo<>(users);
}
}
这里展示了分页插件的典型用法,需要在pom中添加pagehelper依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
5. 实践中的疑难解答
5.1 懒加载陷阱
当我们使用嵌套查询时,MyBatis的延迟加载功能可能会引发NPE异常。例如在用户信息中包含订单列表的场景:
<resultMap id="userWithOrders" type="User">
<collection property="orders" column="id"
select="com.example.mapper.OrderMapper.selectByUserId"
fetchType="lazy"/>
</resultMap>
此时需要在配置文件中启用延迟加载并指定代理方式:
mybatis:
configuration:
lazy-loading-enabled: true
aggressive-lazy-loading: false
proxy-target-class: true
5.2 二级缓存雪崩
配置缓存时要特别注意缓存策略,避免大量缓存同时失效导致的雪崩效应:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
建议生产环境配合Redis等分布式缓存使用,可以通过实现Cache接口来自定义缓存策略。
6. 技术选型深度分析
6.1 适用场景
- 复杂查询主导的系统:适合需要精细控制SQL的场景
- 遗留系统改造:兼容已有复杂SQL的平稳迁移
- 混合数据库环境:同一系统需要操作多种数据库
- 需要动态SQL的场景:查询条件动态组合的情况
6.2 优劣势对比
优势方面:
- SQL可视化调试:直接看到最终执行的SQL语句
- 学习曲线平缓:熟悉SQL的开发者快速上手
- 性能调优便捷:直接优化原生SQL语句
- 灵活的结果映射:支持复杂对象结构的映射
不足之处:
- 需要维护XML文件:项目庞大时文件数量较多
- 简单CRUD略啰嗦:相比Spring Data JPA不够简洁
- 缓存管理复杂:二级缓存需要谨慎处理
7. 架构师的私房建议
- 分页规范:统一使用PageHelper分页插件,避免各写各的分页逻辑
- SQL审查:建立SQL代码审查机制,防止性能杀手SQL进入生产环境
- 安全防御:定期扫描XML文件,防范SQL注入漏洞
- 监控预警:集成Druid监控台,实时观察数据库连接池状态
- 版本规范:锁定MyBatis和Spring Boot的版本号,避免兼容性问题
8. 项目实战进阶
最后通过一个完整的接口案例展示全流程实现:
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/search")
public ResponseResult<PageInfo<User>> search(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Map<String, Object> params = new HashMap<>();
params.put("username", keyword);
return ResponseResult.success(userService.paginationQuery(params, page, size));
}
@PostMapping("/batch")
public ResponseResult<Integer> batchCreate(@RequestBody List<UserDTO> dtos) {
// 此处省略DTO转换逻辑
return ResponseResult.success(userService.batchInsert(users));
}
}
对应的统一返回封装:
public class ResponseResult<T> {
private Integer code;
private String message;
private T data;
// 成功静态方法等实现略
}
9. 应用场景总结
从电商系统的商品搜索模块到金融系统的交易流水查询,从社交平台的动态信息流到物联网设备的数据分析,MyBatis+Spring Boot的组合几乎覆盖了所有需要灵活操作关系型数据库的场景。特别是在需要处理复杂报表查询、历史数据归档、多维度数据分析等场景下,其优势更为明显。
评论