MybatisConfiguration (mybatis配置)导入驱动包和druid,略....
定义多个数据源,本项目只有一读一写(可以根据需要配置一写多读)
DruidDataBaseConfiguration.java
public class DruidDataBaseConfiguration {
/**
* 主库, 一般只用于写数据。 通过配置自动注入
* @return DataSource
*/
@Bean(name = "writeDataSource")
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 读库。通过配置自动注入
* @return
*/
@Bean(name = "readDataSource")
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 读库列表(目前本项目只有一个读库)
* @return
*/
@Bean(name = "readDataSources")
public List readDataSources() {
List dataSources = new ArrayList<>();
dataSources.add(slaveDataSource());
return dataSources;
}
}
application.properties
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
datasource.readSize=1
#主库配置
spring.datasource.druid.master.username=root
spring.datasource.druid.master.password=123456
spring.datasource.druid.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.master.url=jdbc:mysql:///master...
spring.datasource.druid.master.initialSize=5
spring.datasource.druid.master.minIdle=5
spring.datasource.druid.master.maxActive=20
#读库配置
spring.datasource.druid.slave.username=root
spring.datasource.druid.slave.password=123456
spring.datasource.druid.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.slave.url=jdbc:mysql:///slave...
spring.datasource.druid.slave.initialSize=5
spring.datasource.druid.slave.minIdle=5
spring.datasource.druid.slave.maxActive=20
DataSourceType.java(枚举)
DataSourceContextHolder.java(数据源负载切换)
MyAbstractRoutingDataSource.java (读数据源负责均衡配置)
MybatisConfiguration.java (mybatis配置)
WriteDataSource.java (注解类,强制使用主数据源读取则使用该注解)
DataSourceAspect.java (数据源切换)
public enum DataSourceType {
slave("read", "从库"),
master("write", "主库");
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
DataSourceType(String type, String name) {
this.type = type;
this.name = name;
}
}
public class DataSourceContextHolder {
private static final ThreadLocal local = new ThreadLocal<>();
public static ThreadLocal getLocal() {
return local;
}
/**
* 读可能是多个库
*/
public static void read() {
local.set(DataSourceType.slave.getType());
}
/**
* 写只有一个库
*/
public static void write() {
local.set(DataSourceType.master.getType());
}
public static String getJdbcType() {
return local.get();
}
}
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
private final int dataSourceNumber;
private AtomicInteger count = new AtomicInteger(0);
public MyAbstractRoutingDataSource(int dataSourceNumber) {
this.dataSourceNumber = dataSourceNumber;
}
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DataSourceContextHolder.getJdbcType();
if (DataSourceType.master.getType().equals(typeKey)) {
return DataSourceType.master.getType();
}
// 读 简单负载均衡
int number = count.getAndAdd(1);
int lookupKey = number % dataSourceNumber;
return new Integer(lookupKey);
}
}
@Configuration
@Import({DruidDataBaseConfiguration.class})
@ConditionalOnClass({EnableTransactionManagement.class})
@MapperScan(basePackages = {"com.xxx.xxx.dao"})
public class MybatisConfiguration {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private Map pagehelper = new LinkedHashMap<>();
@Value("${spring.datasource.type}")
private Class extends DataSource> dataSourceType;
@Value("${datasource.readSize}")
private String dataSourceSize;
@Resource(name = "writeDataSource")
private DataSource dataSource;
@Resource(name = "readDataSources")
private List readDataSources;
/**
* 动态数据源
*
* @return
*/
@Bean(name = "roundRobinDataSourceProxy")
@Primary
public AbstractRoutingDataSource roundRobinDataSourceProxy() {
int dsSize = Integer.parseInt(StringUtils.isBlank(dataSourceSize) ? "1" : dataSourceSize);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(dsSize);
Map
定义一个主数据源注解,用于一些场景下强制查询主库数据(主从库有延迟,某些场景只能查询主库数据)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface WriteDataSource {
}
定义AOP,用于数据源切换
/**
* 默认拦截dao层方法,切换数据源
* dao层方法命名:
* 以get,query,select,find开头,切换到读库,否则切换到写库
* 当方法添加@WriteDataSource,强制切换到写库
*/
@Aspect
@Component
public class DataSourceAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Around("execution(* com.xxx.xxx.dao..*.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature ms = (MethodSignature) pjp.getSignature();
Method method = ms.getMethod();
boolean write = false;
Annotation[] annotations = method.getDeclaredAnnotations();
if (annotations != null) {
for (int i = 0; i < annotations.length; i++) {
write = annotations[i].annotationType().equals(WriteDataSource.class);
if (write) {
DataSourceContextHolder.write();
log.debug("dataSource切换到:write");
return pjp.proceed();
}
}
}
String methodName = method.getName();
if (methodName.startsWith("select")
|| methodName.startsWith("get")
|| methodName.startsWith("query")
|| methodName.startsWith("find")) {
DataSourceContextHolder.read();
log.debug("dataSource切换到:Read");
} else {
DataSourceContextHolder.write();
log.debug("dataSource切换到:write");
}
return pjp.proceed();
}
}
事务配置
MultiDataSourceTransactionManager.java
DataSourceTransactionManagerConfiguration.java
public class MultiDataSourceTransactionManager extends DataSourceTransactionManager {
private static final long serialVersionUID = 8478667649867892934L;
public MultiDataSourceTransactionManager(DataSource dataSource) {
super(dataSource);
}
/**
* 事务切换到写库, 读库不需要事务
*/
@Override
protected Object doGetTransaction() {
DataSourceContextHolder.write();
return super.doGetTransaction();
}
}
@Configuration
@EnableTransactionManagement
public class DataSourceTransactionManagerConfiguration{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 自定义事务
* MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
* @return
*/
@Resource(name = "roundRobinDataSouceProxy")
private DataSource dataSource;
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
logger.info("-------------------- transactionManager init ---------------------");
return new MultiDataSourceTransactionManager(dataSource);
}
@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate(){
logger.info("-------------------- transactionTemplate init ---------------------");
return new TransactionTemplate(transactionManager());
}
}
注意:事务的数据源必须与执行方法的数据源一致,否则事务不起作用。