springboot mybatis 读写分离集成
2018-03-02 00:00
489 查看
摘要: 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。本示例,通过两种方式实现多数据源切换
背景
在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。本示例,通过两种方式实现多数据源切换
1)手动切换
2)使用注解进行切换
本文使用的是第二种方式,使用注解的方式进行切换
mybatis:
mapperLocations: classpath:mapper/*Mapper.xml
typeAliasesPackage: com.yancheng.app.domain
作用:构建一个DataSourceEnum容器,并提供了向其中设置和获取DataSorceEnum的方法
DynamicDataSource
作用:使用DatabaseContextHolder获取当前线程的DataSoureEnum
MyBatisConfig
通过读取application.properties文件生成两个数据源(masterDataSource,slaverDataSource)
使用以上生成的两个数据源构造动态数据源dataSource
@Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(一般用于多数据源的情况下)
@Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有三个DataSource类型的实例,DynamicDataSource也是一种DataSource,需要指定名称注入)
@Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)
DataSourceTypeAnno数据源类型注解
DataSourceAspect 数据源切面
用于捕获使用数据源注解的方法,并且根据注解上的数据源类型进行切换
觉得本文好的可以点个赞,觉得不好的可以提提建议或意见,一起进步,一起成长!!!
欢迎邮件来信讨论 490514142@qq.com
背景
在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。本示例,通过两种方式实现多数据源切换
1)手动切换
2)使用注解进行切换
本文使用的是第二种方式,使用注解的方式进行切换
一. 准备
1 maven依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.springboot</groupId> <artifactId>multi-datasource</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.41</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version> </dependency> <!-- 使用aspectj时需要 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2 配置
application.ymldruid: type: com.alibaba.druid.pool.DruidDataSource write: url: jdbc:mysql://127.0.0.1:3306/shike?zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver initial-size: 5 min-idle: 1 max-active: 100 test-on-borrow: true connection-test-query: "SELECT 1" read1: url: jdbc:mysql://127.0.0.1:3306/shike1?zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver initial-size: 5 min-idle: 1 max-active: 100 test-on-borrow: true connection-test-query: "SELECT 1" read2: url: jdbc:mysql://127.0.0.1:3306/shike2?zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=utf8&useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver initial-size: 5 min-idle: 1 max-active: 100 test-on-borrow: true connection-test-query: "SELECT 1"
mybatis:
mapperLocations: classpath:mapper/*Mapper.xml
typeAliasesPackage: com.yancheng.app.domain
工具类
DataSourceContextHolder作用:构建一个DataSourceEnum容器,并提供了向其中设置和获取DataSorceEnum的方法
package com.yanchengtech.app.dataSource; /** * @author 文心雕龙 Email:xuwenlong@shike8888.com * @version 创建时间:2017年10月18日 下午2:47:32 类说明 */ public class DbContextHolder { //列举数据源的key public enum DbType { WRITE, READ1,READ2 } // private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>(); private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>() { @Override protected DbType initialValue() { return DbType.WRITE; } }; public static void setDbType(DbType dbType) { if (dbType == null) throw new NullPointerException(); contextHolder.set(dbType); } public static DbType getDbType() { return contextHolder.get() == null ? DbType.WRITE : contextHolder.get(); } public static void resetDbType(){ contextHolder.set(DbType.WRITE); } public static void clearDbType() { contextHolder.remove(); } }
DynamicDataSource
作用:使用DatabaseContextHolder获取当前线程的DataSoureEnum
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { return DbContextHolder.getDbType(); }
MyBatisConfig
通过读取application.properties文件生成两个数据源(masterDataSource,slaverDataSource)
使用以上生成的两个数据源构造动态数据源dataSource
@Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(一般用于多数据源的情况下)
@Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有三个DataSource类型的实例,DynamicDataSource也是一种DataSource,需要指定名称注入)
@Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)
import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.log4j.Logger; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import com.yanchengtech.app.dataSource.DbContextHolder.DbType; import com.yanchengtech.app.dataSource.DynamicDataSource; /** * @author 文心雕龙 Email:xuwenlong@shike8888.com * @version 创建时间:2017年10月18日 下午2:42:53 * 类说明 */ @Configuration @MapperScan("com.yanchengtech.app.mapper") public class MybatisConfig { // private static Logger logger = Logger.getLogger(MybatisConfig.class); @Value("${druid.type}") private Class<? extends DataSource> dataSourceType; @Primary @Bean(name = "writeDataSource") @ConfigurationProperties(prefix = "druid.write") public DataSource writeDataSource(){ return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean(name = "readDataSource1") @ConfigurationProperties(prefix = "druid.read1") public DataSource readDataSource(){ return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean(name = "readDataSource2") @ConfigurationProperties(prefix = "druid.read2") public DataSource readDataSource(){ return DataSourceBuilder.create().type(dataSourceType).build(); } /** * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例) */ @Bean("dynamicDataSource") public DynamicDataSource dynamicDataSource(@Qualifier("writeDataSource") DataSource writeDataSource, @Qualifier("readDataSource") DataSource readDataSource) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); targetDataSources.put(DbType.WRITE, writeDataSource); targetDataSources.put(DbType.READ1, readDataSource1); targetDataSources.put(DbType.READ2, readDataSource2); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法 dataSource.setDefaultTargetDataSource(writeDataSource);// 默认的datasource设置为myTestDbDataSource return dataSource; } /** * 根据数据源创建SqlSessionFactory */ @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource, @Value("${mybatis.typeAliasesPackage}") String typeAliasesPackage, @Value("${mybatis.mapperLocations}") String mapperLocations) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dynamicDataSource);// 指定数据源(这个必须有,否则报错) // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加 factoryBean.setTypeAliasesPackage(typeAliasesPackage);// 指定基包 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));// return factoryBean.getObject(); } /** * 配置事务管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } }
DataSourceTypeAnno数据源类型注解
@Retention(RetentionPolicy.RUNTIME) 说是此注解在运行时可见 @Target(ElementType.METHOD) // 注解可以用在方法上 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.yanchengtech.app.dataSource.DbContextHolder.DbType; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ReadOnlyConnection { DbType value() default DbContextHolder.DbType.READ; }
DataSourceAspect 数据源切面
用于捕获使用数据源注解的方法,并且根据注解上的数据源类型进行切换
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.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import com.yanchengtech.app.dataSource.DbContextHolder.DbType; import java.lang.reflect.Method; @Component @Aspect public class DataSourceAspect { @Pointcut("@annotation(com.yanchengtech.app.dataSource.ReadOnlyConnection)") public void dataSourcePointcut() { } @Around("dataSourcePointcut()") public Object doAround(ProceedingJoinPoint pjp) { MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method method = methodSignature.getMethod(); ReadOnlyConnection typeAnno = method.getAnnotation(ReadOnlyConnection.class); DbType sourceEnum = typeAnno.value(); if (sourceEnum == DbType.WRITE) { DbContextHolder.setDbType(DbType.WRITE); } else if (sourceEnum == DbType.READ) { DbContextHolder.setDbType(DbType.READ); } Object result = null; try { result = pjp.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { DbContextHolder.resetDbType(); } return result; } }
测试:
代码:
Controller:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.yanchengtech.app.service.PosterService; @RestController @RequestMapping("poster") public class PosterCotroller { @Autowired PosterService posterService; @RequestMapping("getAllPoster") private String getAllPoster() { return posterService.findAllPoster(); } @RequestMapping("getAllPoster2") private String getAllPoster2() { return posterService.findAllPoster2(); } @RequestMapping("getAllPoster1") private String getAllPoster1() { return posterService.findAllPoster1(); } }
Service:
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSONObject; import com.yanchengtech.app.dataSource.DbContextHolder.DbType; import com.yanchengtech.app.dataSource.ReadOnlyConnection; import com.yanchengtech.app.domain.Poster; import com.yanchengtech.app.mapper.PosterMapper; import com.yanchengtech.app.service.PosterService; @Service public class PosterServiceImpl implements PosterService{ @Autowired PosterMapper posterMapper; @Override // 默认使用的是主数据库 public String findAllPoster() { JSONObject object = new JSONObject(); List<Poster> list = posterMapper.findAllPoster(); if (null != list && list.size()>0) { object.put("code", 1); object.put("msg", "success"); object.put("data", list); }else{ object.put("code", 0); object.put("msg", "fail"); object.put("data", null); } return object.toString(); } @Override @ReadOnlyConnection(DbType.READ1) public String findAllPoster1() { JSONObject object = new JSONObject(); List<Poster> list = posterMapper.findAllPoster(); if (null != list && list.size()>0) { object.put("code", 1); object.put("msg", "success"); object.put("data", list); }else{ object.put("code", 0); object.put("msg", "fail"); object.put("data", null); } return object.toString(); } @Override @ReadOnlyConnection(DbType.READ2) public String findAllPoster2() { JSONObject object = new JSONObject(); List<Poster> list = posterMapper.findAllPoster(); if (null != list && list.size()>0) { object.put("code", 1); object.put("msg", "success"); object.put("data", list); }else{ object.put("code", 0); object.put("msg", "fail"); object.put("data", null); } return object.toString(); } }
Mapper
import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import com.yanchengtech.app.domain.Poster; @Mapper public interface PosterMapper { @Select("select * from poster where delFlag = 1") List<Poster> findAllPoster(); }
觉得本文好的可以点个赞,觉得不好的可以提提建议或意见,一起进步,一起成长!!!
欢迎邮件来信讨论 490514142@qq.com
相关文章推荐
- Spring Boot 集成MyBatis
- SpringBoot_集成MyBatis(XML方式)
- springboot集成mybatis实例代码
- 微服务开发的入门级框架Spring Boot详解:集成Mybatis
- Spring Boot(六)集成 MyBatis 操作 MySQL 8
- Spring Boot 集成 Mybatis
- Spring Boot 集成Mybatis操作数据库
- Spring Boot 集成 MyBatis
- SpringBoot Mybatis之读写分离
- Spring Boot入门——集成Mybatis
- 【Spring Boot】Spring Boot 集成MyBatis
- springboot之集成mybatis之多数据源
- spring boot 2.0,netty,mybatis,mysql,redis,docker 集成
- springboot的mybatis集成分页插件PageHelper
- spring-boot 速成(9) druid+mybatis 多数据源及读写分离的处理
- SpringBoot集成MyBatis
- SpringBoot入门-16(springboot集成mybatis注解形式)
- SpringBoot学习-(8)集成mybatis
- spring boot集成mybatis,启动报无法创建dataSource问题
- SpringCloud SpringBoot mybatis 分布式微服务(二十八)集成spring cache