亲手教你SpringBoot中的多数据源集成问题
作者:java不简单
引言
其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,eg:MyCAT,Cobar,sharding-JDBC等。
本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化。
应用场景
假设目前我们有以下几种数据访问的场景:
1.一个业务逻辑中对不同的库进行数据的操作(可能你们系统不存在这种场景,目前都时微服务的架构,每个微服务基本上是对应一个数据库比较多,其他的需要通过服务来方案。),
2.访问分库分表的场景;后面的文章会单独介绍下分库分表的集成。
假设这里,我们以6个库,每个库1000张表的例子来介绍),因为随着业务量的增长,一个库很难抗住这么大的访问量。比如说订单表,我们可以根据userid进行分库分表。
分库策略:userId%6[表的数量];
分库分表策略:库路由[userId/(6*1000)/1000],表路由[userId/(6*1000)%1000].
集成方案
方案总览:
方案是基于springjdbc中提供的api实现,看下下面两段代码,是我们的切入点,我们都是围绕着这2个核心方法进行集成的。
第一段代码是注册逻辑,会将defaultTargetDataSource和targetDataSources这两个数据源对象注册到resolvedDataSources中。
第二段是具体切换逻辑: 如果数据源为空,那么他就会找我们的默认数据源defaultTargetDataSource,如果设置了数据源,那么他就去读这个值Object lookupKey = determineCurrentLookupKey();我们后面会重写这个方法。
我们会在配置文件中配置主数据源(默认数据源)和其他数据源,然后在应用启动的时候注册到spring容器中,分别给defaultTargetDataSource和targetDataSources进行赋值,进而进行Bean注册。
真正的使用过程中,我们定义注解,通过切面,定位到当前线程是由访问哪个数据源(维护着一个ThreadLocal的对象),进而调用determineCurrentLookupKey方法,进行数据源的切换。
public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); return dataSource;
1.动态数据源注册注册默认数据源以及其他额外的数据源; 这里只是复制了核心的几个方法,具体的大家下载git看代码。
// 创建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DyncRouteDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //默认数据源 mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); //其他数据源 mpv.addPropertyValue("targetDataSources", targetDataSources); //注册到spring bean容器中 registry.registerBeanDefinition("dataSource", beanDefinition);
2.在Application中加载动态数据源,这样spring容器启动的时候就会将数据源加载到内存中。
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class }) @Import(DyncDataSourceRegister.class) @ServletComponentScan @ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3.数据源切换核心逻辑:创建一个数据源切换的注解,并且基于该注解实现切面逻辑,这里我们通过threadLocal来实现线程间的数据源切换;
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); // 是否分库 boolean isSharding() default false; // 获取分库策略 String strategy() default ""; } // 动态数据源上下文 public class DyncDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceNames = new ArrayList<String>(); public static void setDataSource(String dataSourceName) { contextHolder.set(dataSourceName); } public static String getDataSource() { return contextHolder.get(); public static void clearDataSource() { contextHolder.remove(); public static boolean containsDataSource(String dataSourceName) { return dataSourceNames.contains(dataSourceName); /** * * @author jasoHsu * 动态数据源在切换,将数据源设置到ThreadLocal对象中 */ @Component @Order(-10) @Aspect public class DyncDataSourceInterceptor { @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception { String dbIndx = null; String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx); if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) { DyncDataSourceContextHolder.setDataSource(targetDataSourceName); } @After("@annotation(targetDataSource)") public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) { DyncDataSourceContextHolder.clearDataSource(); // public class DyncRouteDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DyncDataSourceContextHolder.getDataSource(); public DataSource findTargetDataSource() { return this.determineTargetDataSource();
4.与mybatis集成,其实这一步与前面的基本一致。
只是将在sessionFactory以及数据管理器中,将动态数据源注册进去【dynamicRouteDataSource】,而非之前的静态数据源【getMasterDataSource()】
@Resource DyncRouteDataSource dynamicRouteDataSource; @Bean(name = "sqlSessionFactory") public SqlSessionFactory getinvestListingSqlSessionFactory() throws Exception { String configLocation = "classpath:/conf/mybatis/configuration.xml"; String mapperLocation = "classpath*:/com/happy/springboot/service/dao/*/*Mapper.xml"; SqlSessionFactory sqlSessionFactory = createDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation, mapperLocation); return sqlSessionFactory; } @Bean(name = "txManager") public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dynamicRouteDataSource);
5.核心配置参数:
spring: dataSource: happyboot: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true username: root password: admin extdsnames: happyboot01,happyboot02 happyboot01: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true username: root password: admin happyboot02: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true username: root password: admin
6.应用代码
@Service public class UserExtServiceImpl implements IUserExtService { @Autowired private UserInfoMapper userInfoMapper; @TargetDataSource(value="happyboot01") @Override public UserInfo getUserBy(Long id) { return userInfoMapper.selectByPrimaryKey(id); } }
到此这篇关于关于SpringBoot中的多数据源集成的文章就介绍到这了,更多相关SpringBoot多数据源集成内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!