方法一
- 配置多个数据库
spring:
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3305/se?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
movies:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/se_movies?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
- 配置数据库连接
package com.my.equipment.config.oldConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.my.equipment.web.seMoviesDao", sqlSessionTemplateRef = "seMoviesSqlSessionTemplate")
public class SeMoviesDatasourceConfig {
@Bean(name = "seMoviesDataSource")
@ConfigurationProperties(prefix = "spring.datasource.movies")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "seMoviesSqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("seMoviesDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return bean.getObject();
}
@Bean(name = "seMoviesTransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("seMoviesDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "seMoviesSqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("seMoviesSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.my.equipment.config.oldConfig;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.my.equipment.web.dao",sqlSessionTemplateRef = "seSqlSessionTemplate")
public class SeDatasourceConfig {
@Bean(name = "seDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
@Primary
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "seSqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("seDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return bean.getObject();
}
@Bean(name = "seTransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("seDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "seSqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("seSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
方法2
1.配置多个数据库
spring:
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3305/se?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
slave:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3310/se?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
movies:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3307/se_movies?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
2.配置数据库连接
定义多元数据库
package com.my.equipment.utils;
public enum DBTypeEnum {
MASTER,SLAVE,MOVIES;
}
定义数据源切换
package com.my.equipment.utils;
public class DBContextHolder {
private static final ThreadLocal<DBTypeEnum> contextHolder=new ThreadLocal<>();
public static void set(DBTypeEnum dbTypeEnum){
contextHolder.set(dbTypeEnum);
}
public static DBTypeEnum get(){
return contextHolder.get();
}
public static void master(){
set(DBTypeEnum.MASTER);
System.out.println("写");
}
public static void slave(){
set(DBTypeEnum.SLAVE);
System.out.println("读");
}
public static void movies(){
set(DBTypeEnum.MOVIES);
System.out.println("movies");
}
public static void clear(){
contextHolder.remove();
}
}
重写路由选择类
package com.my.equipment.utils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
配置Mybatis SqlSessionFactory 和事务管理器
package com.my.equipment.config;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocation;
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
ResourcePatternResolver resolver=new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocation));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(myRoutingDataSource);
}
}
配置数据源
package com.my.equipment.config;
import com.my.equipment.utils.DBTypeEnum;
import com.my.equipment.utils.MyRoutingDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DatasourceConfig {
/**
* 配置从数据库
* @return
*/
@Bean(name = "slaveDataSource")
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置主数据库
* @return
*/
@Bean(name = "masterDataSource")
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "moviesDataSource")
@ConfigurationProperties("spring.datasource.movies")
public DataSource moviesDataSource(){
return DataSourceBuilder.create().build();
}
@Bean
public DataSource myRoutingDataSource(@Qualifier("slaveDataSource") DataSource slaveDataSource,
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("moviesDataSource") DataSource moviesDataSource){
Map<Object,Object> targetDataSource=new HashMap<>();
targetDataSource.put(DBTypeEnum.MASTER,masterDataSource);
targetDataSource.put(DBTypeEnum.SLAVE,slaveDataSource);
targetDataSource.put(DBTypeEnum.MOVIES,moviesDataSource);
MyRoutingDataSource myRoutingDataSource=new MyRoutingDataSource();
//找不到用默认数据源
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
//可选择的目标数据源
myRoutingDataSource.setTargetDataSources(targetDataSource);
return myRoutingDataSource;
}
}
切面实现数据源切换
package com.my.equipment.aspect;
import com.my.equipment.utils.DBContextHolder;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAop {
//com.my.equipment.web.dao及其子包下所有的方法
@Pointcut("execution(* com.my.equipment.web.dao..*.*(..))")
public void writePoint(){
}
//com.my.equipment.web.seSlaveDao及其子包下所有的方法(..*代表当前及其子目录)
@Pointcut("execution(* com.my.equipment.web.seSlaveDao..*.*(..))")
public void readPoint(){
}
@Pointcut("execution(* com.my.equipment.web.seMoviesDao..*.*(..))")
public void moviesPoint(){
}
@Before("readPoint()")
public void read(){
DBContextHolder.slave();
}
@Before("writePoint()")
public void write(){
DBContextHolder.master();
}
@Before("moviesPoint()")
public void movies(){
DBContextHolder.movies();
}
}
注意:事务问题,可以发现一但添加了@Transactional,那么它的数据源只会去使用默认的数据源。(由于 AbstractRoutingDataSource中有个机制,如果当前上下文的连接对象为空,获取一个连接对象,然后保存起来,下次doBegin再调用时,就直接用这个连接了,根本不做任何切换(类似于缓存命中!),这就导致切换失败,也许有人会想到提高注入优先级,但是本文的切面是基于Dao的,那么会导致无论你怎么调整优先级,务必是@Transctional优先注入,那么要解决该问题个人目前的思路是:在Controller优先切换数据源,在services中使用@Transactional该方法)
@ApiOperation(value = "结束录制")
@GetMapping(value = "/stop")
public String stop() {
try {
for (Map.Entry<Integer, Process> entry : map.entrySet()) {
rtspToMP4.stopRecord(entry.getValue());
}
DBContextHolder.set(DBTypeEnum.MOVIES);
masterSensorService.saveVideoUrl(names,urls);
} catch (Exception e) {
e.printStackTrace();
return "录制失败";
}
return "结束录制";
}
@Override
@Transactional
public void saveVideoUrl(List<String> names, List<String> urls) {
for (int i = 0; i < names.size(); i++) {
RecordVideo recordVideo=new RecordVideo();
recordVideo.setUrl(urls.get(i));
recordVideo.setName(names.get(i));
recordVideoMapper.insertSelective(recordVideo);
}
}
但是该方法仍然属于非分布式数据库事务层面,无法做到在一个方法中回滚两个不同的数据源。