springboot+mybatis+druid实现mysql主从读写分离(三)

一、如何自定义注解

1、使用格式

  • 修饰符

    访问修饰符必须为public,不写默认为public

  • 关键字

    关键字为@interface;

  • 注解名称

    自定义名称

  • 注解类型元素

    注解类型元素是注解中内容,可以理解成自定义接口的实现部分;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSign {

	/**
     * 切换数据源名称
     */
    //返回类型  方法  默认值。
    DbType value() default DbType.MASTER;
}

2、使用元注解

JDK中有一些元注解,主要有@Target,@Retention,@Document,@Inherited用来修饰注解。

具体参考

二、数据源配置

spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://192.168.102.31:3306/test
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver
    slave1:
      jdbc-url: jdbc:mysql://192.168.102.56:3306/test
      username: pig   # 只读账户
      password: 123456
      driver-class-name: com.mysql.j
      dbc.Driver

三、主从分离代码实现

参考另一种实现方式

1、数据源类型

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSign {

	/**
     * 切换数据源名称
     */
    DbType value() default DbType.MASTER;
}

2、设置获取数据源

public class DynamicDbContextHolder {

    public enum DbType {
        MASTER, SLAVE, OTHER
    }

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();

    /**
     * 设置数据源变量
     * @param dbType
     */
    public static void setDbType(DbType dbType) {
        if (dbType == null) {
        	throw new NullPointerException();
        }        	
        contextHolder.set(dbType);
    }

    /**
     * 获取数据源变量
     * @return
     */
    public static DbType getDbType() {
        return contextHolder.get() == null ? DbType.MASTER : contextHolder.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDbType() {
        contextHolder.remove();
    }

}

3、自定义数据源注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSign {
    DbTypeEnum value() default DbTypeEnum.MASTER;
}

4、动态切换数据源AOP切面处理

/**
 * 问题:自定义数据源注解与@Transaction同时使用时不生效
 * 自定义数据源注解与@Transaction注解同一个方法,会先执行@Transaction注解,即获取数据源在切换数据源之前,所以会导致自定义注解失效。
 * 解决方法:定义切换数据源的注解的AOP切面(DynamicDataSourceAspect )上添加注解【@Order(-1),ordel的value越小,就越先执行】,
 * 保证该AOP在@Transactional之前执行
 */
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.jht.jsicp.common.dynamicdb.annotation.DataSourceSign)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSourceSign dataSource = method.getAnnotation(DataSourceSign.class);
        if (dataSource != null) {
        	DynamicDbContextHolder.setDbType(dataSource.value());
        }
        
        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
        	DynamicDbContextHolder.clearDbType();
        }
    }
}
——————————————————————————————————另一种写法———————————————————————————————————————————————
@Aspect
@Component
public class DynamicDbAspect implements PriorityOrdered {
	
	public static final Logger logger = LoggerFactory.getLogger(DynamicDbAspect.class);
	
	
	/**
	 * 切换到master主库
	 * @param proceedingJoinPoint
	 * @param Page
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(master)")
	public Object proceed(ProceedingJoinPoint proceedingJoinPoint, Master master) throws Throwable {
		try {
			//logger.info("set database connection to master only");
			DynamicDbContextHolder.setDbType(DynamicDbContextHolder.DbType.MASTER);
			Object result = proceedingJoinPoint.proceed();
			return result;
		} finally {
			DynamicDbContextHolder.clearDbType();
			//logger.info("restore master database connection");
		}
	}/**
	 * 切换到slave从库
	 * @param proceedingJoinPoint
	 * @param Slave
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(slave)")
	public Object proceed(ProceedingJoinPoint proceedingJoinPoint, Slave slave) throws Throwable {
		try {
			//logger.info("set database connection to slave only");
			DynamicDbContextHolder.setDbType(DynamicDbContextHolder.DbType.SLAVE);
			Object result = proceedingJoinPoint.proceed();
			return result;
		} finally {
			DynamicDbContextHolder.clearDbType();
			//logger.info("restore slave database connection");
		}
	}
	
	@Override
	public int getOrder() {
		return 1;
	}
}

5、动态数据源决策

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

	public static final Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);
	
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDbContextHolder.getDbType();
    }
}

6、数据库(源)、事务配置类

//参考
@Configuration
@EnableTransactionManagement
public class DataSourceConfiguration {
	
	public static final Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);
	
	@Value("${druid.type}") #druid.type=com.alibaba.druid.pool.DruidDataSource
	private Class<? extends DataSource> dataSourceType;
	
	#mybatis配置
    #mybatis.mapper-locations=classpath*:mapper/*.xml
	@Value("${mybatis.mapper-locations}")
	private String mapperLocations;
	
	
	#mybatis.configLocation=classpath:mybatis-config.xml
	@Value("${mybatis.configLocation}")
	private String configLocation;
	
	#mybatis.type-aliases-package=com.jht.jscc
	@Value("${mybatis.type-aliases-package}")
	private String entityPackage;

	/**
	 * 主数据库
	 * @return
	 */
	@Primary
	@Bean(name = "masterDataSource")
	@ConfigurationProperties(prefix = "druid.jsicp.master")
	public DataSource masterDataSource() {
		return DataSourceBuilder.create().type(dataSourceType).build();
	}
	/**
	 * 从数据库(如有多个按这种方式增加)
	 * @return
	 */	
	@Bean(name = "slaveDataSource")
	@ConfigurationProperties(prefix = "druid.jsicp.slave")
	public DataSource slaveDataSource() {
		return DataSourceBuilder.create().type(dataSourceType).build();
	}
	
	/**
	 * 从数据库(如有多个按这种方式增加)
	 * @return
	 */	
	@Bean(name = "otherDataSource")
	@ConfigurationProperties(prefix = "druid.jsicp.other")
	public DataSource otherDataSource() {
		return DataSourceBuilder.create().type(dataSourceType).build();
	}
	@Bean(name = "dataSource")
    public AbstractRoutingDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource ,
    		@Qualifier("slaveDataSource") DataSource slaveDataSource,
    		@Qualifier("otherDataSource") DataSource otherDataSource) {
        DynamicRoutingDataSource proxy = new DynamicRoutingDataSource();
        
        Map<Object, Object> targetDataResources = new HashMap<Object, Object>();
        targetDataResources.put(DynamicDbContextHolder.DbType.MASTER, masterDataSource);
        targetDataResources.put(DynamicDbContextHolder.DbType.SLAVE, slaveDataSource);      
        targetDataResources.put(DynamicDbContextHolder.DbType.OTHER, otherDataSource);      
        
        //设置默认数据源是master,没有添加注解的都是默认数据源
        proxy.setDefaultTargetDataSource(masterDataSource);
        proxy.setTargetDataSources(targetDataResources);
        // 在方法初始化之前执行
        proxy.afterPropertiesSet();
        return proxy;
    }@Bean
//    @ConfigurationProperties(prefix = "mybatis")
	@Primary
    public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dataSource") DataSource dataSource) throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource(configLocation));
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
        // 设置typeAlias 包扫描路径
        sqlSessionFactoryBean.setTypeAliasesPackage(entityPackage);

        return sqlSessionFactoryBean;
    }
	
	// 事务管理
	@Bean
	public PlatformTransactionManager annotationDrivenTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource);
	}
	
}
-----------------------------------------------------------------------------------
//实战
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().build();
    }


    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(DataSource masterDataSource, DataSource slaveDataSource){
        DynamicRoutingDataSource myRoutingDataSource = new DynamicRoutingDataSource();
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DbTypeEnum.MASTER, masterDataSource);
        targetDataSource.put(DbTypeEnum.SLAVE, slaveDataSource);

        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSource);
        return myRoutingDataSource;
    }
}

7、mybatis配置

@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:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值