基于springboot自动装备机制,单数据源的情况下我们通过配置数据库连接信息及引入对应mybatis-starter包,即可完成对数据库的整合,然后开开心心写业务,然而对于业务复杂的应用或者对旧系统进行改造时单应用往往需要集成多个数据源,这个时候数据源的管理就没那么容易了.
常规做法
1、在application.yml配置多个数据源的连接信息,类似这样
spring:
datasource:
minIdle: 10
maxActive: 50
maxLifetime: 180000
idleTimeout: 60000
datasource0:
url: jdbc:mysql://localhost:3339/db01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.jdbc.Driver
datasource1:
url: jdbc:mysql://localhost:3340/db01?useSSL=false&useUnicode=yes&characterEncoding=utf8
username: root
password: 123456
driverClassName: com.mysql.jdbc.Driver
2、配置数据源
@Value("${spring.datasource0.url}")
private String url0;
@Value("${spring.datasource0.username}")
private String username0;
@Value("${spring.datasource0.password}")
private String password0;
@Value("${spring.datasource0.driverClassName}")
private String driverClassName0;
@Value("${spring.datasource1.url}")
private String url1;
@Value("${spring.datasource1.username}")
private String username1;
@Value("${spring.datasource1.password}")
private String password1;
@Value("${spring.datasource1.driverClassName}")
private String driverClassName1;
@Bean("dataSource0")
public DataSource dataSource0() {
return initDataSource(url0,username0,password0,driverClassName0);
}
@Bean("dataSource1")
public DataSource dataSource1() {
return initDataSource(url1,username1,password1,driverClassName1);
}
3、添加各个数据源的mybatis配置,其中还需要不同数据源下mapper文件分开在不同的package下,类似这样
@Configuration
@MapperScan(basePackages = "com.example.dao0" ,sqlSessionFactoryRef="sqlSessionFactory0" )
@EnableTransactionManagement
public class MyBatisConfig0 {
@Autowired
@Qualifier("dataSource0")
private DataSource dataSource;
@Bean("sqlSessionFactory0")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.example.entity");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath*:mapper0/*.xml"));
return bean.getObject();
}
@Bean("platformTransactionManager0")
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
@Configuration
@MapperScan(basePackages = "com.example.dao1" ,sqlSessionFactoryRef="sqlSessionFactory1" )
@EnableTransactionManagement
public class MyBatisConfig1 {
@Autowired
@Qualifier("dataSource1")
private DataSource dataSource;
@Bean("sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.example.entity");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath*:mapper1/*.xml"));
return bean.getObject();
}
@Bean("platformTransactionManager1")
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
优雅配置
常规配置可以解决多数据源的问题,但是也产生了很多限制,例如mapper文件分包,每个datasouce都需要单独的配置信息等.
1、连接信息配置
基于读取json文件及spring FactoryBean将db连接信息放入context上下文中.
json文件
此处可以将公司所有的数据源都加入配置,然后再application.yml中配置具体使用哪几个数据源
{
"mysql01" : {
"dbName" : "db01",
"dbType" : "mysql",
"port" : "3306",
"username" : "root",
"host" : "127.0.0.1",
"password" : "123456"
},
"mysql02" : {
"dbName" : "db02",
"dbType" : "mysql",
"port" : "3306",
"username" : "root",
"host" : "127.0.0.1",
"password" : "123456"
}
}
相关java配置类
@Data
public class DbItem {
@JsonProperty("dbName")
private String dbName;
@JsonProperty("dbType")
private String dbType;
@JsonProperty("host")
private String host;
@JsonProperty("port")
private String port;
@JsonProperty("username")
private String username;
@JsonProperty("password")
private String password;
public String getUrl() {
return String.format(DbConfig.DB_TYPE_MAP.get(dbType).getKey(), host, port, dbName);
}
public String getDriverClassName() {
return DbConfig.DB_TYPE_MAP.get(dbType).getValue();
}
}
public class DbConfig {
public DbConfig() {
}
protected static final Map<String, Pair<String, String>> DB_TYPE_MAP = new HashMap<String, Pair<String, String>>();
static {
//添加mysql连接配置,可以继续添加以支持其它数据库
DB_TYPE_MAP.put("mysql", new Pair("jdbc:mysql://%s:%s/%s?characterEncoding=utf8&useSSL=false",
"com.mysql.jdbc.Driver"));
}
private Map<String, DbItem> dbMap;
public DbConfig(Map<String, DbItem> dbMap) {
super();
this.dbMap = dbMap;
}
public DbItem getDb(String key) {
return dbMap.get(key);
}
}
@Component
public class DbCfgFactoryBean implements FactoryBean<DbConfig> {
@Value("${dbcfg.filepath}")
private String dbcfgpath;
private DbConfig dbConfig;
public DbConfig getObject() throws Exception {
return createDbConfig();
}
public Class<?> getObjectType() {
return DbConfig.class;
}
public boolean isSingleton() {
return true;
}
private DbConfig createDbConfig(){
Reader reader = null;
ResourceLoader loader = new DefaultResourceLoader();
EncodedResource encodedResource = new EncodedResource(loader.getResource(dbcfgpath),"UTF-8");
try{
reader = encodedResource.getReader();
load(reader);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return dbConfig;
}
private void load(Reader reader) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Map<String,DbItem> map = objectMapper.readValue(reader, new TypeReference<Map<String, DbItem>>() {
});
dbConfig = new DbConfig(map);
}
}
2、数据源配置
使用注解+spring动态数据源来实现数据源动态切换
public class DataSourceConstant {
public static final String DEFAULT_DATASOURCE = "dataSource01";
public static final String DATASOURCE_02 = "dataSource02";
}
server:
port: 8082
dbcfg:
filepath: classpath:dbcfg.conf
datasource:
db01: mysql01
db02: mysql02
spring:
datasource:
minIdle: 10
maxActive: 50
maxLifetime: 1800
idleTimeout: 600
@Configuration
public class DataSourceConfig {
@Autowired
private DbConfig dbConfig;
@Value("${spring.datasource.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.maxActive:50}")
private int maxActive;
@Value("${spring.datasource.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.idleTimeout}")
private int idleTimeout;
@Value("${datasource.db02}")
private String dataSource02;
@Value("${datasource.db01}")
private String dataSource01;
@Bean("dataSource01")
public DataSource dataSource01(){
return initDataSource(dataSource01);
}
@Bean("dataSource02")
public DataSource dataSource02(){
return initDataSource(dataSource02);
}
@Bean("multipleDataSource")
public DataSource multipleDataSource(@Qualifier("dataSource01") DataSource dataSource01,
@Qualifier("dataSource02") DataSource dataSource02) {
Map<Object, Object> datasources = new HashMap<Object, Object>();
datasources.put(DataSourceConstant.DEFAULT_DATASOURCE, dataSource01);
datasources.put(DataSourceConstant.DATASOURCE_02, dataSource02);
MultipleDataSource multipleDataSource = new MultipleDataSource();
multipleDataSource.setDefaultTargetDataSource(dataSource01);
multipleDataSource.setTargetDataSources(datasources);
return multipleDataSource;
}
private DataSource initDataSource(String dbName) {
HikariDataSource datasource = new HikariDataSource();
datasource.setJdbcUrl(dbConfig.getDb(dbName).getUrl());
datasource.setUsername(dbConfig.getDb(dbName).getUsername());
datasource.setPassword(dbConfig.getDb(dbName).getPassword());
datasource.setDriverClassName(dbConfig.getDb(dbName).getDriverClassName());
datasource.setMaximumPoolSize(maxActive);
datasource.setMinimumIdle(minIdle);
datasource.setMaxLifetime(maxLifetime);
datasource.setIdleTimeout(idleTimeout);
datasource.setConnectionTestQuery("select 1");
}
}
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceName = new InheritableThreadLocal<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceName.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceName.get();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface DataSource {
String value();
}
3、mybatis配置
@Configuration
@MapperScan("com.example.dao")
public class MybatisConfig implements TransactionManagementConfigurer {
@Autowired
@Qualifier("multipleDataSource")
private DataSource multipleDataSource;
@Bean("sqlSessionFactoryBean")
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(multipleDataSource);
bean.setTypeAliasesPackage("com.example.entity");
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
return bean.getObject();
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(multipleDataSource);
}
}
4、切面
@Aspect
@Component
public class DataSourceAspect implements Ordered {
@Pointcut("@annotation(com.example.annotation.DataSource)")
public void advice(){}
@Around("advice()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object result =null;
if(pjp.getSignature() instanceof MethodSignature){
MethodSignature signature = (MethodSignature) pjp.getSignature();
DataSource annotation = signature.getMethod().getAnnotation(DataSource.class);
if(annotation!=null){
MultipleDataSource.setDataSourceKey(annotation.value());
}
}
try {
result = pjp.proceed();
}finally {
MultipleDataSource.setDataSourceKey(DataSourceConstant.DEFAULT_DATASOURCE);
}
return result;
}
public int getOrder() {
return 0;
}
}