1. SpringBoot与MyBatis整合概述
在Java企业级应用开发中,SpringBoot和MyBatis是两个最常用的框架。SpringBoot提供了快速构建独立、生产级Spring应用的能力,而MyBatis则是一个优秀的持久层框架,它消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。两者的结合能够极大提升开发效率和代码可维护性。
MyBatis的核心优势在于其SQL与Java代码的分离设计。与Hibernate等全自动ORM框架不同,MyBatis允许开发者直接编写原生SQL,同时通过XML或注解方式将SQL与Java对象映射起来。这种半自动化的设计既保留了SQL的灵活性,又简化了数据库操作。
2. 环境准备与项目配置
2.1 依赖引入
在SpringBoot项目中使用MyBatis,首先需要在pom.xml中添加相关依赖:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>mybatis-spring-boot-starter是MyBatis官方提供的SpringBoot启动器,它会自动配置MyBatis所需的基本组件,包括SqlSessionFactory、SqlSessionTemplate等。
2.2 数据源配置
在application.properties或application.yml中配置数据源:
spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=your_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver注意:在生产环境中,建议将数据库密码等敏感信息存储在配置中心或使用环境变量,而不是直接写在配置文件中。
3. MyBatis基础使用
3.1 实体类与Mapper接口
首先定义一个简单的实体类:
public class User { private Long id; private String name; private Integer age; private String email; // getters and setters }然后创建对应的Mapper接口:
@Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User findById(Long id); @Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") int insert(User user); @Update("UPDATE user SET name=#{name}, age=#{age}, email=#{email} WHERE id=#{id}") int update(User user); @Delete("DELETE FROM user WHERE id=#{id}") int delete(Long id); }@Mapper注解告诉MyBatis这是一个Mapper接口,SpringBoot启动时会自动扫描并创建实现类。@Select、@Insert等注解则用于定义SQL语句。
3.2 XML映射文件
对于复杂的SQL,建议使用XML映射文件。首先在application.properties中配置XML文件位置:
mybatis.mapper-locations=classpath:mapper/*.xml然后创建对应的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.mapper.UserMapper"> <resultMap id="userResultMap" type="com.example.entity.User"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age"/> <result property="email" column="email"/> </resultMap> <select id="findAll" resultMap="userResultMap"> SELECT * FROM user </select> <select id="findByCondition" resultMap="userResultMap"> SELECT * FROM user <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="age != null"> AND age = #{age} </if> </where> </select> </mapper>4. 高级特性与最佳实践
4.1 动态SQL
MyBatis提供了强大的动态SQL功能,可以根据不同条件生成不同的SQL语句:
<update id="updateSelective" parameterType="com.example.entity.User"> UPDATE user <set> <if test="name != null">name = #{name},</if> <if test="age != null">age = #{age},</if> <if test="email != null">email = #{email},</if> </set> WHERE id = #{id} </update>4.2 分页查询
SpringBoot整合MyBatis实现分页有多种方式:
- 使用PageHelper插件:
PageHelper.startPage(pageNum, pageSize); List<User> users = userMapper.findAll(); PageInfo<User> pageInfo = new PageInfo<>(users);使用MyBatis-Plus的分页插件(如果项目中使用MyBatis-Plus)
手动编写分页SQL:
<select id="findByPage" resultMap="userResultMap"> SELECT * FROM user LIMIT #{offset}, #{pageSize} </select>4.3 事务管理
SpringBoot中默认已经配置了事务管理,只需在Service层方法上添加@Transactional注解即可:
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional public void updateUser(User user) { userMapper.update(user); // 其他数据库操作 } }注意:@Transactional默认只对RuntimeException及其子类异常回滚,如果需要其他异常也触发回滚,可以指定rollbackFor属性。
5. 常见问题与解决方案
5.1 映射问题
问题描述:数据库字段与Java属性名不一致导致映射失败。
解决方案:
- 使用@Results注解:
@Results({ @Result(property = "userName", column = "user_name"), @Result(property = "userAge", column = "user_age") }) @Select("SELECT user_name, user_age FROM user WHERE id = #{id}") User findUserWithDifferentColumnNames(Long id);- 在XML中使用resultMap(推荐):
<resultMap id="userResultMap" type="User"> <result property="userName" column="user_name"/> <result property="userAge" column="user_age"/> </resultMap>5.2 缓存问题
MyBatis有一级缓存和二级缓存:
- 一级缓存:SqlSession级别,默认开启
- 二级缓存:Mapper级别,需要手动配置
开启二级缓存:
- 在配置文件中:
mybatis.configuration.cache-enabled=true- 在Mapper接口上添加注解:
@CacheNamespace public interface UserMapper { // ... }或者在XML映射文件中:
<mapper namespace="com.example.mapper.UserMapper"> <cache/> <!-- 其他配置 --> </mapper>注意:二级缓存可能导致脏读问题,在分布式环境下需要特别小心。
5.3 性能优化
- 批量操作:
@Insert("<script>" + "INSERT INTO user(name, age, email) VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.name}, #{item.age}, #{item.email})" + "</foreach>" + "</script>") void batchInsert(List<User> users);- 延迟加载: 在配置文件中开启:
mybatis.configuration.lazy-loading-enabled=true mybatis.configuration.aggressive-lazy-loading=false然后在关联查询中使用:
<resultMap id="userWithOrders" type="User"> <collection property="orders" column="id" select="com.example.mapper.OrderMapper.findByUserId" fetchType="lazy"/> </resultMap>6. 实际项目中的经验分享
6.1 多数据源配置
在实际项目中,经常需要连接多个数据库。SpringBoot中配置多数据源:
- 定义多个数据源配置类:
@Configuration @MapperScan(basePackages = "com.example.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate") public class PrimaryDataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/primary/*.xml")); return bean.getObject(); } @Bean public SqlSessionTemplate primarySqlSessionTemplate( @Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } }- 在application.properties中配置多个数据源:
# 主数据源 spring.datasource.primary.url=jdbc:mysql://localhost:3306/primary_db spring.datasource.primary.username=root spring.datasource.primary.password=123456 spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver # 从数据源 spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondary_db spring.datasource.secondary.username=root spring.datasource.secondary.password=123456 spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver6.2 枚举类型处理
MyBatis提供了TypeHandler来处理Java枚举类型与数据库值的转换:
- 创建自定义TypeHandler:
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> { @Override public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException { return UserStatus.fromCode(rs.getInt(columnName)); } // 其他重载方法... }- 注册TypeHandler:
@MappedTypes(UserStatus.class) @MappedJdbcTypes(JdbcType.INTEGER) public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> { // ... }或者在XML中指定:
<resultMap id="userResultMap" type="User"> <result property="status" column="status" typeHandler="com.example.handler.UserStatusTypeHandler"/> </resultMap>6.3 复杂查询优化
对于复杂的多表关联查询,建议:
- 使用DTO接收查询结果,而不是直接使用实体类:
public class UserOrderDTO { private String userName; private String orderNo; private BigDecimal amount; // getters and setters }- 在XML中使用resultMap映射:
<resultMap id="userOrderResultMap" type="UserOrderDTO"> <result property="userName" column="user_name"/> <result property="orderNo" column="order_no"/> <result property="amount" column="amount"/> </resultMap> <select id="findUserOrders" resultMap="userOrderResultMap"> SELECT u.name as user_name, o.order_no, o.amount FROM user u JOIN orders o ON u.id = o.user_id WHERE u.id = #{userId} </select>7. 测试与调试技巧
7.1 单元测试
SpringBoot提供了方便的测试支持:
@SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testFindById() { User user = userMapper.findById(1L); assertNotNull(user); assertEquals("张三", user.getName()); } @Test @Transactional @Rollback public void testInsert() { User user = new User(); user.setName("李四"); user.setAge(25); user.setEmail("lisi@example.com"); int result = userMapper.insert(user); assertEquals(1, result); assertNotNull(user.getId()); } }7.2 SQL日志输出
在开发阶段,可以开启MyBatis的SQL日志:
# 显示执行的SQL及其参数 logging.level.com.example.mapper=DEBUG或者在配置类中配置:
@Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration -> { configuration.setLogImpl(StdOutImpl.class); }; } }7.3 性能监控
集成Druid数据源监控:
- 添加依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>- 配置Druid:
spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* spring.datasource.druid.stat-view-servlet.reset-enable=false spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=admin访问http://localhost:8080/druid即可查看SQL监控信息。
8. 项目结构建议
一个良好的项目结构能提高代码的可维护性:
src/main/java ├── com.example │ ├── config // 配置类 │ ├── controller // 控制器 │ ├── service // 服务层 │ │ ├── impl // 服务实现 │ ├── mapper // Mapper接口 │ ├── entity // 实体类 │ ├── dto // 数据传输对象 │ ├── vo // 视图对象 │ ├── handler // TypeHandler │ ├── interceptor // 拦截器 │ └── Application.java // 启动类 src/main/resources ├── mapper // XML映射文件 ├── static // 静态资源 ├── templates // 模板文件 └── application.properties9. 版本兼容性与升级
不同版本的SpringBoot与MyBatis可能存在兼容性问题:
- SpringBoot 2.5.x 推荐使用 mybatis-spring-boot-starter 2.2.x
- SpringBoot 2.4.x 推荐使用 mybatis-spring-boot-starter 2.1.x
- SpringBoot 2.3.x 推荐使用 mybatis-spring-boot-starter 2.1.x
升级时需要注意:
- 检查依赖兼容性
- 备份重要数据
- 逐步测试各个功能模块
10. 扩展与自定义
10.1 自定义插件
MyBatis允许开发插件来拦截核心方法执行:
@Intercepts({ @Signature(type= Executor.class, method="update", args={MappedStatement.class, Object.class}), @Signature(type= Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class MyBatisInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 前置处理 Object result = invocation.proceed(); // 后置处理 return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 设置属性 } }然后在配置类中注册:
@Bean public MyBatisInterceptor myBatisInterceptor() { return new MyBatisInterceptor(); }10.2 自定义TypeHandler
对于特殊的数据类型转换,可以创建自定义TypeHandler:
public class JsonTypeHandler<T> extends BaseTypeHandler<T> { private Class<T> type; public JsonTypeHandler(Class<T> type) { this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } @Override public T getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); return json == null ? null : JSON.parseObject(json, type); } // 其他重载方法... }使用方式:
@TableName(autoResultMap = true) public class User { @TableField(typeHandler = JsonTypeHandler.class) private List<String> tags; }11. 安全注意事项
SQL注入防护:
- 始终使用#{}而不是${}进行参数绑定
- 对用户输入进行严格验证
- 使用MyBatis的动态SQL标签而不是字符串拼接
敏感数据保护:
- 数据库密码等敏感信息应加密存储
- 使用配置中心管理敏感配置
- 限制数据库用户的权限
日志安全:
- 生产环境不应记录完整的SQL和参数
- 敏感字段应在日志中脱敏
12. 性能调优建议
- 连接池配置:
spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000- MyBatis配置优化:
mybatis.configuration.default-fetch-size=100 mybatis.configuration.default-statement-timeout=30 mybatis.configuration.map-underscore-to-camel-case=true- 批量操作:
@Transactional public void batchInsert(List<User> users) { SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH); try { UserMapper mapper = sqlSession.getMapper(UserMapper.class); for (User user : users) { mapper.insert(user); } sqlSession.commit(); } finally { sqlSession.close(); } }13. 常见错误与解决方案
Invalid bound statement (not found):
- 检查Mapper接口是否被@Mapper注解或@MapperScan扫描到
- 检查XML文件路径是否正确
- 检查XML中的namespace是否与Mapper接口全限定名一致
Parameter 'xxx' not found:
- 检查参数名是否与方法参数名一致
- 使用@Param注解明确指定参数名:
User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);TooManyResultsException:
- 确保查询结果只有一条记录时使用selectOne
- 或者修改查询条件确保结果唯一
14. 与其他技术整合
14.1 整合MyBatis-Plus
MyBatis-Plus是对MyBatis的增强:
- 添加依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency>- 创建通用Mapper:
public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> { }- 使用示例:
@Service public class UserService { @Autowired private UserMapper userMapper; public Page<User> findUsers(int page, int size) { return userMapper.selectPage(new Page<>(page, size), null); } }14.2 整合Redis缓存
- 添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>- 配置Redis:
spring.redis.host=localhost spring.redis.port=6379- 使用缓存注解:
@Cacheable(value = "user", key = "#id") public User findById(Long id) { return userMapper.findById(id); }15. 实际项目经验总结
SQL管理:
- 复杂的SQL建议写在XML中
- 简单的CRUD可以使用注解
- 保持SQL的可读性,适当添加注释
事务边界:
- 事务应放在Service层
- 避免长事务
- 合理设置事务隔离级别和传播行为
代码生成:
- 使用MyBatis Generator或MyBatis-Plus代码生成器
- 自定义模板以满足项目规范
- 生成的代码应放在单独的模块或目录
监控与报警:
- 监控慢SQL
- 设置连接池使用阈值报警
- 定期检查数据库性能
文档维护:
- 维护数据字典
- 记录重要的SQL变更
- 编写数据库设计文档
通过以上全面的介绍,你应该已经掌握了在SpringBoot项目中集成和使用MyBatis的核心知识和技巧。实际项目中,还需要根据具体需求和团队规范进行适当调整和扩展。