springboot mybatis 读写分离集成

背景

    在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。本示例,通过两种方式实现多数据源切换 

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>2.1.3.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>2.0.0</version> 
   </dependency> 
   <dependency> 
      <groupId>mysql</groupId> 
      <artifactId>mysql-connector-java</artifactId> 
      <version>runtime</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.yml

 

spring:
    datasource:
#        测试环境
      type: com.alibaba.druid.pool.DruidDataSource
      shike:
        url: jdbc:mysql://***.**.**.*:3306/shike?zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
        username: root
        password: ******
        #springboot 1.*使用com.mysql.jdbc.Driver;2.*的使用com.mysql.cj.jdbc.Driver
        driver-class-name: com.mysql.cj.jdbc.Driver
        initial-size: 5
        min-idle: 1
        max-active: 100
        test-on-borrow: true
        connection-test-query: "SELECT 1"
      gift:
        url: jdbc:mysql://***.**.**.*:3306/gift_db?zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
        username: root
        password: ******
        driver-class-name: com.mysql.cj.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();

}

springboot2.0 注解不仅可以在service层使用,也可以使用在dao/mapper中的方法上.

springboot1.* 只能注解在service层才能生效,且必须加上:

      <dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.5</version> <!-- 自行使用适用版本 -->
		</dependency>

    觉得本文好的可以点个赞,觉得不好的可以提提建议或意见,一起进步,一起成长!!!

    欢迎邮件来信讨论 490514142@qq.com

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/wxdl/blog/1628007

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值