Spring+Mybatis多数据源的实现

浅谈Spring事务隔离级别:[url]http://www.cnblogs.com/yangy608/archive/2011/06/29/2093478.html[/url]
spring里面事务的传播属性和事务隔离级别 :[url]http://blog.csdn.net/it_man/article/details/5074371[/url]
[size=medium]第一种方法:创建两个会话工厂[/size]
配置如下
<beans>
<!-- 数据源1 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 数据库基本信息配置 -->
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="driverClassName" value="${driverClassName}" />
<property name="filters" value="${filters}" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="${maxActive}" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="${initialSize}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 数据源2 -->
<bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${sync.url}" />
<property name="username" value="${sync.username}" />
<property name="password" value="${sync.password}" />
<property name="driverClassName" value="${sync.driverClassName}" />
<property name="filters" value="${filters}" />
<property name="maxActive" value="${maxActive}" />
<property name="initialSize" value="${initialSize}" />>
<property name="maxWait" value="${maxWait}" />
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 第一个sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<!-- mapper扫描 -->
<property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
</bean>

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory" />
</bean>
<!-- 第二个sqlSessionFactory -->
<bean id="syncSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="syncDataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mybatis/sync/*.xml"></property>
</bean>
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="syncSqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="syncSqlSessionFactory" />
</bean>
<!-- <aop:aspectj-autoproxy proxy-target-class="true" /> -->
</beans>

这种方法,在程序中只需要切换sqlSessionTemplate即可,但是这样第二数据源无事务,
容易抛出TransactionSynchronizationManager.unbindResourceIfPossible异常,这也是其缺点
是否可以再添加一个事务管理器?
<bean name="syncTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   
<property name="dataSource" ref="syncDataSource"></property>
</bean>

你可以试试,我测试没问题。
jdbc.properties配置文件
url=jdbc\:mysql\://localhost\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8 
driverClassName=com.mysql.jdbc.Driver
username=donald
password=123456

#sync datasource
sync.url=jdbc\:mysql\://192.168.32.128\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8
sync.driverClassName=com.mysql.jdbc.Driver
sync.username=donald
sync.password=123456


[size=medium] 第二种方法:扩展数据源路由[/size]
查看AbstractRoutingDataSource
//抽象数据源路由
public abstract class AbstractRoutingDataSource extends AbstractDataSource
implements InitializingBean
{

public void afterPropertiesSet()
{
if(targetDataSources == null)
throw new IllegalArgumentException("Property 'targetDataSources' is required");
resolvedDataSources = new HashMap(targetDataSources.size());
Object lookupKey;
DataSource dataSource;
//将配置的多数据源添加到resolvedDataSources中
for(Iterator iterator = targetDataSources.entrySet().iterator(); iterator.hasNext(); resolvedDataSources.put(lookupKey, dataSource))
{
java.util.Map.Entry entry = (java.util.Map.Entry)iterator.next();
lookupKey = resolveSpecifiedLookupKey(entry.getKey());
dataSource = resolveSpecifiedDataSource(entry.getValue());
}

if(defaultTargetDataSource != null)
//默认数据源
resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource);
}
//数据源key
protected Object resolveSpecifiedLookupKey(Object lookupKey)
{
return lookupKey;
}
//获取数据源根据dataSource
protected DataSource resolveSpecifiedDataSource(Object dataSource)
throws IllegalArgumentException
{
if(dataSource instanceof DataSource)
return (DataSource)dataSource;
if(dataSource instanceof String)
//从bean容器中获取对应的数据源,(DataSource)beanFactory.getBean(dataSourceName, javax/sql/DataSource);
// in BeanFactoryDataSourceLookup.getDataSource(String dataSourceName)
return dataSourceLookup.getDataSource((String)dataSource);
else
throw new IllegalArgumentException((new StringBuilder()).append("Illegal data source value - only [javax.sql.DataSource] and String supported: ").append(dataSource).toString());
}
//获取连接
public Connection getConnection()
throws SQLException
{
//再看determineTargetDataSource
return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource()
{
//获取当前数据源名,这里是关键
Object lookupKey = determineCurrentLookupKey();
//获取当前数据源
DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey);
if(dataSource == null && (lenientFallback || lookupKey == null))
//如果dataSource为空,则dataSource为默认的数据源resolvedDataSources
dataSource = resolvedDefaultDataSource;
if(dataSource == null)
throw new IllegalStateException((new StringBuilder()).append("Cannot determine target DataSource for lookup key [").append(lookupKey).append("]").toString());
else
return dataSource;
}
//determineCurrentLookupKey方法,为抽象方法,待子类扩展,这是不是给了我们一种思路
protected abstract Object determineCurrentLookupKey();
private Map targetDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource
private Object defaultTargetDataSource;
private boolean lenientFallback;
private DataSourceLookup dataSourceLookup;
private Map resolvedDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource
private DataSource resolvedDefaultDataSource;
}

从分析AbstractRoutingDataSource获取数据源得出,想要实现多数据源,只需要扩展AbstractRoutingDataSource,并实现determineCurrentLookupKey方法即可,并在determineCurrentLookupKey方法中切换数据源名即可。
下面实验:
//数据源路由
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态切换数据源
* @author donald
*
*/
public class MultipleRoutingDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}

}
//这里我们创建一个数据源上下文句柄,以便切换数据源
/**
* 数据源上下文
* @author donald
*
*/
public class DataSourceContextHolder {
public final static String DATA_SOURCE_LOCAL = "dataSource";
public final static String DATA_SOURCE_SYNC = "syncDataSource";
//对数据源名,线程隔离
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static void setDataSourceType(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}

//配置如下
<beans>
<!-- 数据源1 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 数据库基本信息配置 -->
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="driverClassName" value="${driverClassName}" />
<property name="filters" value="${filters}" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="${maxActive}" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="${initialSize}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 数据源2 -->
<bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${sync.url}" />
<property name="username" value="${sync.username}" />
<property name="password" value="${sync.password}" />
<property name="driverClassName" value="${sync.driverClassName}" />
<property name="filters" value="${filters}" />
<property name="maxActive" value="${maxActive}" />
<property name="initialSize" value="${initialSize}" />>
<property name="maxWait" value="${maxWait}" />
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 数据源路由 -->
<bean id="multipleDataSource" class="com.dataSource.MultipleRoutingDataSource">
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="dataSource"/>
<!-- 目标数据源 -->
<property name="targetDataSources">
<map>
<!-- 注意这里的value是和上面的DataSource的id对应,key要和
下面的DataSourceContextHolder中的常量对应 -->
<entry value-ref="dataSource" key="dataSource"/>
<entry value-ref="syncDataSource" key="syncDataSource"/>
</map>
</property>
</bean>
<!-- 配置mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 这里为multipleDataSource,可以统一管理事务 -->
<property name="dataSource" ref="multipleDataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<!-- mapper扫描 -->
<property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory" />
</bean>
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"></property>
</bean>
<!-- <aop:aspectj-autoproxy proxy-target-class="true" /> -->
</beans>

手动测试
@Controller
@RequestMapping(value="/test")
public class TestController extends BaseController{
private static Logger log = LoggerFactory.getLogger(TestController.class);
@Resource(name = "daoSupport")
private DaoSupport dao;

@SuppressWarnings("unchecked")
@RequestMapping("/db")
public void testDbSource(HttpServletResponse response) throws IOException {
log.debug("=======Into testDbSource==============");
PageData pd = this.getPageData();
try {
DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);
List<PageData> lpd = (List<PageData>) dao.findForList("test.list", pd);
log.info("=============localDao size:"+lpd.size()+","+DataSourceContextHolder.getDataSourceType());
DataSourceContextHolder.clearDataSourceType();
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
try {
DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_SYNC);
List<PageData> lpdTest = (List<PageData>) dao.findForList("test.list", pd);
log.info("=============syncDao size:"+lpdTest.size()+","+DataSourceContextHolder.getDataSourceType());
DataSourceContextHolder.clearDataSourceType();
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
response.getWriter().write("test");
}
}

访问http://localhost:8080/r/test/db.do控制台输出
2016-09-21 17:57:13 -40921 [com.controller.test.TestController] INFO - =============localDao size:5,dataSource
2016-09-21 17:57:13 -40941 [com.controller.test.TestController] INFO - =============syncDao size:4,syncDataSource
上面的方式是手动切换数据源,下面我们通过Spring AOP实现动态切换数据源:
定义注解
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbSource {

String value() default "";

}

定义AOP
/**
* 根据DAO的DbSource的值,动态切换数据源
* @author donald
*
*/
@Aspect
@Component
public class DataSourceAspect {

private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
//定义切点
@Pointcut("@annotation(com.fh.dataSource.DbSource)")
public void dbSourceAspect() {}

/* @Before("dbSourceAspect()")
public void doBefore(JoinPoint joinPoint) {
try {
System.out.println("Dao-class:" + (joinPoint.getTarget().getClass().getName()));
System.out.println("DataSource:" + getDbSourceValue(joinPoint));
} catch (Exception e) {
// 记录本地异常日志
logger.error("exception", e.getMessage());
}
}*/
/**
* 切换数据源
* @param joinPoint
*/
/*有参数的处理
Object[] args = joinPoint.getArgs();
if(args != null && args.length > 1)
{
log.info("==============Around-args:"+JsonUtil.toJson(args));

obj = joinPoint.proceed(args);
}
else{
obj = joinPoint.proceed();
}*/
@Around("dbSourceAspect()")
public void doAround(ProceedingJoinPoint joinPoint) {
try {
log.info("=============Dao-class:" + (joinPoint.getTarget().getClass().getName()));
log.info("=============DataSource:" + getDbSourceValue(joinPoint));
joinPoint.proceed();
DataSourceContextHolder.clearDataSourceType();
}
catch(Throwable e){
log.error("=============Throwable:", e.getMessage());
}
}
/**
* 切换数据源,当注解方法有返回值的处理情况
* @param joinPoint
*/
/*
@Around("dbSourceAspect()")
public Object doAround(ProceedingJoinPoint joinPoint) {
//切入方法返回对象
Object obj = null;
try {
log.debug("=============class:" + (joinPoint.getTarget().getClass().getName()));
log.debug("=============DataSource:" + getDbSourceValue(joinPoint));
obj = joinPoint.proceed();
}
catch(Throwable e){
log.error("=============Throwable:", e.getMessage());
e.printStackTrace();
}
finally{
DataSourceContextHolder.clearDataSourceType();
}
return obj;
} */
/**
* 获取数据源id
* @param joinPoint
* @return
* @throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static String getDbSourceValue(JoinPoint joinPoint) throws Exception {
//根据连接点获取class
String targetName = joinPoint.getTarget().getClass().getName();
Class targetClass = Class.forName(targetName);
// DbSource dbSource = (DbSource) targetClass.getAnnotation(DbSource.class);
//根据连接点获取method
String methodName = joinPoint.getSignature().getName();
//根据连接点获取args
Object[] arguments = joinPoint.getArgs();
Method[] methods = targetClass.getMethods();
String dbId ="";
//获取注解连接点的值
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
dbId = method.getAnnotation(DbSource.class).value();
if(!StringUtils.isBlank(dbId)){
DataSourceContextHolder.setDataSourceType(dbId);
}
else{
dbId = DataSourceContextHolder.DATA_SOURCE_LOCAL;
DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);
}
break;
}
}
}
return dbId;
}
}

测试
@Controller
@RequestMapping(value="/test")
public class TestController extends BaseController{
private static Logger log = LoggerFactory.getLogger(TestController.class);
@Resource(name = "daoSupport")
private DaoSupport dao;
@SuppressWarnings("unchecked")
@RequestMapping("/db1")
@DbSource(DataSourceContextHolder.DATA_SOURCE_LOCAL)
public void testDbSource1(HttpServletResponse response) throws IOException {
log.info("=======Into testDbSource1==============");
PageData pd = this.getPageData();
try {
List<PageData> lpd = (List<PageData>) dao.findForList("test.list", pd);
log.info("=============localDao size:"+lpd.size()+","+DataSourceContextHolder.getDataSourceType());
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
response.getWriter().write("db1");
}
@SuppressWarnings("unchecked")
@RequestMapping("/db2")
@DbSource(DataSourceContextHolder.DATA_SOURCE_SYNC)
public void testDbSource2(HttpServletResponse response) throws IOException {
log.info("=======Into testDbSource2==============");
PageData pd = this.getPageData();
try {
List<PageData> lpdTest = (List<PageData>) dao.findForList("test.list", pd);
log.info("=============synDao size:"+lpdTest.size()+","+DataSourceContextHolder.getDataSourceType());
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
response.getWriter().write("db2");
}
}

访问http://localhost:8080/r/test/db1.do,控制台输出
2016-09-21 17:58:12 -99217 [com.dataSource.DataSourceAspect] INFO - =============Dao-class:com.controller.test.TestController
2016-09-21 17:58:12 -99220 [com.dataSource.DataSourceAspect] INFO - =============DataSource:dataSource
2016-09-21 17:58:12 -99220 [com.controller.test.TestController] INFO - =======Into testDbSource1==============
2016-09-21 17:58:12 -99225 [com.controller.test.TestController] INFO - =============localDao size:5,dataSource
访问http://localhost:8080/r/test/db2.do,控制台输出
2016-09-21 17:58:16 -104167 [com.dataSource.DataSourceAspect] INFO - =============Dao-class:com.controller.test.TestController
2016-09-21 17:58:16 -104167 [com.dataSource.DataSourceAspect] INFO - =============DataSource:syncDataSource
2016-09-21 17:58:16 -104168 [com.controller.test.TestController] INFO - =======Into testDbSource2==============
2016-09-21 17:58:17 -104180 [com.controller.test.TestController] INFO - =============synDao size:4,syncDataSource
至此动态切换成功,
[color=blue]从上面可以看出,SqlSessionFactoryBean的获取数据源是通过数据源路由,
我们通过扩展数据源路由来实现,动态切换数据源。[/color]
注意:
[color=red]当动态数据源切换失败时,查看是不是事务因素,要先理解事务,例如你的事务是放在service...,你切换数据库时为了保证事务的完整性,你应该在进入service之前切换掉数据源,因为如果你是在service方法中切换数据源那是不可行的,因为这个时候spring已经打开了一个事务,他会阻止你切换,所以你应在这之前切换,然后进入service方法,.这样spring又给你新切换的数据源加上事务了[/color]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值