基于注解的Spring多数据源配置和使用

时间:2022-09-23 22:59:38

基于注解的Spring多数据源配置和使用

ADO.NET视频教程需要的加qq:1225462853,备注:程序员学习视频

===================================================================

2016年netty/mina/java nio视频教程java游戏服务器设计教程
互联网架构师教程: http://blog.csdn.net/pplcheer/article/details/71887910

互联网架构师视频课程 Dubbo ActiveMQ spring Netty MongoDB Jvm :http://blog.csdn.net/pplcheer/article/details/72794970
需要的加qq:1225462853,备注:程序员学习视频
其他视频都可以索要(Netty   NET    C++ 等等)
==================================================================


接上一篇Spring整合Mybatis

其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换。
先看一下spring获取数据源的源码:

基于注解的Spring多数据源配置和使用

可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。
因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。
第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

/**
* com.mybatis.common.DynamicDataSource.java
* @author 作者 : pplsunny
* @version 创建时间:2017年7月16日 下午5:35:57
* 类说明
*/

package com.mybatis.common;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* XXXX
*/
public class DynamicDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}

}

第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:

/**
* com.mybatis.common.DataSourceContextHolder.java
* @author 作者 : pplsunny
* @version 创建时间:2017年7月16日 下午5:35:03
* 类说明
*/

package com.mybatis.common;

/**
* XXXX
*/
public class DataSourceContextHolder {
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();
}
}

第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:

<?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:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!-- Spring希望管理所有的业务逻辑组件,等。。。 -->
<context:component-scan base-package="com.mybatis">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<!-- 引入数据库的配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- Spring用来控制业务逻辑。数据源、事务控制、aop -->
<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${orcl.url}"></property>
<property name="driverClass" value="${orcl.driver}"></property>
<property name="user" value="${orcl.username}"></property>
<property name="password" value="${orcl.password}"></property>
</bean>

<!--统一的dataSource -->
<bean id="dynamicDataSource" class="com.mybatis.common.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--通过不同的key决定用哪个dataSource -->
<entry value-ref="dataSource1" key="dataSource1"></entry>
<entry value-ref="dataSource2" key="dataSource2"></entry>
</map>
</property>
<!--设置默认的dataSource -->
<property name="defaultTargetDataSource" ref="dataSource1">
</property>
</bean>
<!-- spring事务管理 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- <property name="dataSource" ref="dataSource"></property> -->
<property name="dataSource" ref="dynamicDataSource" />
</bean>

<!-- 开启基于注解的事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

<!-- 整合mybatis 目的:1、spring管理所有组件。mapper的实现类。 service==>Dao @Autowired:自动注入mapper;
2、spring用来管理事务,spring声明式事务 -->
<!--创建出SqlSessionFactory对象 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定mybatis全局配置文件的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dynamicDataSource"></property>
<!-- 指定mybatis,mapper文件的位置 -->
<property name="mapperLocations" value="classpath*:com/mybatis/**/*.xml"></property>
</bean>

<!--配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>

<!-- 扫描所有的mapper接口的实现,让这些mapper能够自动注入; base-package:指定mapper接口的包名 -->
<mybatis-spring:scan base-package="com.mybatis.dao" />


</beans>


到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。

示例代码如下:

package com.mybatis.controller;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.mybatis.bean.EmpOracle;
import com.mybatis.bean.Employee;
import com.mybatis.common.DataSourceContextHolder;
import com.mybatis.service.EmployeeService;
import com.mybatis.service.OracleDBService;

@Controller
public class EmployeeController {

@Autowired
EmployeeService mysqlService;

@Autowired
OracleDBService oracleService;

@RequestMapping("/getemps")
public String emps(Map<String, Object> map) {
List<Employee> emps = mysqlService.getEmps();
map.put("allEmps", emps);
return "list";
}


@RequestMapping("/getOracleemps")
public String getOracleEmp(Map<String, Object> map) {
DataSourceContextHolder.setDbType("dataSource2");
List<EmpOracle> emps = oracleService.getEmps();
map.put("allEmps", emps);
return "listOracle";
}

@RequestMapping("/dbtest")
public String updateDb(Map<String, Object> map) {
Employee emp = new Employee();
emp.setId(2);
emp.setLastName("mysqlName");
int re1 = mysqlService.updateEmpt(emp);

DataSourceContextHolder.setDbType("dataSource2");
EmpOracle orac = new EmpOracle();
orac.setEmpno("7788");
orac.setEname("oracleName");
int re2 = oracleService.updateEmpt(orac);

map.put("dbtest", re1 + re2);
return "dbtest";
}
}

------------------------------华丽的分割线------------------------------------

但是问题来了,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置^_^。
首先,我们得定义一个名为DataSource的注解,代码如下:

 @Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface DataSource {
String value();
}

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

public class DataSourceAspect {

/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
public void intercept(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
}

/**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
} catch (Exception e) {
System.out.println(clazz + ":" + e.getMessage());
}
}

}
最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

<bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<!-- 拦截所有service方法 -->
<aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
</bean>

OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

@Service
// 默认DataServiceImpl下的所有方法均访问数据源1
@DataSource("dataSource1")
public class DataServiceImpl implements DataService {
@Autowired
private DataMapper dataMapper;

@Override
public List<Map<String, Object>> getList1() {
// 不指定,则默认使用数据源1
return dataMapper.getList1();
}

@Override
// 覆盖类上指定的,使用数据源2
@DataSource("dataSource2")
public List<Map<String, Object>> getList2() {
return dataMapper.getList2();
}

@Override
// 覆盖类上指定的,使用数据源3
@DataSource("dataSource3")
public List<Map<String, Object>> getList3() {
return dataMapper.getList3();
}
}

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。