您的位置:首页 > 编程语言 > Java开发

springboot mybatis 读写分离集成

2018-03-02 00:00 489 查看
摘要: 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。本示例,通过两种方式实现多数据源切换

背景

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

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.yml

druid:

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