javax.sql.DataSource
java
javax.sql.XADataSource
mysql
org.springframework.jdbc.datasource.embedded,EnbeddedDataSource
spring
数据库链接池sql
Apache Commons DBCP数据库
Tomcat DBCPapp
实际生产中极可能会出现.ide
Spring 中的事务经过PlatformTransactionManagere
管理, 使用 AOP 实现, 具体实现类是 TransactionInterceptor
.spring-boot
事务传播(propagation)与保护点(savepoint): 两者密切相关.微服务
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.username=xlx spring.datasource.password=xlx
注意serverTimezone
的配置, 亚洲通常配置为 Hongkong
.ui
DataSource
@Repository public class ProductRepository { @Autowired private DataSource dataSource; ... }
spring.ds1.name=master spring.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.ds1.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false spring.ds1.username=xlx spring.ds1.password=xlx spring.ds2.name=slave spring.ds2.driver-class-name=com.mysql.cj.jdbc.Driver spring.ds2.url=jdbc:mysql://localhost:3306/product?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf8&useSSL=false spring.ds2.username=xlx spring.ds2.password=xlx
AbstractRoutingDataSource
抽象类并实现方法determineCurrentLookupKey()
这个类就是须要使用的数据源类型.
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DbTypeContextHolder.get(); } }
查看 AbstractRoutingDataSource
的源码能够发现, 其定义了一个 Map<Object, Object> targetDataSources;
用来保存多数据源, 而上面重写的方法返回的就是须要使用的数据源的key.
public enum DBType { MASTER, SLAVE }
DbTypeContextHolder
其中使用一个 ThreadLocal
来保存key, 三个方法都必不可少. 分别用来获取, 设置, 清除.@Slf4j public class DbTypeContextHolder { private final static ThreadLocal<DBType> holder = new ThreadLocal<DBType>(); private final static ThreadLocal<Boolean> alwaysWrite = new ThreadLocal<Boolean>(); public static DBType get() { if (alwaysWrite.get() != null && alwaysWrite.get()) { log.info("将强制使用Write"); return DBType.MASTER; } log.info("获取到: "+holder.get().name()); return holder.get(); } public static void set(DBType dbType) { log.info("设置为: "+dbType.name()); holder.set(dbType); } public static void clean() { log.info("清除..."); holder.remove(); } public static void setAlwaysWrite() { log.info("设置强制Write"); alwaysWrite.set(true); } public static void cleanAlwaysWrite() { log.info("清除强制Write"); clean(); alwaysWrite.remove(); } }
MultiDataSourceConfiguration
@Configuration public class MultiDataSourceConfiguration { @Autowired Environment environment; @Bean public DataSource getDynamicDataSource(){ DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object,Object> map = new HashMap<>(); for (int i = 1; i < 3 ; i++) { String name = environment.getProperty("spring.ds"+String.valueOf(i)+".name"); if (name==null) break; DataSource dataSource = DataSourceBuilder.create() .driverClassName(environment.getProperty("spring.ds"+String.valueOf(i)+".driver-class-name")) .username(environment.getProperty("spring.ds"+String.valueOf(i)+".username")) .url(environment.getProperty("spring.ds"+String.valueOf(i)+".url")) .password(environment.getProperty("spring.ds"+String.valueOf(i)+".password")) .build(); if (name.trim().toUpperCase().equals("MASTER")){ dynamicDataSource.setDefaultTargetDataSource(dataSource); map.put(DBType.MASTER,dataSource); }else{ map.put(DBType.SLAVE,dataSource); } } dynamicDataSource.setTargetDataSources(map); return dynamicDataSource; } }
public Boolean save(Product product) throws SQLException { System.out.println("save product : "+product); DbTypeContextHolder.set(DBType.MASTER); System.out.println(dataSource.getConnection()); DbTypeContextHolder.clean(); DbTypeContextHolder.set(DBType.SLAVE); System.out.println(dataSource.getConnection()); DbTypeContextHolder.clean(); return true; }
结果以下,说明是两个不一样的数据源:
HikariProxyConnection@1667860231 wrapping com.mysql.cj.jdbc.ConnectionImpl@78794b01 HikariProxyConnection@556437990 wrapping com.mysql.cj.jdbc.ConnectionImpl@314c0916
能够定义切面来自动切换数据源. 好比普通的读写分离.
读写分离是经过定义在仓储层方法上的规则来实现, 同时也定义了在服务层必须使用写库来读取数据的方式.
除此以外, 若是使用 @Transactional
, 由于其也是经过AOP方式实现, 因此与自动切换的AOP存在顺序关系, 为了能在 @Transactional
事务前设置好数据源, 须要增长 @Order(0)
.
@Aspect @Component @Order(0) public class DataSourceAspect { // 必须使用写库的 @Pointcut("execution(* com.xlx.product.repository.repo..*.save*(..)) || execution(* com.xlx.product.repository.repo..*.insert*(..)) || execution(* com.xlx.product.repository.repo..*.update*(..))") public void write(){} @Before("write()") public void beforeWrite(JoinPoint joinPoint){ System.out.println("beforeWrite()"+joinPoint.getSignature().getName()); DbTypeContextHolder.set(DBType.MASTER); } @After("write()") public void afterWrite(JoinPoint joinPoint){ DbTypeContextHolder.clean(); } // 必须使用读库的 @Pointcut("execution(* com.xlx.product.repository.repo..*.get*(..))|| execution(* com.xlx.product.repository.repo..*.select*(..))||execution(* com.xlx.product.repository.repo..*.count*(..))") public void read(){} @Before("read()") public void beforeRead(JoinPoint joinPoint){ DbTypeContextHolder.set(DBType.SLAVE); } @After("read()") public void afterRead(JoinPoint joinPoint){ DbTypeContextHolder.clean(); } // 服务层有注解的方法特殊处理 @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)" + "||@annotation(com.xlx.product.repository.annotation.DBTypeAnnotation)") public void readWrite(){} @Before("readWrite()") public void before(JoinPoint joinPoint){ MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); if(methodSignature.getMethod().isAnnotationPresent(Transactional.class)) DbTypeContextHolder.set(DBType.MASTER); if(methodSignature.getMethod().isAnnotationPresent(DBTypeAnnotation.class)) DbTypeContextHolder.setAlwaysWrite(); } @After("readWrite()") public void after(JoinPoint joinPoint){ DbTypeContextHolder.clean(); DbTypeContextHolder.cleanAlwaysWrite(); } }
/** * 放置在方法上的注解, 用来指定使用的数据源类型, 默认使用主数据源 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DBTypeAnnotation { @AliasFor("values") DBType dbType() default DBType.MASTER; @AliasFor("dbType") DBType values() default DBType.MASTER; }
@Service @Slf4j public class ProductService { @Autowired ProductRepository repository; public String findAndSave(){ log.info("findAndSave() 方法开始....."); String rs = testFunc(); log.info("findAndSave() 方法结束....."); return rs; } @DBTypeAnnotation public String findAndSaveWithWrite(){ log.info("findAndSaveWithWrite() 方法开始....."); String rs = testFunc(); log.info("findAndSaveWithWrite() 方法结束....."); return rs; } private String testFunc() { List<Product> productList = repository.selectAll(); repository.saveProduct(productList.get(0)); productList = repository.selectAll(); repository.saveProduct(productList.get(0)); return "ok"; } }
@RestController public class ProductController { @Autowired ProductService productService; @GetMapping("/product/findAndSave") public String findAndSave()throws SQLException { return productService.findAndSave(); } @GetMapping("/product/findAndSaveWithWrite") public String findAndSaveWithWrite()throws SQLException { return productService.findAndSaveWithWrite(); } }