这个根据自己项目的配置项进行,有的习惯在mybaits下配置db,我的是在spring.datasource配置:
master名字,slaver1名字自己取,也可以叫write,read
配置mybaits配置项:
# MyBatis
mybatis:
type-aliases-package: com.zyd.blog.persistence.beans
mapper-locations: classpath:/mybatis/*.xml
config-location: classpath:/config/mybatis-config.xml
由于涉及到事务处理,可能会遇到事务中同时用到读库和写库,可能会有延时造成脏读,所以增加了线程变量设置,来保证一个事务内读写都是同一个库 新增文件
package com.zyd.blog.framework.holder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 利用ThreadLocal封装的保存数据源上线的上下文context
*/
public class DataSourceContextHolder {
private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
public static final String WRITE = "master";
public static final String READ = "salver";
private static ThreadLocal<String> contextHolder= new ThreadLocal<>();
public static void setDbType(String dbType) {
if (dbType == null) {
log.error("dbType为空");
throw new NullPointerException();
}
log.info("设置dbType为:{}",dbType);
contextHolder.set(dbType);
}
public static String getDbType() {
return contextHolder.get() == null ? WRITE : contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
新增文件:DataSourceRouter.java,继承AbstractRoutingDataSource ,重写determineCurrentLookupKey,用于动态切换数据源配置
package com.zyd.blog.framework.config;
import com.zyd.blog.framework.holder.DataSourceContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Random;
public class DataSourceRouter extends AbstractRoutingDataSource {
@Value("${spring.datasource.num}")
private int num;
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DataSourceContextHolder.getDbType();
if (typeKey == DataSourceContextHolder.WRITE) {
// System.out.println("使用了写库");
log.info("使用了写库");
return typeKey;
}
//随机1-num的随机数
Random rand = new Random();
int randomNumber = rand.nextInt(num) + 1;
//使用随机数决定使用哪个读库
log.info("使用了读库"+randomNumber);
// System.out.println("使用了读库"+randomNumber);
return DataSourceContextHolder.READ+randomNumber;
}
}
新增MybatisConfig.java,你也可以取别的名字,比如DatabaseConfig.java
/**
* 写数据源
*
* @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。
* 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource writeDataSource() {
return new DruidDataSource();
}
/**
* 读数据源,有几个就配置几个
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slaver1")
public DataSource read1() {
return new DruidDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slaver2")
public DataSource read2() {
return new DruidDataSource();
}
@MapperScan(basePackages = "com.zyd.blog.persistence.mapper",sqlSessionFactoryRef = "sqlSessionFactory")
由于多数据库连接,所以需要自定义sqlSessionFactory,指定数据库连接和实例化配置 在 mybaitsConfig.java额外增加属性:
@Value("${mybatis.type-aliases-package}")
private String typeAliasesPackage;
@Value("${mybatis.mapper-locations}")
private String mapperLocation;
@Value("${mybatis.config-location}")
private String configLocation;
自定义sqlsession工厂方法:
/**
* 多数据源需要自己设置sqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(routingDataSource());
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 实体类对应的位置
bean.setTypeAliasesPackage(typeAliasesPackage);
// mybatis的XML的配置
bean.setMapperLocations(resolver.getResources(mapperLocation));
bean.setConfigLocation(resolver.getResource(configLocation));
return bean.getObject();
}
/**
* 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理
*/
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
return new DataSourceTransactionManager(routingDataSource());
}
package com.zyd.blog.framework.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.zyd.blog.framework.holder.DataSourceContextHolder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://docs.zhyd.me
* @date 2018/4/16 16:26
* @since 1.0
*/
@Configuration
@MapperScan(basePackages = "com.zyd.blog.persistence.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfig {
@Value("${mybatis.type-aliases-package}")
private String typeAliasesPackage;
@Value("${mybatis.mapper-locations}")
private String mapperLocation;
@Value("${mybatis.config-location}")
private String configLocation;
/**
* 写数据源
*
* @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。
* 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource master() {
return new DruidDataSource();
}
/**
* 读数据源,有几个就配置几个
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slaver1")
public DataSource slaver1() {
return new DruidDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slaver2")
public DataSource slaver2() {
return new DruidDataSource();
}
/**
* 多数据源需要自己设置sqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(routingDataSource());
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 实体类对应的位置
bean.setTypeAliasesPackage(typeAliasesPackage);
// mybatis的XML的配置
bean.setMapperLocations(resolver.getResources(mapperLocation));
bean.setConfigLocation(resolver.getResource(configLocation));
return bean.getObject();
}
/**
* 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理
*/
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
return new DataSourceTransactionManager(routingDataSource());
}
/**
* 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源
*/
@Bean
public AbstractRoutingDataSource routingDataSource() {
DataSourceRouter proxy = new DataSourceRouter();
Map<Object, Object> targetDataSources = new HashMap<>(3);
targetDataSources.put(DataSourceContextHolder.WRITE, master());
targetDataSources.put(DataSourceContextHolder.READ + 1, slaver1());
targetDataSources.put(DataSourceContextHolder.READ + 2, slaver2());
proxy.setDefaultTargetDataSource(master());
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
}
只需要调用 DataSourceContextHolder.setDbType(DataSourceContextHolder.READ);
即可切换读写
在实际业务中,手动切换并不方便,我们可以使用java的AOP注解方式去实现注解切换, 新增DBRead.java文件
package com.zyd.blog.framework.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Description 通过该接口注释的service使用读模式,其他使用写模式
*
* 接口注释只是一种办法,如果项目已经有代码了,通过注释可以不修改任何业务代码加持读写分离
* 也可以通过切面根据方法开头来设置读写模式,例如getXXX()使用读模式,其他使用写模式
*
* @author fxb
* @date 2018-08-31
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBRead {
}
新增ReadInterceptor.java文件,用于配置切面
package com.zyd.blog.framework.mysql;
import com.zyd.blog.framework.holder.DataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* Description
*
* @author fxb
* @date 2018-08-31
*/
@Aspect
@Component
public class ReadInterceptor implements Ordered {
private static final Logger log = LoggerFactory.getLogger(ReadInterceptor.class);
@Around("@annotation(DBRead)")
public Object setRead(ProceedingJoinPoint joinPoint, DBRead DBRead) throws Throwable {
try {
//如果当前线程已经是已读,则不做修改
DataSourceContextHolder.setDbType(DataSourceContextHolder.READ);
return joinPoint.proceed();
} finally {
DataSourceContextHolder.clearDbType();
log.info("清除threadLocal");
}
}
@Override
public int getOrder() {
return 0;
}
}
在方法上配置@DBRead注解即可:
完成