网上关于动态数据源配置的博文一搜一大堆,都是拿来主义,往往把需要的人弄得不是太明白,也没有个具体的好用的简单的demo例子供人参考,本篇,我也是拿来主义,,我拿来核心的core,进行demo案列整理,我只挑重要的部分讲,demo会在最后提供GitHub下载
(
博主 2018年3月16日14:26:47
注: 这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不但失效而且无法实现
一致性,因为涉及到跨库,因此我们必须另想办法来实现事务的ACID原则,下一篇,我会讲解如何利用atomikos来实现分布式事务的管理和应用。
)
sql脚本最后附上
DynamicDataSourceRegister.java
package com.appleyk.config;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.appleyk.datasource.DynamicDataSource;
import com.appleyk.datasource.DynamicDataSourceContextHolder;
import com.appleyk.pojo.DataSourceInfo;
/**
*
* 功能描述:动态数据源注册 启动动态数据源请在启动类中(如Start)
* 添加 @Import(DynamicDataSourceRegister.class)
*/
@Configuration
@EnableTransactionManagement
@EnableConfigurationProperties(DataSourceProperties.class)
@MapperScan("com.appleyk")
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// 数据源
private DataSource defaultDataSource;
private Map customDataSources = new HashMap<>();
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map
注意,数据源注册成功后,如果不手动配置各自的事务,会导致后面的数据源表面虽切换成功,但是在默认的同事务下,主从业务执行的时候仍然使用的是默认的主库数据源,也就是会造成“数据源切换失效”(事务一旦开启,Connection就不能再改变)
因此,我们需要在Spring-Boot里,根据数据源的名称向spring容器中注入各自的事务(Bean),当然name我们是知道的
package com.appleyk.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.appleyk.annotation.DataSource;
import com.appleyk.datasource.DynamicDataSourceContextHolder;
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, DataSource ds) throws Throwable {
String dsId = ds.name();
System.err.println(dsId);
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
System.err.println("数据源[{"+ds.name()+"}]不存在,使用默认数据源 >"+point.getSignature());
} else {
System.err.println("Use DataSource : "+ds.name()+">"+point.getSignature() );
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
}
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, DataSource ds) {
System.err.println("Revert DataSource : "+ds.name()+" > "+point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
(3)二者事务虽然不一样,但可以根据各自的业务执行状态进行整个object对象存储的事务控制,比如,先入主库,如果主库insert成功,才向下执行从库的对象存储,如果主库事务回滚,我们可以手动抛出异常,并终止从库的数据操作;
同理,从库的service操作也可以进行相应的事物控制(事物没有进行过多的验证,可自行解决)
ObjectController.java
package com.appleyk.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.appleyk.entity.A;
import com.appleyk.result.ResponseMessage;
import com.appleyk.result.ResponseResult;
import com.appleyk.service.ObjectService;
@RestController
@RequestMapping("/rest/v1.0.1/object")
public class ObjectController {
@Autowired
private ObjectService objService;
@PostMapping("/save")
public ResponseResult SaveObject(@RequestBody A a) throws Exception {
if (objService.Save(a)) {
return new ResponseResult(ResponseMessage.OK);
}
return new ResponseResult(ResponseMessage.INTERNAL_SERVER_ERROR);
}
}
注意:master的a表和slave的b表结构一样,只是名称不一样
object对象 分 --- A实体对象对应主库master的a表,B实体(A实体对象构造而来)对象对应从库slave的b表
master -- a表
slave -- b表
{
"name": "appleyk",
"sex": "F",
"age":27
}
2.Spring-Boot启动
3.接口测试
a表
b表
控制台数据源切换信息打印
下一篇:Spring-Boot + Atomikos 实现跨库的分布式事务管理