添加配置文件
spring:
jpa:
properties:
hibernate:
session_factory:
statement_inspector: com.gutousu.dynamic_switch_data_source.config.StatementInspectorImpl
datasource:
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://0.0.0.0:1/gutousu_dynamic_switch_data_source_test_1?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
username:
password:
secondary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://0.0.0.0:2/gutousu_dynamic_switch_data_source_test_2?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
username:
password:
这个是拦截最终执行的sql语句用的,用于测试,观察事务
spring:
jpa:
properties:
hibernate:
session_factory:
statement_inspector: com.gutousu.dynamic_switch_data_source.config.StatementInspectorImpl
这两个是数据源的配置
datasource:
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://0.0.0.0:1/gutousu_dynamic_switch_data_source_test_1?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
username:
password:
secondary:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://0.0.0.0:2/gutousu_dynamic_switch_data_source_test_2?useSSL=true&verifyServerCertificate=false&useUnicode=true&characterEncoding=UTF8
username:
password:
添加数据源配置类
@Setter
@ConfigurationProperties(prefix = "spring.datasource")
@Configuration
public class DataSourceConfig
{
private static final ThreadLocal<String> datasourceHolder = new ThreadLocal<>();
private HikariDataSource primary;
private HikariDataSource secondary;
private static final AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource()
{
@Override
protected Object determineCurrentLookupKey()
{
return datasourceHolder.get();
}
};
public static void setDataSource(String sourceName)
{
datasourceHolder.set(sourceName);
}
public static void clearDataSource()
{
datasourceHolder.remove();
}
@Bean
public DataSource dataSource()
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnum.PRIMARY.getValue(), primary);
targetDataSources.put(DataSourceEnum.SECONDARY.getValue(), secondary);
abstractRoutingDataSource.setTargetDataSources(targetDataSources);
abstractRoutingDataSource.setDefaultTargetDataSource(primary);
return abstractRoutingDataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
}
每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
private static final ThreadLocal<String> datasourceHolder = new ThreadLocal<>();
通过ConfigurationProperties注解读取配置文件中的数据库连接信息,将他们放到这里
private HikariDataSource primary;
private HikariDataSource secondary;
获取连接信息的类,实现determineCurrentLookupKey方法获取数据源的key
private static final AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource()
{
@Override
protected Object determineCurrentLookupKey()
{
return datasourceHolder.get();
}
};
用两个静态方法,切换数据源,和清除key信息
public static void setDataSource(String sourceName)
{
datasourceHolder.set(sourceName);
}
public static void clearDataSource()
{
datasourceHolder.remove();
}
将数据源放到一个Map中
然后将Map放到abstractRoutingDataSource中
返回abstractRoutingDataSource到spring的bean容器中
@Bean
public DataSource dataSource()
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnum.PRIMARY.getValue(), primary);
targetDataSources.put(DataSourceEnum.SECONDARY.getValue(), secondary);
abstractRoutingDataSource.setTargetDataSources(targetDataSources);
abstractRoutingDataSource.setDefaultTargetDataSource(primary);
return abstractRoutingDataSource;
}
获取刚刚添加的bean,将其放到事务中,再返回到spring的bean容器中
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
return new DataSourceTransactionManager(dataSource);
}
添加一个枚举
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum DataSourceEnum
{
PRIMARY("primary"),
SECONDARY("secondary");
private String value;
}
添加一个注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource
{
DataSourceEnum value();
}
添加一个aop,拦截方法,切换数据源
@Aspect
@Order(-1)
@Component
public class DataSourceAop
{
@Before("@annotation(dataSource)")
public void before(JoinPoint joinPoint, DataSource dataSource)
{
DataSourceConfig.setDataSource(dataSource.value().getValue());
}
@After("@annotation(dataSource)")
public void after(JoinPoint joinPoint, DataSource dataSource)
{
DataSourceConfig.clearDataSource();
}
}
这里是为了,在事务开启之前切换数据源
@Order(-1)
实现一下StatementInspector接口,这个接口会拦截所有将要执行的sql语句,用于测试事务
@Component
public class StatementInspectorImpl implements StatementInspector
{
@Override
public String inspect(String sql)
{
return sql;
}
}
--------------------------------------------------------------------------------------------------------------
最后添加一些测试
两个方法测试的接口
public interface ITestService
{
String t1();
String t2();
}
实现方法测试接口,在方法上加上 aop直接
@Service
public class TestService implements ITestService
{
@Autowired
private IUserRepository userRepository;
@DataSource(value = DataSourceEnum.SECONDARY)
//@Transactional
public String t1()
{
user user = userRepository.save(new user("张三",21));
System.out.print("");
return "";
}
@DataSource(value = DataSourceEnum.PRIMARY)
@Transactional
public String t2()
{
user user = userRepository.save(new user("张三",21));
System.out.print("");
return "";
}
}
添加一个controller
@RestController
@RequestMapping("/test")
public class TestController
{
@Autowired
private ITestService testService;
@GetMapping("/t1")
public String t1()
{
testService.t1();
return "";
}
@GetMapping("/t2")
public String t2()
{
testService.t2();
return "";
}
}
测试一下没有事务的方法一
被aop拦截到了,切换到了注解中指定的数据源
进入方法,执行添加方法
这里直接执行了添加的sql语句
数据库中有数据了
测试一下有事务的方法二
被aop拦截到了,并切换到了另一个数据源
进入方法添加数据
这里执行完添加了,但是没有执行sql语句,说事务没有提交,只在事务中进行了添加
最后结束方法的时候执行了添加的sql语句
完结!撒花!