为了提高数据库的查询效率,利用数据库主从机制,写走主库,查询走从库。如果只是实现一主一从类似简单的主从模式,可以继承AbstractRoutingDataSource实现读写分离。而不需使用mycat,sharedingJDBC等数据库插件。
分析AbstractRoutingDataSource可知,defaultTargetDataSource,表示默认的数据源;targetDataSources表示配置的所有数据源集合;afterPropertiesSet方法spring bean对象初始化方法,会把targetDataSources和defaultTargetDataSource,设置为resolvedDataSources和resolvedDefaultDataSource。getConnection()获取jdbc的连接,并通过determineTargetDataSource()获取指定的数据源,AbstractRoutingDataSource使用模板类的模式,在父类定义了determineCurrentLookupKey()虚拟方法,获取lookupkey对象;其子类必须实现该方法。源码如下:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
/**
*配置的数据源
*/
@Nullable
private Map
创建DataSourceAddressEnum枚举类,定义MASTER与SLAVE,路由名称。代码如下:
public enum DataSourceAddressEnum {
/**
* 主数据库
*/
MASTER,
/**
* 从数据库
*/
SLAVE;
}
创建DataSourceContextHolder,使用ThreadLocal,定义每次操作的类型枚举,代码如下:
public class DataSourceContextHolder {
private static final ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceAddressEnum.MASTER);
public static void setCurrentDataSource(DataSourceAddressEnum dataSourceAddressEnum) {
CONTEXT_HOLDER.set(dataSourceAddressEnum);
}
public static DataSourceAddressEnum getCurrentDataSource() {
return CONTEXT_HOLDER.get();
}
public static void removeDataSource() {
CONTEXT_HOLDER.remove();
}
}
创建RoutingDataSourceWithAddress,继承AbstractRoutingDataSource,实现determineCurrentLookupKey,即实现了可以根据DataSourceAddressEnum枚举类实现数据源的动态路由,代码如下:
public class RoutingDataSourceWithAddress extends AbstractRoutingDataSource {
/**
* @param defaultTargetDataSource 默认的 DataSource
* @param targetDataSources 配置的所有 DataSource
*/
public RoutingDataSourceWithAddress(DataSource defaultTargetDataSource, Map
使用AOP+注解的方式,对指定的方法进行数据源动态切换的控制。创建RoutingDataSource注解,定义需要路由的数据源,创建RoutingDataSourceAOP定义数据源路由的切面操作,代码如下:
/**
* DataSource路由注解
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RoutingDataSource {
/**
* 路由的DataSource地址,默认为MASTER
*/
DataSourceAddressEnum value() default DataSourceAddressEnum.MASTER;
}
/**
* RoutingDataSource 的aop拦截
**/
@Aspect
@Component
@Order(10000)
@Slf4j
public class RoutingDataSourceAOP {
@Pointcut("@annotation(com.kuqi.mall.demo.conmon.datasource.RoutingDataSource)|| @within(com.kuqi.mall.demo.conmon.datasource.RoutingDataSource)")
public void routingDataSourcePointcut() {
}
@Around("routingDataSourcePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RoutingDataSource routerDataSource = method.getAnnotation(RoutingDataSource.class);
// 如果没有设置则默认为 MASTER
DataSourceAddressEnum dataSourceAddressEnum = Objects.isNull(routerDataSource) ? DataSourceAddressEnum.MASTER : routerDataSource.value();
DataSourceContextHolder.setCurrentDataSource(dataSourceAddressEnum);
try {
return joinPoint.proceed();
} finally {
DataSourceContextHolder.removeDataSource();
}
}
}
创建基于Springboot的自动配置类RoutingDataSourceAutoConfiguration,只要配置了master和slave的属性文件,和mybatis属性文件,就可以自动启动配置。RoutingDataSourceAutoConfiguration源码如下:
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class, DruidDataSource.class})
@EnableConfigurationProperties(MybatisProperties.class)
public class RoutingDataSourceAutoConfiguration {
/**
* 配置master数据源
*/
@Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource() {
DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
return dataSource;
}
/**
* 配置slave数据源
*/
@Bean(name = "slaveDataSource", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DataSource slaveDataSource() {
DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
return dataSource;
}
/**
* 初始化路由DataSource
*/
@Bean(name = "routingDataSourceWithAddress")
public DataSource dataSource(
@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource) {
DataSource defaultTargetDataSource;
Map
数据源使用DruidDataSource,详细的配置文件,master和slave数据源,可以配置不同的数据库用来测试,实际开发中,配置为满足数据库主从复制的配置,配置代码如下:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/kuqi_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/kuqi_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
在springboot的启动类中,屏蔽DruidDataSourceAutoConfigure自动配置类,就能启动。启动类代码,以及操作示例代码如下:
/**
* springboot启动类exclude DruidDataSourceAutoConfigure
*/
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@MapperScan(basePackages = {"com.kuqi.mall.demo.dao"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
/**
* 注解定义动态数据源操作
*/
@Override
@RoutingDataSource(DataSourceAddressEnum.SLAVE)
public CouponBo getFromSlave(Long id) {
return get(id);
}
完整示例代码,可以参考链接spring-boot-mall项目https://github.com/alldays/spring-boot-mall