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<DataSource> readDataSources() {
List<DataSource> 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<String> local = new ThreadLocal<>();
public static ThreadLocal<String> 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<String, String> 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<DataSource> readDataSources;
/**
* 動態數據源
*
* @return
*/
@Bean(name = "roundRobinDataSourceProxy")
@Primary
public AbstractRoutingDataSource roundRobinDataSourceProxy() {
int dsSize = Integer.parseInt(StringUtils.isBlank(dataSourceSize) ? "1" : dataSourceSize);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(dsSize);
Map<Object, Object> targetDataSources = new HashMap<>();
// 寫
targetDataSources.put(DataSourceType.master.getType(), dataSource);
//多個讀數據庫時
for (int i = 0; i < dsSize; i++) {
targetDataSources.put(i, readDataSources.get(i));
}
proxy.setDefaultTargetDataSource(dataSource);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
/**
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "dynamicSqlSessionFactory")
@Primary
public SqlSessionFactory setSqlSessionFactory(@Qualifier("roundRobinDataSourceProxy") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.xxx.xxx.dao");
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
properties.putAll(pagehelper);
interceptor.setProperties(properties);
sqlSessionFactoryBean.getObject().getConfiguration().addInterceptor(interceptor);
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "dynamicSessionTemplate")
@Primary
public SqlSessionTemplate setSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
定義一個主數據源註解,用於一些場景下強制查詢主庫數據(主從庫有延遲,某些場景只能查詢主庫數據)
@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());
}
}
注意:事務的數據源必須與執行方法的數據源一致,否則事務不起作用。