在SSM(Spring+SpringMVC+MyBatis)框架中实现一次操作多数据库的需求,通常涉及多个数据源的配置、动态切换以及事务管理,以下从配置、代码实现、事务处理等方面详细说明具体步骤和注意事项。
多数据源配置
首先需要在Spring配置文件中定义多个数据源,每个数据源对应一个数据库,配置两个数据源dataSource1
和dataSource2
,分别指向不同的数据库。
<!-- 数据源1 --> <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"/> <property name="user" value="root"/> <property name="password" value="123456"/> </bean> <!-- 数据源2 --> <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db2"/> <property name="user" value="root"/> <property name="password" value="123456"/> </bean>
配置SqlSessionFactory和Mapper扫描
每个数据源需要独立的SqlSessionFactory
和MapperScannerConfigurer
,通过指定不同的sqlSessionFactoryBeanName
和basePackage
来实现隔离。
<!-- SqlSessionFactory1 --> <bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource1"/> <property name="mapperLocations" value="classpath:mapper/db1/*.xml"/> </bean> <!-- SqlSessionFactory2 --> <bean id="sqlSessionFactory2" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource2"/> <property name="mapperLocations" value="classpath:mapper/db2/*.xml"/> </bean> <!-- Mapper扫描1 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper.db1"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory1"/> </bean> <!-- Mapper扫描2 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper.db2"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory2"/> </bean>
动态数据源切换
通过继承AbstractRoutingDataSource
实现动态数据源切换,在determineCurrentLookupKey()
方法中返回当前线程对应的数据源标识。
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } }
定义线程安全的DataSourceContextHolder
来存储当前数据源类型:
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(String type) { contextHolder.set(type); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } }
在Spring配置中注册动态数据源:
<bean id="dynamicDataSource" class="com.example.config.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="db1" value-ref="dataSource1"/> <entry key="db2" value-ref="dataSource2"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1"/> </bean>
注解实现数据源切换
自定义注解@DataSource
,结合AOP实现动态切换:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "db1"; }
AOP切面实现:
@Aspect @Component public class DataSourceAspect { @Before("@annotation(dataSource)") public void setDataSource(DataSource dataSource) { DataSourceContextHolder.setDataSourceType(dataSource.value()); } @After("@annotation(dataSource)") public void clearDataSource(DataSource dataSource) { DataSourceContextHolder.clearDataSourceType(); } }
在Service方法上使用注解:
@Service public class UserService { @DataSource("db1") public void insertUser(User user) { // 操作db1 } @DataSource("db2") public void insertLog(Log log) { // 操作db2 } }
多数据源事务管理
多数据源事务需要使用JtaTransactionManager
或编程式事务,以下是基于JtaTransactionManager
的配置:
添加JTA依赖(如Atomikos或Bitronix):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency>
配置JTA事务管理器:
<bean id="jtaTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"/>
在Service方法上添加
@Transactional
注解,确保多个数据源操作在同一个事务中:@Transactional public void transferData() { DataSourceContextHolder.setDataSourceType("db1"); userMapper.insert(user); // db1操作 DataSourceContextHolder.setDataSourceType("db2"); logMapper.insert(log); // db2操作 }
注意事项
- 线程安全:
DataSourceContextHolder
使用ThreadLocal
确保线程隔离。 - 事务一致性:跨数据库事务需保证所有数据库支持XA协议,或采用最终一致性方案。
- 性能影响:频繁切换数据源可能影响性能,建议按模块划分数据源。
- Mapper隔离:不同数据源的Mapper接口和XML文件需严格分开,避免冲突。
相关问答FAQs
Q1: 如何在同一个Service方法中同时操作多个数据库?
A: 可以通过手动切换数据源实现,在Service方法中,先调用DataSourceContextHolder.setDataSourceType("db1")
执行第一个数据库操作,再切换为"db2"
执行第二个数据库操作,若需事务一致性,需结合JTA事务管理器,确保所有操作在同一个事务中提交或回滚。
Q2: 多数据源配置下,MyBatis的Mapper扫描如何避免冲突?
A: 为每个数据源配置独立的SqlSessionFactory
和MapperScannerConfigurer
,并通过basePackage
指定不同的Mapper接口包路径。db1
的Mapper放在com.example.mapper.db1
,db2
的Mapper放在com.example.mapper.db2
,确保XML文件和接口包路径一一对应,避免扫描混乱。
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复