SpringBoot整合MyBatis实战与最佳实践

发布时间:2026/9/14 21:42:24
SpringBoot整合MyBatis实战与最佳实践 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 groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.0/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependencymybatis-spring-boot-starter是MyBatis官方提供的SpringBoot启动器它会自动配置MyBatis所需的基本组件包括SqlSessionFactory、SqlSessionTemplate等。2.2 数据源配置在application.properties或application.yml中配置数据源spring.datasource.urljdbc:mysql://localhost:3306/your_database?useSSLfalseserverTimezoneUTC spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.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-locationsclasspath:mapper/*.xml然后创建对应的XML文件?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mapper.UserMapper resultMap iduserResultMap typecom.example.entity.User id propertyid columnid/ result propertyname columnname/ result propertyage columnage/ result propertyemail columnemail/ /resultMap select idfindAll resultMapuserResultMap SELECT * FROM user /select select idfindByCondition resultMapuserResultMap SELECT * FROM user where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /where /select /mapper4. 高级特性与最佳实践4.1 动态SQLMyBatis提供了强大的动态SQL功能可以根据不同条件生成不同的SQL语句update idupdateSelective parameterTypecom.example.entity.User UPDATE user set if testname ! nullname #{name},/if if testage ! nullage #{age},/if if testemail ! nullemail #{email},/if /set WHERE id #{id} /update4.2 分页查询SpringBoot整合MyBatis实现分页有多种方式使用PageHelper插件PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.findAll(); PageInfoUser pageInfo new PageInfo(users);使用MyBatis-Plus的分页插件如果项目中使用MyBatis-Plus手动编写分页SQLselect idfindByPage resultMapuserResultMap SELECT * FROM user LIMIT #{offset}, #{pageSize} /select4.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 iduserResultMap typeUser result propertyuserName columnuser_name/ result propertyuserAge columnuser_age/ /resultMap5.2 缓存问题MyBatis有一级缓存和二级缓存一级缓存SqlSession级别默认开启二级缓存Mapper级别需要手动配置开启二级缓存在配置文件中mybatis.configuration.cache-enabledtrue在Mapper接口上添加注解CacheNamespace public interface UserMapper { // ... }或者在XML映射文件中mapper namespacecom.example.mapper.UserMapper cache/ !-- 其他配置 -- /mapper注意二级缓存可能导致脏读问题在分布式环境下需要特别小心。5.3 性能优化批量操作Insert(script INSERT INTO user(name, age, email) VALUES foreach collectionlist itemitem separator, (#{item.name}, #{item.age}, #{item.email}) /foreach /script) void batchInsert(ListUser users);延迟加载 在配置文件中开启mybatis.configuration.lazy-loading-enabledtrue mybatis.configuration.aggressive-lazy-loadingfalse然后在关联查询中使用resultMap iduserWithOrders typeUser collection propertyorders columnid selectcom.example.mapper.OrderMapper.findByUserId fetchTypelazy/ /resultMap6. 实际项目中的经验分享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.urljdbc:mysql://localhost:3306/primary_db spring.datasource.primary.usernameroot spring.datasource.primary.password123456 spring.datasource.primary.driver-class-namecom.mysql.cj.jdbc.Driver # 从数据源 spring.datasource.secondary.urljdbc:mysql://localhost:3306/secondary_db spring.datasource.secondary.usernameroot spring.datasource.secondary.password123456 spring.datasource.secondary.driver-class-namecom.mysql.cj.jdbc.Driver6.2 枚举类型处理MyBatis提供了TypeHandler来处理Java枚举类型与数据库值的转换创建自定义TypeHandlerpublic class UserStatusTypeHandler extends BaseTypeHandlerUserStatus { 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)); } // 其他重载方法... }注册TypeHandlerMappedTypes(UserStatus.class) MappedJdbcTypes(JdbcType.INTEGER) public class UserStatusTypeHandler extends BaseTypeHandlerUserStatus { // ... }或者在XML中指定resultMap iduserResultMap typeUser result propertystatus columnstatus typeHandlercom.example.handler.UserStatusTypeHandler/ /resultMap6.3 复杂查询优化对于复杂的多表关联查询建议使用DTO接收查询结果而不是直接使用实体类public class UserOrderDTO { private String userName; private String orderNo; private BigDecimal amount; // getters and setters }在XML中使用resultMap映射resultMap iduserOrderResultMap typeUserOrderDTO result propertyuserName columnuser_name/ result propertyorderNo columnorder_no/ result propertyamount columnamount/ /resultMap select idfindUserOrders resultMapuserOrderResultMap 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} /select7. 测试与调试技巧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(lisiexample.com); int result userMapper.insert(user); assertEquals(1, result); assertNotNull(user.getId()); } }7.2 SQL日志输出在开发阶段可以开启MyBatis的SQL日志# 显示执行的SQL及其参数 logging.level.com.example.mapperDEBUG或者在配置类中配置Configuration public class MyBatisConfig { Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration - { configuration.setLogImpl(StdOutImpl.class); }; } }7.3 性能监控集成Druid数据源监控添加依赖dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.6/version /dependency配置Druidspring.datasource.druid.stat-view-servlet.enabledtrue spring.datasource.druid.stat-view-servlet.url-pattern/druid/* spring.datasource.druid.stat-view-servlet.reset-enablefalse spring.datasource.druid.stat-view-servlet.login-usernameadmin spring.datasource.druid.stat-view-servlet.login-passwordadmin访问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.xSpringBoot 2.4.x 推荐使用 mybatis-spring-boot-starter 2.1.xSpringBoot 2.3.x 推荐使用 mybatis-spring-boot-starter 2.1.x升级时需要注意检查依赖兼容性备份重要数据逐步测试各个功能模块10. 扩展与自定义10.1 自定义插件MyBatis允许开发插件来拦截核心方法执行Intercepts({ Signature(type Executor.class, methodupdate, args{MappedStatement.class, Object.class}), Signature(type Executor.class, methodquery, 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对于特殊的数据类型转换可以创建自定义TypeHandlerpublic class JsonTypeHandlerT extends BaseTypeHandlerT { private ClassT type; public JsonTypeHandler(ClassT 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 ListString tags; }11. 安全注意事项SQL注入防护始终使用#{}而不是${}进行参数绑定对用户输入进行严格验证使用MyBatis的动态SQL标签而不是字符串拼接敏感数据保护数据库密码等敏感信息应加密存储使用配置中心管理敏感配置限制数据库用户的权限日志安全生产环境不应记录完整的SQL和参数敏感字段应在日志中脱敏12. 性能调优建议连接池配置spring.datasource.druid.initial-size5 spring.datasource.druid.min-idle5 spring.datasource.druid.max-active20 spring.datasource.druid.max-wait60000 spring.datasource.druid.time-between-eviction-runs-millis60000 spring.datasource.druid.min-evictable-idle-time-millis300000MyBatis配置优化mybatis.configuration.default-fetch-size100 mybatis.configuration.default-statement-timeout30 mybatis.configuration.map-underscore-to-camel-casetrue批量操作Transactional public void batchInsert(ListUser 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-PlusMyBatis-Plus是对MyBatis的增强添加依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.4.3/version /dependency创建通用Mapperpublic interface BaseMapperT extends com.baomidou.mybatisplus.core.mapper.BaseMapperT { }使用示例Service public class UserService { Autowired private UserMapper userMapper; public PageUser findUsers(int page, int size) { return userMapper.selectPage(new Page(page, size), null); } }14.2 整合Redis缓存添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency配置Redisspring.redis.hostlocalhost spring.redis.port6379使用缓存注解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的核心知识和技巧。实际项目中还需要根据具体需求和团队规范进行适当调整和扩展。