SpringMvc项目配置多数据源

SpringMvc项目配置多数据源

1. 首选添加另外的数据源

数据源配置在dbconfig.properties文件

1
2
3
4
5
6
7
8
9
#db1
db1.url=jdbc:mysql://localhost:3306/db1
db1.username=root
db1.password=123456
#
#db2
db2.url=jdbc:mysql://localhost:3306/db2
db2.username=root
db2.password=123456

2.在springmvc配置文件中增加对第二个数据源的支持

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
61
62
63
64
65
66
67
68
69
<!-- 阿里 druid数据库连接池 -->
<!-- 这是第一个数据源 -->
<bean id="db1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 数据库基本信息配置 -->
<property name="url" value="${db1.url}"/>
<property name="username" value="${db1.username}"/>
<property name="password" value="${db1.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}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${validationQuery}"/>
<property name="testWhileIdle" value="${testWhileIdle}"/>
<property name="testOnBorrow" value="${testOnBorrow}"/>
<property name="testOnReturn" value="${testOnReturn}"/>
<property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}"/>
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="${removeAbandoned}"/>
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="${logAbandoned}"/>
<property name="connectionInitSqls" value="set names utf8mb4;"/>
</bean>

<!-- 阿里 druid数据库连接池 -->
<!-- 这是第二个数据源 -->
<bean id="db2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 数据库基本信息配置 -->
<property name="url" value="${db2.url}"/>
<property name="username" value="${db2.username}"/>
<property name="password" value="${db2.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}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${validationQuery}"/>
<property name="testWhileIdle" value="${testWhileIdle}"/>
<property name="testOnBorrow" value="${testOnBorrow}"/>
<property name="testOnReturn" value="${testOnReturn}"/>
<property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}"/>
<!-- 打开removeAbandoned功能 -->
<property name="removeAbandoned" value="${removeAbandoned}"/>
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="${logAbandoned}"/>
<property name="connectionInitSqls" value="set names utf8mb4;"/>
</bean>

这时候项目已经有两个数据源了。但是什么时候选择哪个数据源,我们还要告诉程序,告诉指定哪一个数据源。需要继承spring给我们提供的AbstractRoutingDataSource类。在我们需要动态切换另外的数据源时,可以提供设置指定数据源。但需要确保不同线程中的数据源不互相干扰。这时可以利用ThreadLocal在同一个线程中传递上下文信息,达到多数据源访问的效果。

1
2
3
4
5
6
7
//MultiDataSource.java 
public class MultiDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContentHolder.getDataSource();
}
}

定义枚举类来枚举数据源

1
2
3
4
public enum DBSource {
DB1(),
DB2()
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DatabaseContentHolder {

//用ThreadLocal来设置当前线程使用哪个dataSource
private static final ThreadLocal<DBSource> contextHolder = new ThreadLocal<>();

public static void setDataSource(DBSource DBSource) {
contextHolder.set(DBSource);
}

public static DBSource getDataSource() {
DBSource DBSource = contextHolder.get();
if (DBSource == null)
return DBSource.DB1;
return DBSource;
}

public static void remove() {
contextHolder.remove();
}
}

在spring 配置文件中注册数据源.map内的key需要跟数据源枚举类中定义的值相同,value-ref的值需要跟spring配置中定义的数据源的id相同。

1
2
3
4
5
6
7
8
9
10
11
<!--多数据源配置-->
<bean id="multiDataSource" class="com.***.MultiDataSource">
<property name="targetDataSources">
<map key-type="com.***.DBSource">
<entry value-ref="db1" key="DB1"/>
<entry value-ref="db2" key="DB2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource">
</property>
</bean>

注册好数据源,还要把数据源绑定到mybaits和事务中。

1
2
3
4
5
6
7
8
9
10
11
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multiDataSource"></property>
</bean>

<!-- 配置mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multiDataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
<!-- mapper扫描 -->
<property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
</bean>

这样多数据源就配置完成了。如果不指定数据源,默认使用db1的数据源,如果需要动态切换到db2,则需要手动调用DatabaseContentHolder.setDataSource(DBSource.DB2)来告知这个线程都采用db2的数据源。


3.利用aop和注解实现自动指定。

先定义一个方法注解

1
2
3
4
5
6
7
//Database.java
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Database {
DBSource source() default DBSource.READ_WRITE;
}

然后利用aop,在加入该注解的方法前调用DatabaseContentHolder.setDataSource(DBSource.DB2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Component
public class DatabaseAspect {
@Pointcut("@annotation(com.***.Database)")
public void aspect() {
}


@Before("aspect()")
public void doBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(Database.class)) {
Database database = method.getAnnotation(Database.class);
DatabaseContentHolder.setDataSource(database.source());
}
}

@AfterReturning(pointcut = "aspect()")
public void doAfter(JoinPoint joinPoint) {
DatabaseContentHolder.remove();
}
}

这样,如果方法没有Database注解,就默认调用db1连接,如果添加了@Database(source = DBSource.ONLY_READ)注解则调用db2

-------------本文结束-------------