spring和mybatis的整合
一、环境搭建与依赖管理
1. 依赖配置扩展
除了基础的Spring、MyBatis和数据库驱动,推荐添加以下依赖以增强功能:
<!-- Spring JDBC(事务管理依赖) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<!-- 日志框架(推荐SLF4J + Logback) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- 测试支持 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.20</version>
<scope>test</scope>
</dependency>
2. 多环境配置(开发、测试、生产)
通过Spring的Profile机制实现环境隔离:
<!-- applicationContext.xml -->
<beans profile="dev">
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<!-- 开发环境数据库配置 -->
</bean>
</beans>
<beans profile="prod">
<bean id="dataSource" class="com.zaxxer.HikariDataSource">
<!-- 生产环境数据库配置 -->
</bean>
</beans>
激活方式:
-
JVM参数:
-Dspring.profiles.active=dev
-
代码中:
System.setProperty("spring.profiles.active", "dev")
二、进阶配置技巧
1. JavaConfig配置方式(替代XML)
使用注解驱动配置更简洁:
@Configuration
@MapperScan("com.example.mapper") // 自动扫描Mapper接口
@EnableTransactionManagement // 启用注解事务
public class MyBatisConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test_db");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource());
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml"));
return factory.getObject();
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
2. 多数据源配置
在复杂项目中可能需要连接多个数据库:
@Configuration
public class MultiDataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
return factory.getObject();
}
@Bean
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
return factory.getObject();
}
}
三、MyBatis高阶特性实战
1. 动态SQL与复杂查询
在Mapper XML中编写动态SQL:
<select id="selectUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
ORDER BY id DESC
</select>
2. 一级与二级缓存配置
-
一级缓存:默认开启,作用域为SqlSession。
<mapper namespace="com.example.mapper.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
</mapper>
在Spring中需确保SqlSessionFactory的配置:
factory.setConfiguration(mybatisConfiguration()); // 设置全局配置
// 自定义Configuration
public Configuration mybatisConfiguration() {
Configuration config = new Configuration();
config.setCacheEnabled(true); // 启用二级缓存
return config;
}
3. 自定义TypeHandler
处理复杂类型转换(如JSON字段与Java对象的映射):
@MappedTypes(UserDetail.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class UserDetailTypeHandler extends BaseTypeHandler<UserDetail> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
UserDetail parameter, JdbcType jdbcType) {
ps.setString(i, objectMapper.writeValueAsString(parameter));
}
@Override
public UserDetail getNullableResult(ResultSet rs, String columnName) {
return parseJson(rs.getString(columnName));
}
// 其他方法省略...
}
在MyBatis配置中注册:
<typeHandlers>
<typeHandler handler="com.example.handler.UserDetailTypeHandler"/>
</typeHandlers>
四、性能优化与最佳实践
1. 连接池调优(HikariCP为例)
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setConnectionTimeout(30000); // 连接超时时间(毫秒)
config.setIdleTimeout(600000); // 空闲连接存活时间
config.setMaxLifetime(1800000); // 连接最大生命周期
2. 批量操作提升性能
使用SqlSession
的批量模式:
@Autowired
private SqlSessionFactory sqlSessionFactory;
public void batchInsertUsers(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insertUser(user);
}
session.commit(); // 手动提交
}
}
3. SQL优化建议
-
避免在循环中执行单条SQL
-
使用
<foreach>
标签实现批量插入:<insert id="batchInsert"> INSERT INTO user (name, email) VALUES <foreach item="user" collection="list" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>
五、测试与调试
1. 单元测试(Spring Test + JUnit)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyBatisConfig.class)
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
@Transactional // 测试后自动回滚
public void testInsertUser() {
User user = new User("Alice", "alice@example.com");
userMapper.insertUser(user);
assertNotNull(user.getId());
}
}
2. SQL日志输出
配置Logback(logback.xml
):
<logger name="com.example.mapper" level="DEBUG"/> <!-- Mapper接口日志 -->
<logger name="java.sql.Connection" level="DEBUG"/> <!-- SQL执行日志 -->
<logger name="org.mybatis" level="TRACE"/> <!-- MyBatis内部日志 -->
六、常见问题深度解析
1. 事务失效场景
-
原因1:非public方法使用
@Transactional
-
原因2:自调用(同类方法内部调用)
解决方案:通过AOP代理调用或使用AopContext.currentProxy()
。
2. 懒加载异常(LazyInitializationException)
-
背景:在事务外访问延迟加载的关联对象
-
解决方案:
-
使用
OpenSessionInViewFilter
(Web项目) -
在事务范围内提前加载所需数据(如
JOIN FETCH
)
-
3. MyBatis映射字段丢失
-
原因:数据库字段名与Java属性名不一致(如驼峰vs下划线)
-
解决:
全局配置开启驼峰映射:<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>