在大数据高并发的应用场景下,为了更快的响应用户请求,读写分离是比较常见的应对方案。读写分离会使用多数据源的使用。下面记录如何搭建SpringBoot2 + Druid + Mybatis 多数据源配置以及在使用过程遇到的问题。
一、先从pom.xml入手(使用springboot 2的版本)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version></parent>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency></dependencies>
inject是java依赖注入标准。spring默认支持识别。spring自带的@Autowired的缺省情况等价于JSR-330的@Inject注解;@Qualifier的缺省的根据Bean名字注入情况等价于JSR-330的@Named注解。
二、添加读取DB的Mapper
@Mapperpublic interface AssetMapper { @Select("select * from Asset where account = #{account}") Asset queryName(String account);}
此处使用mybatis的注解功能,因此可以少省去*.xml等配置文件。
三、添加多数据源的配置参数
spring.datasource.druid.write.url=jdbc:mysql://192.168.0.110:3306/master?characterEncoding=utf-8&serverTimezone=Asia/Shanghaispring.datasource.druid.write.username=rootspring.datasource.druid.write.password=123456spring.datasource.druid.write.driver-class-name=com.mysql.cj.jdbc.Driver#spring.datasource.druid.read.url=jdbc:mysql://192.168.0.110:3306/slave1?characterEncoding=utf-8&serverTimezone=Asia/Shanghaispring.datasource.druid.read.username=rootspring.datasource.druid.read.password=123456spring.datasource.druid.read.driver-class-name=com.mysql.cj.jdbc.Driver 新版本mysql的url后面必需要添加serverTimezone=。 不然会报以下异常:2019-06-05 18:47:24.058 ERROR 17804 --- [-Create-6910184] com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://localhost:3306/master, errorCode 0, state 01S00 java.sql.SQLException: The server time zone value ‘?й???????‘ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
四、配置数据源
@Configurationpublic class DataSourceConfig { @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.druid.write") @Primary public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.druid.read") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Inject @Named("masterDataSource") private DataSource masterDataSource; @Inject @Named("slaveDataSource") private DataSource slaveDataSource; /** * 根据数据源创建SqlSessionFactory */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); return sessionFactory.getObject(); }}
SqlSessionFactory必需要重新创建,若不创建会报循环调用异常
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘masterDataSource‘: Requested bean is currently in creation: Is there an unresolvable circular reference?
因为SqlSessionFactory还是走默认创建的方式 。
上下文中如何得知使用那个数据源,可使用ThreadLocal来处理。
五、数据源路由
public class DataSourceContextRouting implements AutoCloseable { static final ThreadLocal<String> dataSourceKeyThreadLocal = new ThreadLocal<>(); public String getDataSourceName(){ String key = dataSourceKeyThreadLocal.get(); return StringUtils.isBlank(key) ?"masterDataSource":key; } public DataSourceContextRouting(String key){ dataSourceKeyThreadLocal.set(key); } @Override public void close() throws Exception { dataSourceKeyThreadLocal.remove(); }}
spring的提供动态源实现功能。只需要继承AbstractRoutingDataSource,并重写protected Object determineCurrentLookupKey()
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextRouting.getDataSourceName(); }}
//此为核心代码
@Bean
public DynamicDataSource dataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("masterDataSource", masterDataSource); targetDataSources.put("slaveDataSource", slaveDataSource); DynamicDataSource dataSource = new DynamicDataSource(); //设置数据源映射 dataSource.setTargetDataSources(targetDataSources); //设置默认数据源,当无法映射到数据源时会使用默认数据源 dataSource.setDefaultTargetDataSource(slaveDataSource); dataSource.afterPropertiesSet(); return dataSource;}
六、controller路由切换
@RequestMapping("master") public String master(String account){ String key = "masterDataSource"; new DataSourceContextRouting(key); //TODO ..... } @RequestMapping("slave") public String slave(String account){ String key = "slaveDataSource"; new DataSourceContextRouting(key); //TODO...... }
到此为止,整个多数据源配置完成了。
但这种对代码侵入比较多,可以使用注解的方式来处理。先定义注解标识
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TargetDataSource { String value(); }
使用注解那需要对此进行解析切入,因此就需要用上spring AOP的功能。
首先添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>
然后添加对其解析Aspect
@Aspect@Namedpublic class DataSourceRoutingAspect { @Around("@annotation(targetDataSource)") public Object routingWithDataSource(ProceedingJoinPoint joinPoint, TargetDataSource targetDataSource) throws Throwable { String key = targetDataSource.value(); try (DataSourceContextRouting ctx = new DataSourceContextRouting(key)) { return joinPoint.proceed(); } }}
@RequestMapping("master")
@TargetDataSource("masterDataSource")
public String master(String account){
TODO:.....
}
@RequestMapping("slave")
@TargetDataSource("slaveDataSource")
public String slave(String account){
TODO:.....
}
原文地址:https://www.cnblogs.com/song27/p/10977241.html