druid多数据源配置+Datasurce动态切换
AbstractRoutingDataSource 数据源动态切换
spring 使用AbstractRoutingDataSource自定义动态数据源时的事务处理, 需要继承spring的AbstractRoutingDataSource定义自己的动态数据源,可以根据需要动态的切换不同数据库的数据源,使用起来非常方便。
1
2
3
4
5
6
7
8
9
10
11
|
public class ChooseDataSource extends AbstractRoutingDataSource {
/**
* 获取与数据源相关的key
* 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
* 在通过determineTargetDataSource获取目标数据源时使用
*/
@Override
protected Object determineCurrentLookupKey() {
return RouteHolder.getRouteKey();
}
}
|
通过容器RouteHolder存储当前线程使用的数据源的key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/**
* 保存当前线程数据源的key
*/
public class RouteHolder {
private static ThreadLocal<String> routeKey = new ThreadLocal<String>();
/**
* 获取当前线程的数据源路由的key
* @return
*/
public static String getRouteKey()
{
String key = routeKey.get();
return key;
}
/**
* 绑定当前线程数据源路由的key
* 在使用完成之后,必须调用removeRouteKey()方法删除
* @param key
*/
public static void setRouteKey(String key)
{
routeKey.set(key);
}
/**
* 删除与当前线程绑定的数据源路由的key
*/
public static void removeRouteKey()
{
routeKey.remove();
}
}
|
使用spring 的aop编程在业务逻辑方法运行前将当前方法使用数据源的key从业务逻辑方法上自定义注解@DataSource中解析数据源key并添加到RouteHolder中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 执行dao方法之前的切面
* 获取datasource对象之前往RouteHolder中指定当前线程数据源路由的key
*
*/
public class DataSourceAspect {
/**
* 在dao层方法之前获取datasource对象之前在切面中指定当前线程数据源路由的key
*/
public void before(JoinPoint point)
{
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
if (classz != null && classz.length > 0 ) {
Method m = classz[ 0 ].getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource. class )) {
DataSource data = m.getAnnotation(DataSource. class );
RouteHolder.setRouteKey(data.value());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
解释:
DataSourceAspect 这个切面类,应该针对的被代理类应该是service的实现类(serviceImpl),因为dao层用的mybatis只有一个dao层的接口,所以放在service上做处理比较好。
业务逻辑方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Named ( "userService" )
public class UserService
{
@Inject
private UserDao userDao;
@DataSource ( "master" )
@Transactional (propagation=Propagation.REQUIRED)
public void updatePasswd( int userid,String passwd)
{
User user = new User();
user.setUserid(userid);
user.setPassword(passwd);
userDao.updatePassword(user);
}
@DataSource ( "slave" )
@Transactional (propagation=Propagation.REQUIRED)
public User getUser( int userid)
{
User user = userDao.getUserById(userid);
System.out.println( "username------:" +user.getUsername());
return user;
}
}
|
注解类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* RUNTIME
* 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
* @author jiangxm
*
*/
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
public @interface DataSource {
String value();
}
|
spring的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
< bean id = "dataSource" class = "com.westone.datasource.DbRouteDataSource" >
< property name = "targetDataSources" >
< map >
<!-- write -->
< entry key = "master" value-ref = "master" ></ entry >
<!-- read -->
< entry key = "slave" value-ref = "slave" ></ entry >
</ map >
</ property >
</ bean >
< bean id = "master" class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName" value = "${jdbc.driverclass}" />
< property name = "url" value = "${jdbc.masterurl}" />
< property name = "username" value = "${jdbc.username}" />
< property name = "password" value = "${jdbc.password}" />
< property name = "maxActive" value = "${jdbc.maxActive}" ></ property >
< property name = "maxIdle" value = "${jdbc.maxIdle}" ></ property >
< property name = "maxWait" value = "${jdbc.maxWait}" ></ property >
</ bean >
< bean id = "slave" class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName" value = "${jdbc.driverclass}" />
< property name = "url" value = "${jdbc.slaveurl}" />
< property name = "username" value = "${jdbc.username}" />
< property name = "password" value = "${jdbc.password}" />
< property name = "maxActive" value = "${jdbc.maxActive}" ></ property >
< property name = "maxIdle" value = "${jdbc.maxIdle}" ></ property >
< property name = "maxWait" value = "${jdbc.maxWait}" ></ property >
</ bean >
< bean id = "sqlSessionTemplate" class = "org.mybatis.spring.SqlSessionTemplate" >
< constructor-arg index = "0" ref = "sqlSessionFactory" />
</ bean >
< bean id = "sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" >
< property name = "configLocation" value = "classpath:config/mybatis/mybatis.cfg.xml" ></ property >
< property name = "dataSource" ref = "dataSource" />
</ bean >
<!-- 配置mapper的映射扫描器 根据包中定义的接口自动生成dao的实现类-->
< bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" >
< property name = "basePackage" value = "com.westone.dao" ></ property >
</ bean >
<!-- 为业务逻辑层的方法解析@DataSource注解 为当前线程的routeholder注入数据源key -->
< bean id = "aspectBean" class = "com.westone.datasource.aspect.DataSourceAspect" ></ bean >
< aop:config >
< aop:aspect id = "dataSourceAspect" ref = "aspectBean" >
< aop:pointcut id = "dataSourcePoint" expression = "execution(public * com.westone.service.*.*(..))" />
< aop:before method = "beforeDaoMethod" pointcut-ref = "dataSourcePoint" />
</ aop:aspect >
</ aop:config >
<!-- 事务管理器配置 -->
< bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
<!-- 开启事务注解驱动 在业务逻辑层上使用@Transactional 注解 为业务逻辑层管理事务-->
< tx:annotation-driven transaction-manager = "transactionManager" />
|
事务管理配置一定要配置在往RouteHolder中注入数据源key之前 否则会报
Could not open JDBC Connection for transaction; nested exception is java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null] 找不到数据源错误。
由此就可以根据方法上的@DataSource(“master”) 注解配置不同的数据源key 使用动态数据源。
解释:
1
|
java.lang.reflect.Method.getAnnotation(Class annotationClass)
|
参数:
annotationClass - Class对象对相应的注解类型,比如Datasource.class 。
返回值:
如果存在于此元素,则返回该元素注解指定的注解对象,否则返回为null
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
public class MethodDemo {
public static void main(String[] args) {
Method[] methods = SampleClass. class .getMethods();
Annotation annotation = methods[ 0 ].getAnnotation(CustomAnnotation. class );
if (annotation instanceof CustomAnnotation){
CustomAnnotation customAnnotation = (CustomAnnotation) annotation;
System.out.println( "name: " + customAnnotation.name());
System.out.println( "value: " + customAnnotation.value());
}
}
}
@CustomAnnotation (name= "SampleClass" , value = "Sample Class Annotation" )
class SampleClass {
private String sampleField;
@CustomAnnotation (name= "getSampleMethod" , value = "Sample Method Annotation" )
public String getSampleField() {
return sampleField;
}
public void setSampleField(String sampleField) {
this .sampleField = sampleField;
}
}
@Retention (RetentionPolicy.RUNTIME)
@interface CustomAnnotation {
public String name();
public String value();
}
|
编译并运行上面的程序,这将产生以下结果
-name: getSampleMethod
value: Sample Method Annotation
1
|
getInterfaces()
|
能够获得这个对象所实现的接口
配置多数据源并实现Druid自动切换
Spring Boot配置多数据源
配置yml文件
这里并没有对spring.datasource配置数据源,因为增加新数据源后,系统会覆盖由spring.datasource自动配置的内容。
这里自定义了两个数据源spring.datasource.cmmi和spring.datasource.zentao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
base:
type: com.alibaba.druid.pool.DruidDataSource
driver- class -name: com.mysql.cj.jdbc.Driver
initialize: true #指定初始化数据源,是否用data.sql来初始化,默认: true
name: cmmi
url: jdbc:mysql: //127.0.0.1:3306/cmmi?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: root
zentao:
type: com.alibaba.druid.pool.DruidDataSource
driver- class -name: com.mysql.cj.jdbc.Driver
initialize: true
name: zentaopro
url: jdbc:mysql: //127.0.0.1:3306/zentaopro?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: root
|
主数据源配置
注意,配置类需要对DataSource、DataSourceTransactionManager、SqlSessionFactory 、SqlSessionTemplate四个数据项进行配置;DataSource类型需要引入javax.sql.DataSource;当系统中有多个数据源时,必须有一个数据源为主数据源,使用@Primary修饰。
@MapperScan对指定dao包建立映射,确保在多个数据源下,自动选择合适的数据源,而在service层里不需要做特殊说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Configuration
@MapperScan (basePackages = "cmmi.dao.base" , sqlSessionTemplateRef = "baseSqlSessionTemplate" )
public class BaseDataSourceConfig {
@Bean (name = "baseDataSource" )
@ConfigurationProperties (prefix = "spring.datasource.base" )
@Primary
public DataSource setDataSource() {
return DataSourceBuilder.create().build();
}
@Bean (name = "baseTransactionManager" )
@Primary
public DataSourceTransactionManager setTransactionManager( @Qualifier ( "baseDataSource" ) DataSource dataSource) {
return new DruidDataSource();
}
@Bean (name = "baseSqlSessionFactory" )
@Primary
public SqlSessionFactory setSqlSessionFactory( @Qualifier ( "baseDataSource" ) DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources( "classpath:mapper/base/*.xml" ));
return bean.getObject();
}
@Bean (name = "baseSqlSessionTemplate" )
@Primary
public SqlSessionTemplate setSqlSessionTemplate( @Qualifier ( "baseSqlSessionFactory" ) SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
|
从数据源配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Configuration
@MapperScan (basePackages = "cmmi.dao.zentao" , sqlSessionTemplateRef = "zentaoSqlSessionTemplate" )
public class ZentaoDataSourceConfig {
@Bean (name = "zentaoDataSource" )
@ConfigurationProperties (prefix = "spring.datasource.zentao" )
public DataSource setDataSource() {
return new DruidDataSource();
}
@Bean (name = "zentaoTransactionManager" )
public DataSourceTransactionManager setTransactionManager( @Qualifier ( "zentaoDataSource" ) DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean (name = "zentaoSqlSessionFactory" )
public SqlSessionFactory setSqlSessionFactory( @Qualifier ( "zentaoDataSource" ) DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources( "classpath:mapper/zentao/*.xml" ));
return bean.getObject();
}
@Bean (name = "zentaoSqlSessionTemplate" )
public SqlSessionTemplate setSqlSessionTemplate( @Qualifier ( "zentaoSqlSessionFactory" ) SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
|
使用dao
这里只需要正常使用dao就可以了,spring会根据数据源配置的映射自动选择相应数据源,而不需要在service做特殊说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Service
public class TestService {
private final ZtUserMapper ztUserMapper;
private final LevelDic levelDic;
@Autowired
public TestService(ZtUserMapper ztUserMapper, LevelDic levelDic) {
this .ztUserMapper = ztUserMapper;
this .levelDic = levelDic;
}
public void test() {
ztUserMapper.selectByPrimaryKey( 1 );
levelDic.setDicId( new Integer( 1 ).byteValue());
}
}
|
日志
o.a.c.c.C.[Tomcat].[localhost].[/cmmi] : Initializing Spring FrameworkServlet ‘dispatcherServlet'
o.s.web.servlet.DispatcherServlet : FrameworkServlet ‘dispatcherServlet': initialization started
o.s.web.servlet.DispatcherServlet : FrameworkServlet ‘dispatcherServlet': initialization completed in 23 ms
com.alibaba.druid.pool.DruidDataSource : {dataSource-1,cmmi} inited
com.alibaba.druid.pool.DruidDataSource : {dataSource-2,zentaopro} inited
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/fragrant_no1/article/details/87803041