背景
需求是这样,项目中有两个数据库,但是中间有一个商户的配置表需要在从A项目同步到B项目,原先这些是通过接口的形式来完成,但是考虑到出现新增字段后者修改字段需要同时修改接口提供端以及接口调用端。
这里采用在同一个项目中采用定时任务去调用两个数据库直接完成从A库到B库的数据复制。
这里我使用的是Spring以及Mybatis来完成。
设计思想
首先得配置两个数据源,在特定的方法调用时手动指定该方法访问的数据源。
由于mybatis访问数据源的方法都已经进了封装,这里我采用继承后重写设置数据源的方法,让它能按我的需求使用不同的数据源。
下面的例子我们能看到,实际的DaoImpl类中,获取sqlSession是通过继承的父类(AbstractBaseMyBatisDao)的getSqlSession方法来实现。
进入到父类(AbstractBaseMyBatisDao)我们可以看到,这是一个抽象类,内部获取sqlSession的方法也是来自他的父类 在mybatis-dao-framework.jar中的类(SqlSessionDaoSupport)
到这里(SqlSessionDaoSupport.java),一目了然,需要执行的sql使用哪个数据源,是通过setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)这个方法指定的。
此时思路就很清晰了,我只需要让我需要访问数据库的方法在setSqlSessionFactory时就根据我的需要设置特定的数据源。
这里有两种方式:
1、重写setSqlSessionFactory让他按我们需要的方式进行set操作
2、直接重写getSqlSession,指定我需要返回的sqlSession(sqlSession需要数据源)
第一种有个点需要注意,如果只是继承SqlSessionDaoSupport.java你会发现想要重写这个setSqlSessionFactory方法是不行的,因为这个方法是final的无法被重写。需要重新定义一个类来替代SqlSessionDaoSupport.java并将setSqlSessionFactory方法定义成非final。
第二种同理,因为getSqlSession方法被设置成final这里要重写 需要修改成非final。
这里我直接重新定义了一个名为DynamicDaoSupport的类,该类直接继承自DaoSupport并实现其中的所有方法,唯一的不同点就是setSqlSessionFactory方法以及getSqlSession方法被定义成了非final的,这样我在外部调用时就可以重写这个方法,按我的需要set指定的SqlSessionFactory。
下面是具体实现代码:
首先我们得定义两个数据源,并通过org.springframework.beans.factory.config.MethodInvokingFactoryBean
将sqlSessionFactory注册到指定的类中,这样我在处理类中就能拿到sqlSessionFactory的bean。这里通过targetObject指定需要注入到哪个类,通过targetMethod找到对应类的对应方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- myBatis分页bean -->
<bean id="paginationInterceptor"
class="com.smartpay.its.boss.framework.repository.mybatis.pagination.interceptor.PaginationInterceptor"></bean>
<!-- sqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 这里可以省略,采用spring配置方式 <property name="configLocation" value="classpath:mybatis/conf/mybatis-config.xml"
/> -->
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
<property name="typeAliasesPackage" value="com.smartpay.ops.boss.task" />
<!-- 显式指定Mapper文件位置 -->
<property name="mapperLocations" value="classpath*:mybatis/bm/**/*Mapper.xml" />
<!-- 这里进行配置mybatis拦截器插件,在这里我已经通过注解方式封装了,因为个人感觉拦截器的模式不够灵活,还影响性能 -->
<property name="plugins">
<array>
<ref bean="paginationInterceptor" />
</array>
</property>
<property name="configurationProperties">
<props>
<prop key="dialect">oracle</prop>
</props>
</property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!-- MSC库 mscdbSqlSessionFactory配置 -->
<bean id="mscdbSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--这里可以省略,采用spring配置方式 <property name="configLocation" value="classpath:mybatis/conf/mybatis-config.xml"-->
<property name="dataSource" ref="dataSourceMscdb" />
<!--自动扫描entity目录, 省掉Configuration.xml里的手工配置-->
<property name="typeAliasesPackage" value="com.smartpay.ops.boss.task" />
<!--显式指定Mapper文件位置-->
<property name="mapperLocations" value="classpath*:mybatis/mscdb/**/*Mapper.xml" />
<!--这里进行配置mybatis拦截器插件,在这里我已经通过注解方式封装了,因为个人感觉拦截器的模式不够灵活,还影响性能-->
<property name="plugins">
<array>
<ref bean="paginationInterceptor" />
</array>
</property>
<property name="configurationProperties">
<props>
<prop key="dialect">oracle</prop>
</props>
</property>
</bean>
<bean id="dynamicSqlSessionDaoSupport" class="com.smartpay.ops.boss.task.util.DynamicSqlSessionDaoSupport" lazy-init="true"></bean>
<!-- 把SqlSessionFactory注册到SqlSessionDaoSupport中-->
<bean id="orderCreatorRegister" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject">
<ref local="dynamicSqlSessionDaoSupport" />
</property>
<property name="targetMethod">
<value>setDefaultFactory</value>
</property>
<property name="arguments">
<ref bean="mscdbSqlSessionFactory" />
</property>
</bean>
</beans>
我们在DynamicSqlSessionDaoSupport中定义了一个静态的map用于接收注入的bean
package com.smartpay.ops.boss.task.util;
import static org.springframework.util.Assert.notNull;
import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.smartpay.ops.boss.task.system.impl.AutoTaskServiceImpl;
/**
* 重写原始的sqlSession获取sqlSessionFactory的方式
* @author jugg
*
*/
public class DynamicSqlSessionDaoSupport extends DynamicDaoSupport {
final static Loggerlogger = LoggerFactory.getLogger(AutoTaskServiceImpl.class);
private static Map<String, SqlSessionFactory> targetSqlSessionFactorys=new HashMap<String, SqlSessionFactory>();
private SqlSessionFactory defaultTargetSqlSessionFactory;
private SqlSession sqlSession;
/**
* 通过spring注册数据源sqlSessionFactory
* @param sqlSessionFactory
*/
public void setDefaultFactory(DefaultSqlSessionFactory sqlSessionFactory) {
defaultTargetSqlSessionFactory=sqlSessionFactory;
if(defaultTargetSqlSessionFactory==null){
logger.info("注入的sqlSessionFactory为空");
}else{
targetSqlSessionFactorys.put("msc", defaultTargetSqlSessionFactory);
}
}
@Override
public final SqlSession getSqlSession() {
if (targetSqlSessionFactorys != null) {
SqlSessionFactory sessionFactory=targetSqlSessionFactorys.get("msc");
this.sqlSession = SqlSessionUtils.getSqlSession(sessionFactory);
return this.sqlSession;
}else{
logger.error("获取sqlSessionFactory数据源失败!");
return null;
}
}
@Override
protected void checkDaoConfig() {
//Assert.notNull(getSqlSession(), "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
}
}
上面的代码,getSqlSession没有直接像父类那样返回this.sqlSession。而是自己通过SqlSessionUtils根据指定的sessionFactory生成出sqlSession。
这里有一点。
下面是实际调用端的写法:
在sql需要执行的时候,getSession方法实际已经进入到父类DynamicBaseMybatisDao的getSqlSession。流程图如下:
测试验证:
上下两个findByPKId方法调用两个daoImpl类,这两个类的区别就是一个使用原生的AbstractBaseMyBatisDao一个使用我们自己定义的DynamicBaseMybatisDao。原生的访问了xml中配置的sqlSession,而自定义的类访问的是我们通过spring注入的另一个数据源mscdbSqlSessionFactory
我们看到调用findById方法查询出两个库里的结果,结果显而易见!