本文共 10995 字,大约阅读时间需要 36 分钟。
为了提高数据库的查询效率,利用数据库主从机制,写走主库,查询走从库。如果只是实现一主一从类似简单的主从模式,可以继承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 ThreadLocalCONTEXT_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)@Slf4jpublic 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项目
转载地址:http://ewyc.baihongyu.com/