最近项目中需要接入多个数据源,起初准备让 Mybatis 来进行数据源的动态选择,但查询 Mybatis 的相关文档后,未发现官方对多数据源的支持,并且我的项目中 Mybatis 的使用采用全注解的方式,如果实例化两个 SqlSessionFactory,在全注解使用 Mybatis 的情况下,无法显式指定某个 Mapper 使用哪一个 sqlSessionFactory。
继续查询相关资料,发现 Spring 自 2.0.1 开始就提供了对动态数据源的支持,参见 Dynamic DataSource Routing ,咳咳,由于文章历史悠久,追溯至 2007年 ,所以在项目中我对其进行了改进,思路依然与原文基本一致,只不过把文中基于 XML 的相关配置改为了我们项目中的基于 Java Code 的配置。
定义多个数据源的枚举,本例中仅包含两个数据源
1
2
3
4
5
6
7/**
* Created by Poison on 8/15/2016.
*/
enum DataSource {
FIRST,
SECOND
}定义 DataSourceContextHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* Created by Poison on 8/15/2016.
*/
class DataSourceContextHolder {
private static ThreadLocal<DataSource> contextHolder = new ThreadLocal<>();
static void setDataSource(DataSource dataSource) {
contextHolder.set(dataSource);
}
static DataSource getDataSource() {
return contextHolder.get();
}
static void restoreToFirstDataSource() {
contextHolder.set(DataSource.FIRST);
}
}定义 RoutingDataSource
1
2
3
4
5
6
7
8
9
10
11/**
* Created by Poison on 8/15/2016.
*/
class RoutingDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}Datasource 相关配置
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
70
71
72
public javax.sql.DataSource dataSourceForFirst() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDataSourceClassName(getProperty("jdbc.dataSourceClassName"));
hikariConfig.setUsername(getProperty("jdbc.username"));
hikariConfig.setPassword(getProperty("jdbc.password"));
hikariConfig.setMinimumIdle(Integer.parseInt(getProperty("jdbc.minimumIdle")));
hikariConfig.setMaximumPoolSize(Integer.parseInt(getProperty("jdbc.maximumPoolSize")));
hikariConfig.addDataSourceProperty("serverName", getProperty("jdbc.serverName"));
hikariConfig.addDataSourceProperty("port", getProperty("jdbc.port"));
hikariConfig.addDataSourceProperty("databaseName", getProperty("jdbc.databaseName"));
optimizeHikariConfigForMysql(hikariConfig);
return new HikariDataSource(hikariConfig);
}
private void optimizeHikariConfigForMysql(HikariConfig hikariConfig) {
hikariConfig.addDataSourceProperty("cachePrepStmts", true);
hikariConfig.addDataSourceProperty("prepStmtCacheSize", 250);
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
}
public javax.sql.DataSource dataSourceForSecond() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDataSourceClassName(env.getProperty("jdbc.second.dataSourceClassName"));
hikariConfig.setUsername(getProperty("jdbc.second.username"));
hikariConfig.setPassword(getProperty("jdbc.second.password"));
hikariConfig.setMinimumIdle(Integer.parseInt(env.getProperty("jdbc.second.minimumIdle")));
hikariConfig.setMaximumPoolSize(Integer.parseInt(env.getProperty("jdbc.second.maximumPoolSize")));
hikariConfig.addDataSourceProperty("serverName", getProperty("jdbc.second.serverName"));
hikariConfig.addDataSourceProperty("port", getProperty("jdbc.second.port"));
hikariConfig.addDataSourceProperty("databaseName", getProperty("jdbc.second.databaseName"));
optimizeHikariConfigForMysql(hikariConfig);
return new HikariDataSource(hikariConfig);
}
public javax.sql.DataSource dataSource() {
RoutingDataSource routingDataSource = new RoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSource.FIRST, dataSourceForFirst());
targetDataSources.put(DataSource.SECOND, dataSourceForSecond());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(dataSourceForFirst());
return routingDataSource;
}
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setTypeAliasesPackage("me.tianshuang.domain");
sessionFactory.setTypeHandlers(new TypeHandler[]{new LocalDateTimeTypeHandler()});
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}AOP 相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* Created by Poison on 8/15/2016.
*/
public class AroundDataSource {
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
DataSourceContextHolder.setDataSource(DataSource.SECOND);
Object result = pjp.proceed();
DataSourceContextHolder.restoreToFirstDataSource();
return result;
}
}
在对数据源进行路由的控制这方面,采用了 AOP 的思想,在我们的项目中,因为第二个数据源用的频率很低,所以为需要操作第二个数据源的 Mapper 单独建立了一个包 (me.tianshuang.dao.second) ,在此包下的所有 Mapper 的方法在执行前将切换数据源为 SECOND ,执行完方法后又切回数据源 First 。
以上只是切换数据源的一种方案,本文的关键就在于 RoutingDataSource ,而在哪里切换数据源,读者完全可根据自身项目需要选择最适合的方案,该例只是适合我所在项目的一个例子。