項目初期純 Spring Data JPA,後期引入 MyBatis 處理複雜多表關聯查詢。兩套 ORM 共存需解決:數據源共享、事務管理統一、Repository/Mapper 掃描隔離、懶加載異常。
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = "com.example.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
@EnableJpaRepositories(basePackages = "com.example.repository", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class DualOrmConfig {
@Primary // JPA 預設使用
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
// ========== MyBatis 配置 ==========
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
bean.setTypeAliasesPackage("com.example.entity");
// 關鍵:開啟駝峰映射、懶加載
org.apache.ibatis.session.Configuration cfg = new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setLazyLoadingEnabled(true);
cfg.setAggressiveLazyLoading(false);
bean.setConfiguration(cfg);
return bean.getObject();
}
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource ds) {
return new DataSourceTransactionManager(ds); // 統一事務管理器
}
// ========== JPA 配置 ==========
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("dataSource") DataSource ds) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(ds);
em.setPackagesToScan("com.example.entity");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties props = new Properties();
props.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
props.setProperty("hibernate.hbm2ddl.auto", "update");
props.setProperty("hibernate.show_sql", "false");
props.setProperty("hibernate.format_sql", "true");
// 關鍵:解決 no Session 懶加載異常
props.setProperty("hibernate.enable_lazy_load_no_trans", "true");
em.setJpaProperties(props);
return em;
}
@Bean
public JpaTransactionManager jpaTransactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
spring:
datasource:
url: jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
jpa:
open-in-view: false # 關鍵:關閉 OSIV,避免懶加載隱患,Controller 層不再有 Session
# mybatis 配置已在 Java Config 完成,這裡可留空
| 場景 | 建議 |
|---|---|
| 單一 JPA Repository 操作 | @Transactional 在 Service 方法上,自動使用 JpaTransactionManager |
| 單一 MyBatis Mapper 操作 | 同樣 @Transactional,DataSourceTransactionManager 兼容 |
| 混合操作 (JPA + MyBatis) | 同一 @Transactional 方法內調用,共用同一 DB 連接與事務,自動回滾 |
| 跨數據源 | 需引入 JTA (Atomikos/Bitronix) 或 Seata,超出本文範圍 |
核心原則:同一 DataSource + 同一 PlatformTransactionManager + @Transactional,Spring 會自動綁定連接到 ThreadLocal,實現混合操作事務一致性。
| 坑點 | 現象 | 解決 |
|---|---|---|
| JPA 懶加載報錯 | org.hibernate.LazyInitializationException: could not initialize proxy - no Session | 1) 設置 hibernate.enable_lazy_load_no_trans=true 2) 推薦:Service 層用 @Transactional 或 DTO 投影查詢避免懶加載 |
| MyBatis 駝峰映射失效 | 數據庫字段 user_name 映射不到 userName | Configuration 設置 mapUnderscoreToCamelCase=true |
| 事務不生效 | 混合操作拋異常但數據已提交 | 確保 Service 類非 final、方法 public、@Transactional 在 public 方法上、掃描包正確 |
| 實體類衝突 | JPA Entity 與 MyBatis ResultType 共用類,註解衝突 | 建議分包:entity (JPA) / dto (MyBatis ResultMap) 或統一用 JPA 註解兼容 |
| 雙主鍵生成策略衝突 | JPA @GeneratedValue vs MyBatis <selectKey> | 統一由數據庫自增/序列生成,JPA 用 GenerationType.IDENTITY,MyBatis useGeneratedKeys="true" keyProperty="id" |
repository (JPA) / mapper (MyBatis) / entity (共用或分離)DataSourceTransactionManager,@Transactional 統管spring.jpa.open-in-view=false,強制 Service 層負責事務邊界Spring Data JPA + MyBatis 混合整合的核心在於 共享 DataSource、統一 TransactionManager、隔離掃描包路徑、關閉 OpenSessionInView。遵循上述配置模板與分工原則,即可在同一項目中享受 JPA 開發效率與 MyBatis SQL 掌控力的雙重紅利。