java(spring boot 读写分离)双数据源,注解形式动态代理数据源
发布时间:2020-12-15 07:18:33 所属栏目:Java 来源:网络整理
导读:应用场景是我们一个线上项目,因为客户需要很高的并发量的导出excle文档,然后对于报表的SQL来说,因为数据量的原因,查询的时间相对来说是比较长的, 在SQL执行过程中,项目还在实时的运营,为了不影响运营,我们决定采用数据库读写分离,避免锁表以及并发
应用场景是我们一个线上项目,因为客户需要很高的并发量的导出excle文档,然后对于报表的SQL来说,因为数据量的原因,查询的时间相对来说是比较长的, 在SQL执行过程中,项目还在实时的运营,为了不影响运营,我们决定采用数据库读写分离,避免锁表以及并发效率上的影响。 ? 这个例子写在spring boot + mybatis的架构上 使用的是application.properties如果需要使用yml转换一下格式就好了 #连接池 database.type=com.alibaba.druid.pool.DruidDataSource #可读写数据库 database.d0.url=jdbc:mysql://10.10.59.10/test database.d0.username=root database.d0.password=root database.d0.driver-class-name=com.mysql.jdbc.Driver #只读数据库 database.d1.url=jdbc:mysql://10.10.59.11/test database.d1.username=root database.d1.password=root database.d1.driver-class-name=com.mysql.jdbc.Driver #Mapper配置文件 mybatis.mapper-locations=classpath*:/mapper/*Mapper.xml 首先我们来准备几个基础类 ReadWriteSplitRoutingDataSource.class 继承了 AbstractRoutingDataSource,这个类主要管理我们的多个数据源之间的路由 /** * 数据源路由 */ class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataBaseContextHolder.getDataBaseType(); } } DataBaseContextHolder.class 自定义用来管理调配数据源的类 用 ThreadLocal解决了多线程情况下数据源错乱的问题 /** * 调配数据源 */ public class DataBaseContextHolder { //区分主从数据源 public enum DataBaseType { D0,D1 } //线程局部变量 private static final ThreadLocal<DataBaseType> contextHolder = new ThreadLocal<>(); //往线程里边set数据类型 public static void setDataBaseType(DataBaseType dataBaseType) { contextHolder.set(dataBaseType); } //从容器中获取数据类型 public static DataBaseType getDataBaseType() { return contextHolder.get() == null ? DataBaseType.D0 : contextHolder.get(); } //清空容器中的数据类型 public static void clearDataBaseType() { contextHolder.remove(); } } 然后开始配置数据源 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * 配置数据源 */ @Configuration public class DataSourceConfiguration { private static Logger LOGGER = LoggerFactory.getLogger(DataSourceConfiguration.class); //连接池 @Value("${database.type}") private Class<? extends DataSource> dataSourceType; /** * 可读写数据源 * * @return d0DataSource */ @Bean(name = "d0DataSource") @ConfigurationProperties(prefix = "database.d0") public DataSource d0DataSource() { DataSource d0DataSource = DataSourceBuilder.create().type(dataSourceType).build(); LOGGER.info("========D0: {}=========",d0DataSource); return d0DataSource; } /** * 只读数据源 * * @return d1DataSource */ @Bean(name = "d1DataSource") @ConfigurationProperties(prefix = "database.d1") public DataSource d1DataSource() { DataSource d1DataSource = DataSourceBuilder.create().type(dataSourceType).build(); LOGGER.info("========D1: {}=========",d1DataSource); return d1DataSource; } /** * 把两个数据源装配在一起 这里不要用Autowired 会报错 要用Qualifier * * @param d0DataSource * @param d1DataSource * @return myRoutingDataSource */ @Bean public DataSource myRoutingDataSource(@Qualifier("d0DataSource") DataSource d0DataSource,@Qualifier("d1DataSource") DataSource d1DataSource) { Map<Object,Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataBaseContextHolder.DataBaseType.D0,d0DataSource); targetDataSources.put(DataBaseContextHolder.DataBaseType.D1,d1DataSource); ReadWriteSplitRoutingDataSource myRoutingDataSource = new ReadWriteSplitRoutingDataSource(); //设置默认数据源 myRoutingDataSource.setDefaultTargetDataSource(d0DataSource); //设置双数据源 myRoutingDataSource.setTargetDataSources(targetDataSources); return myRoutingDataSource; } } ?数据源配置好了,接下来配置Mybatis import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.annotation.Resource; import javax.sql.DataSource; /** * Mybatis配置 给sqlSessionFactory绑定双数据源 */ //开始事务 @EnableTransactionManagement @Configuration public class MybatisConfiguration { //双数据源 @Resource(name = "myRoutingDataSource") private DataSource myRoutingDataSource; //Mapper文件包路径 @Value("${mybatis.mapper-locations}") private String mybatisLocations; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); //设置双数据源 sqlSessionFactoryBean.setDataSource(myRoutingDataSource); //设置mapper文件路径 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mybatisLocations)); return sqlSessionFactoryBean.getObject(); } @Bean public PlatformTransactionManager platformTransactionManager() { //配置数据源事务管理 return new DataSourceTransactionManager(myRoutingDataSource); } } 接下来就开始配置我们的注解和切面 自定义一个注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //该注解应用在方法上 @Target({ElementType.METHOD,ElementType.TYPE}) //在运行时运行 @Retention(RetentionPolicy.RUNTIME) public @interface ReadOnlyDB { } 写一个切面来切这个注解 因为这个切面比较特殊,需要的优先级很高,所以需要继承Ordered类 设置一个优先级 import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Aspect @Component public class ReadOnlyConnectionInterceptor implements Ordered { public static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class); /** * 切入点 */ @Pointcut("@annotation(com.beyondsoft.common.datasource.ReadOnlyDB)") public void msPointCut() { } //Around 我们从程序进入就开始处理,直至结束 @Around("msPointCut()") public Object proceed(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { try { LOGGER.info("---------------update database D1---------------"); DataBaseContextHolder.setDataBaseType(DataBaseContextHolder.DataBaseType.D1); //方法执行完毕 Object result = proceedingJoinPoint.proceed(); return result; } finally { DataBaseContextHolder.clearDataBaseType(); LOGGER.info("---------------end database D1---------------"); } } @Override public int getOrder() { //最高优先级 return Ordered.HIGHEST_PRECEDENCE; } } 使用方法就是在你需要开启只读的service上注解上我们刚才定义的 @ReadOnlyDB 启动过程中可以看到数据源加载成功 ? ? ?数据源成功路由的效果如下图 ? ? 如果大家有什么不解,或意见,欢迎在下方留言,楼主看到就会回复的,谢谢。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |