这种方式实现了一个写库多个读库,使用的是同一套Mapper接口和XML文件,这样就有很好的拓展性,具体代码如下:
先是生成不同的数据源,其中多个读数据源合并:
@Configuration
public class DataBaseConfiguration{
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
@Primary
@ConfigurationProperties(prefix = "spring.write.datasource")
public DataSource writeDataSource() {
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 有多少个从库就要配置多少个
* @return
*/
@Bean(name = "readDataSourceOne")
@ConfigurationProperties(prefix = "spring.read.one")
public DataSource readDataSourceOne(){
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "readDataSourceTwo")
@ConfigurationProperties(prefix = "spring.read.two")
public DataSource readDataSourceTwo() {
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean("readDataSources")
public List<DataSource> readDataSources(){
List<DataSource> dataSources=new ArrayList<DataSource>();
dataSources.add(readDataSourceOne());
dataSources.add(readDataSourceTwo());
return dataSources;
}
}
生成一套SqlSessionFactory,进行动态切换
@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataBaseConfiguration.class})
@MapperScan(basePackages={"com.hui.readwrite.mapper.master1"})
public class TxxsbatisConfiguration {
@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;
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
sqlSessionFactoryBean.setTypeAliasesPackage("com.hui.readwrite.po");
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/master1*//*.xml"));
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sqlSessionFactoryBean.getObject();
}
/**
* 有多少个数据源就要配置多少个bean
* @return
*/
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy() {
int size = Integer.parseInt(dataSourceSize);
TxxsAbstractRoutingDataSource proxy = new TxxsAbstractRoutingDataSource(size);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DataSourceType.write.getType(),dataSource);
for (int i = 0; i < size; i++) {
targetDataSources.put(i, readDataSources.get(i));
}
proxy.setDefaultTargetDataSource(dataSource);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
}
进行选择,和读库的简单负载。Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据库,这样我们可以在执行查询之前,设置读取从库,在执行完成后,恢复到主库。实现可动态路由的数据源,在每次数据库查询操作前执行
public class TxxsAbstractRoutingDataSource extends AbstractRoutingDataSource {
private final int dataSourceNumber;
private AtomicInteger count = new AtomicInteger(0);
public TxxsAbstractRoutingDataSource(int dataSourceNumber) {
this.dataSourceNumber = dataSourceNumber;
}
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DataSourceContextHolder.getJdbcType();
if (typeKey.equals(DataSourceType.write.getType()))
return DataSourceType.write.getType();
// 读 简单负载均衡
int number = count.getAndAdd(1);
int lookupKey = number % dataSourceNumber;
return new Integer(lookupKey);
}
}
利用AOP的方式实现,方法的控制
@Aspect
@Component
public class DataSourceAop {
public static final Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
@Before("execution(* com.hui.readwrite.mapper..*.select*(..)) || execution(* com.hui.readwrite.mapper..*.get*(..))")
public void setReadDataSourceType() {
DataSourceContextHolder.read();
logger.info("dataSource切换到:Read");
}
@Before("execution(* com.hui.readwrite.mapper..*.insert*(..)) || execution(* com.hui.readwrite.mapper..*.update*(..))")
public void setWriteDataSourceType() {
DataSourceContextHolder.write();
logger.info("dataSource切换到:write");
}
}
DataSourceContextHolder类:
public class DataSourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);
public static final String DATA_SOURCE_WRITE = "WRITE";
public static final String DATA_SOURCE_READ = "READ";
//线程本地环境
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 绑定当前线程数据源路由的key
* 使用完成后必须调用removeRouteKey()方法删除
*/
public static void read(){
if(logger.isDebugEnabled())
logger.debug("======切换数据源,类型:"+type+"=======");
contextHolder.set(DataSourceContextHolder.DATA_SOURCE_READ);
}
/**
* 绑定当前线程数据源路由的key
* 使用完成后必须调用removeRouteKey()方法删除
*/
public static void write(){
if(logger.isDebugEnabled())
logger.debug("======切换数据源,类型:"+type+"=======");
contextHolder.set(DataSourceContextHolder.DATA_SOURCE_WRITE);
}
/**
* 获取当前线程的数据源路由的key
*/
public static String getType(){
return contextHolder.get();
}
/**
* 删除与当前线程绑定的数据源路由的key
*/
public static void removeType(){
contextHolder.remove();
}
}
配置文件:
#一些总的配置文件
spring.aop.auto=true
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
datasource.readSize=2
# 主数据源,默认的
spring.write.datasource.driverClassName=com.mysql.jdbc.Driver
spring.write.datasource.url=jdbc:mysql://localhost:3306/learn?useUnicode=true&characterEncoding=utf8
spring.write.datasource.username=root
spring.write.datasource.password=root
# 从数据源
spring.read.one.driverClassName=com.mysql.jdbc.Driver
spring.read.one.url=jdbc:mysql://localhost:3307/learn?useUnicode=true&characterEncoding=utf8
spring.read.one.username=root
spring.read.one.password=root
spring.read.two.driverClassName=com.mysql.jdbc.Driver
spring.read.two.url=jdbc:mysql://localhost:3308/learn?useUnicode=true&characterEncoding=utf8
spring.read.two.username=root
spring.read.two.password=root