Spring Boot 读写分离简单实现:
一、数据源配置
此处只简单配置两个数据源,master(主-写)和slave(从-读)
@PropertySource读取指定目录下配置文件
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import java.util.HashMap;
import java.util.Map;
/**
* 数据源配置类
* 此处默认两个数据库,master(写)、slave(读)
*/
@Configuration
@PropertySource("classpath:config/${spring.profiles.active}/db.properties")
public class DataSourceConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceConfig.class);
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix="spring.datasource.master")
public DataSource masterDataSource() {
LOGGER.debug("[DataSourceConfig] Init DataSource Master");
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix="spring.datasource.slave")
public DataSource slaveDataSource() {
LOGGER.debug("[DataSourceConfig] Init DataSource Slave");
return DataSourceBuilder.create().build();
}
@Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceContextHolder.DbType.MASTER, masterDataSource);
targetDataSources.put(DataSourceContextHolder.DbType.SLAVE, slaveDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
myRoutingDataSource.setTargetDataSources(targetDataSources);
return myRoutingDataSource;
}
}
db.properties配置文件
spring.datasource.master.jdbc-url = jdbc:mysql://127.0.0.1:3306/db1
spring.datasource.master.username = admin
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = com.mysql.jdbc.Driver
spring.datasource.slave.jdbc-url = jdbc:mysql://127.0.0.1:3306/db2
spring.datasource.slave.username = admin
spring.datasource.slave.password = 123456
spring.datasource.slave.driverClassName = com.mysql.jdbc.Driver
二、线程上下文
用于保存当前线程本地变量,存放数据源类型
/**
* 线程上下文 <br>
* 用于保存线程本地变量DbType(用于切换数据源)
* 如果是异步线程操作,需要用InheritableThreadLocal (需要保存父线程的变量)
*
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public class DataSourceContextHolder {
private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();
public static ThreadLocal<DbType> getLocal() {
return contextHolder;
}
private DataSourceContextHolder () {
}
public static DbType getDbType() {
return contextHolder.get() == null ? DbType.MASTER : contextHolder.get();
}
public static void setDbType(DbType dbType) {
contextHolder.set(dbType);
}
public static void clearDbType() {
contextHolder.remove();
}
public enum DbType {
MASTER,
SLAVE
}
}
三、继承AbstractRoutingDataSource ,实现动态数据源配置
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 多数据源切换 <br>
*
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}
}
四、使用AOP实现数据源的切换
此处使用aop,对dao层的方法进行切面,根据方法名query,update等进行读写数据源的切换
import com.study.demo.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 数据源切面,用于在执行sql前进行读写数据源切换 <br>
* 〈功能详细描述〉
*
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
@Aspect
@Order(-1) // 保证该AOP在@Transactional之前执行
@Component
public class DataSourceAop {
private static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
@Pointcut("execution(* com.study.demo.dao..*.*(..))")
public void cutPoint() {}
@Before("execution(* com.study.demo.dao..*.query*(..))")
public void setReadDataSourceType() {
DataSourceContextHolder.setDbType(DataSourceContextHolder.DbType.MASTER);
logger.debug("[DataSourceConfig] DataSource Covert To Read");
}
@Before("execution(* com.study.demo.dao..*.insert*(..))" +
"|| execution(* com.study.demo.dao..*.update*(..))" +
"|| execution(* com.study.demo.dao..*.delete*(..))")
public void setWriteDataSourceType() {
DataSourceContextHolder.setDbType(DataSourceContextHolder.DbType.SLAVE);
logger.debug("[DataSourceConfig] DataSource Covert To Write");
}
// ☆☆☆☆☆ 也可以获取参数中路由字段进行切换数据源 ☆☆☆☆☆
@Before(value="cutPoint()")
public void setDynamicDataSource(JoinPoint point){
logger.debug("[DataSourceConfig] rout...");
}
}
五、Mybatis配置
import com.study.demo.interceptor.DynamicSQLInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* MyBatis 配置类 <br>
*
* 为动态数据源绑定SqlSessionFactory
*
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:config/sqlMap/*.xml"));
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
DynamicSQLInterceptor interceptor = new DynamicSQLInterceptor();
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
return sqlSessionFactory;
}
}
对于分表操作,可以简单对Mybatis进行拦截,根据路由字段简单实现SQL的修改(表号),详细在下一篇进行简单demo示例。