1.先在database.properties 配置文件中添加两个数据库链接(这里是作者的文件名,根据自身项目而定)
orcl.driver=oracle.jdbc.OracleDriver
orcl.url=jdbc:oracle:thin:@localhost:1521:orcl
orcl.user=scott
orcl.pwd=123456
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/appinfodb?useUnicode=true&characterEncoding=utf-8
user=root
password=root
2.修改我们的applicationContext-mybatis.xml文件(文件名根据自身项目而定)
//连接oracle 的数据源
<context:property-placeholder location="classpath:database.properties" />
<!-- 获取数据源 使用BasicDataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" scope="singleton">
<property name="driverClassName" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.user}" />
<property name="password" value="${orcl.pwd}" />
</bean>
//连接MySQL的数据源
<bean id="dataSource_MySql" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" scope="singleton">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 下面的是切换数据库的自定义类 -->
//这里的class文件是后面进行创建的
<bean id="multipleDataSource" class="cn.appsys.database.DynamicDataSource">
<property name="defaultTargetDataSource" ref="dataSource_MySql"></property>
<property name="targetDataSources">
<map>
<entry key="dataSource_MySql" value-ref="dataSource_MySql"></entry>
<entry key="dataSource" value-ref="dataSource"></entry>
</map>
</property>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"/>
</bean>
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations">
<list>
<value>classpath:cn/appsys/dao/**/*.xml</value>
</list>
</property>
</bean>
<!-- 扫描mapper映射文件 MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="cn.appsys.dao"/>
</bean>
3.创建类指定配置使用哪个数据库
public class DBContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
public static String getDbType() {
return ((String) contextHolder.get());
}
public static void clearDbType() {
contextHolder.remove();
}
}
4.定义动态数据源说明
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey(){
return DBContextHolder.getDbType();
}
}
5.最后一步在我们的Serviceimpl层为我们的方法进行标注为那个数据源进行操作
@Service("devservice")
public class DevUserServiceImpl implements DevUserService{
@Resource(name="devUserdao")
private DevUserDao devuserdao;
/**
* 登录方法
*/
@Override
public DevUser devlogin(String devCode, String devPassword) {
// TODO Auto-generated method stub
//这里选中为oracle数据库进行操作
DBContextHolder.setDbType("dataSource");
return devuserdao.devlogin(devCode, devPassword);
}
问题:
进行数据源切换配置运行成功后,我们数据源切换的Service层加入事务控制,发现此时数据源切换失败,dao只会访问默认的数据源。出现这个问题的原因,我在网上找了一下:
- AOP可以触发数据源字符串的切换,这个没问题
- 数据源真正切换的关键是 AbstractRoutingDataSource 的 determineCurrentLookupKey() 被调用,此方法是在open connection时触发。
- 事务是在connection层面管理的,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改
也就是说将数据源切换和事务处理都放在Service层,则数据源切换时失效的。
这时候就说一下,配置文件中配置的事务切面。如下:
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<!-- <bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> -->
<!-- 配置事务通知属性 -->
<!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> -->
<!-- 定义事务传播属性 -->
<!-- <tx:attributes> -->
<!-- 不配方法将只读,但read-only默认非只读false。注解方式配置有什么区别?TODO 复习事务相关知识 -->
<!-- <tx:method name="regist*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="new*" propagation="REQUIRED" />
<tx:method name="set*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="change*" propagation="REQUIRED" />
<tx:method name="check*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" read-only="true" />
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="load*" propagation="REQUIRED" read-only="true" />
<tx:method name="*" propagation="REQUIRED"/> -->
<!-- </tx:attributes>
</tx:advice> -->
<!-- 配置事务切面 -->
<!-- <aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* com.cems.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />
</aop:config> -->
这就会导致到service层,检测到方法名带有“find”,“load”等字样的方法会进入事务,一旦进入事务除非整个事务结束,若是在中途中切换数据源也是无效的。这是因为当我们配置了事物管理器和拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存一些东西:DataSource、SqlSessionFactory、Connection等,所以,我们在外面再怎么设置要求切换数据源也没用,因为Conneciton都是从缓存中拿的,所以我们要想能够顺利的切换数据源,实际就是能够动态的根据DatabaseType获取不同的Connection,并且要求不能影响整个事物的特性。
1,在dao实现事务控制:此种方式不太合理,在于再dao层加入事务控制,无法保证一个方法内的事务的一致性
2.将数据源切换和事务开启按顺序进行,先切换,再开启事务。如下所示:比如在service层中方法名不带有这些“find”,“load”等这些字样,这样就不会进入事务,搜索的过程中进行数据源的切换也没有问题。若执行插入,修改的操作还是要进入事务,以防止数据的正确性受到影响。
3.重写MultiDataSourceTransaction事务