读写分离常见有俩种方式

  1 第一种方式比较常用就是定义2个数据库连接,一个是Master,另一个是Slave。更新数据时我们取Master,查询数据时取Slave。太过简单不做介绍。

  2 第二种方数据源式动态切换,将数据源动态织入到程序中,达到动态选择读取主库还是从库的目的。主要使用的技术是:annotation,Spring AOP ,反射。下面介绍这种方式

首先创建DatabaseConfiguration
package com.testdatasource.third.configuration.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.testdatasource.common.enums.DatasourceType;
import com.testdatasource.third.configuration.properties.DruidProperties;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
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.lookup.AbstractRoutingDataSource; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; @Configuration
@EnableAutoConfiguration
public class DatabaseConfiguration {
@Autowired
private ApplicationContext appContext; @Autowired
private DruidProperties druidProperties; @Bean(name = "readDatasource")
@Primary
@ConfigurationProperties(prefix = "read.datasource")
public DataSource readDatasource() {
return getReadDruidDataSource();
} /**
* 写入数据源连接池配置
*/
@Bean(name = "writeDatasource")
@ConfigurationProperties(prefix = "write.datasource")
public DataSource writeDatasource() {
return getWriteDruidDataSource();
} private DruidDataSource getReadDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.configR(dataSource);
return dataSource;
} private DruidDataSource getWriteDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.configW(dataSource);
return dataSource;
} //初始化数据源bean,这个bean很重要,后面事务控制也会用到
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) {
DynamicDataSource proxy = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put( DatasourceType.read.getName(), read);
targetDataSources.put(DatasourceType.write.getName(), write);
proxy.setDefaultTargetDataSource(read);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
//初始化SqlSessionFactory,将自定义的多数据源ReadWriteSplitRoutingDataSource类实例注入到工厂中
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory. setDataSource(this.roundRobinDataSouceProxy(read, write));
sqlSessionFactory.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*Mapper.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setGlobalConfig(globalConfiguration());
return sqlSessionFactory.getObject();
} @Bean
public GlobalConfiguration globalConfiguration() {
GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
conf.setLogicDeleteValue("-1");
conf.setLogicNotDeleteValue("1");
conf.setIdType(0);
conf.setDbColumnUnderline(true);
conf.setRefresh(true);
return conf;
}
}

创建 DataSourceContextHolder

package com.testdatasource.third.configuration.datasource;

import com.testdatasource.common.enums.DatasourceType;

/**
* ClassName:DataSourceContextHolder
*
* @Description : 当前线程数据源
*/
public class DataSourceContextHolder {
/**
* 默认数据源
*/
public static final String DEFAULT_DS = DatasourceType.read.getName(); private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 设置数据源名
public static void setDB(String dbType) {
contextHolder.set(dbType);
} // 获取数据源名
public static String getDB() {
return (contextHolder.get());
} // 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}

创建 DynamicDataSource

package com.testdatasource.third.configuration.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* ClassName:DynamicDataSource
*
* @Description : 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDB();
}
}

创建 MybatisPlusConfig

package com.testdatasource.third.configuration.datasource;

import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* ClassName:MybatisPlusConfig
* @Description : mybatis分页插件
* @version
*/
@Configuration
public class MybatisPlusConfig { /**
* mybatis-plus分页插件<br>
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
return paginationInterceptor;
}
}

创建 MyDataSourceTransactionManagerAutoConfiguration

package com.testdatasource.third.configuration.datasource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; /**
*
* 多数据源事物
*/
@Configuration
@EnableTransactionManagement
public class MyDataSourceTransactionManagerAutoConfiguration extends DataSourceTransactionManagerAutoConfiguration {
@Autowired
private ApplicationContext appContext;
/**
* 自定义事务
* MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
* @return
*/
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManagers() {
return new DataSourceTransactionManager((DataSource)appContext.getBean("roundRobinDataSouceProxy"));
}
}

数据源枚举类

package com.testdatasource.common.enums;

/**
* ClassName:DatasourceType
*
* @Description :数据库读写类型
* @version
*/
public enum DatasourceType {
write("write"), read("read"); private String name; private DatasourceType(String name) {
this.name = name();
} public String getName() {
return this.name;
} public void setName(String name) {
this.name = name;
}
}

数据源配置类

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import java.sql.SQLException; /**
* ClassName:DruidProperties
*
* @Description :
* druid连接池配置文件属性,说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可
* </p>
*/
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties { @Value("${spring.datasource.read_url}")
private String rurl;
@Value("${spring.datasource.read_username}")
private String rusername;
@Value("${spring.datasource.read_password}")
private String rpassword;
@Value("${spring.datasource.write_url}")
private String wurl;
@Value("${spring.datasource.write_username}")
private String wusername;
@Value("${spring.datasource.write_password}")
private String wpassword; private String driverClassName = "com.mysql.jdbc.Driver"; private Integer initialSize = 2; private Integer minIdle = 1; private Integer maxActive = 20; private Integer maxWait = 60000; private Integer timeBetweenEvictionRunsMillis = 60000; private Integer minEvictableIdleTimeMillis = 300000; private String validationQuery = "SELECT 1 FROM DUAL"; private Boolean testWhileIdle = true; private Boolean testOnBorrow = false; private Boolean testOnReturn = false; private Boolean poolPreparedStatements = true; private Integer maxPoolPreparedStatementPerConnectionSize = 20; private String filters = "stat"; public void configR(DruidDataSource dataSource) { dataSource.setUrl(rurl);
dataSource.setUsername(rusername);
// dataSource.setPassword(AESUtil.decrypt(rpassword, null));
dataSource.setPassword(AESUtils.AESDecode(null,rpassword)); dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize); // 定义初始连接数
dataSource.setMinIdle(minIdle); // 最小空闲
dataSource.setMaxActive(maxActive); // 定义最大连接数
dataSource.setMaxWait(maxWait); // 最长等待时间 // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn); // 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void configW(DruidDataSource dataSource) { dataSource.setUrl(wurl);
dataSource.setUsername(wusername);
dataSource.setPassword(AESUtils.AESDecode(null,wpassword)); dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize); // 定义初始连接数
dataSource.setMinIdle(minIdle); // 最小空闲
dataSource.setMaxActive(maxActive); // 定义最大连接数
dataSource.setMaxWait(maxWait); // 最长等待时间 // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn); // 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getRurl() {
return rurl;
} public void setRurl(String rurl) {
this.rurl = rurl;
} public String getRusername() {
return rusername;
} public void setRusername(String rusername) {
this.rusername = rusername;
} public String getRpassword() {
return rpassword;
} public void setRpassword(String rpassword) {
this.rpassword = rpassword;
} public String getWurl() {
return wurl;
} public void setWurl(String wurl) {
this.wurl = wurl;
} public String getWusername() {
return wusername;
} public void setWusername(String wusername) {
this.wusername = wusername;
} public String getWpassword() {
return wpassword;
} public void setWpassword(String wpassword) {
this.wpassword = wpassword;
} public String getDriverClassName() {
return driverClassName;
} public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
} public Integer getInitialSize() {
return initialSize;
} public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
} public Integer getMinIdle() {
return minIdle;
} public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
} public Integer getMaxActive() {
return maxActive;
} public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
} public Integer getMaxWait() {
return maxWait;
} public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
} public Integer getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
} public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
} public Integer getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
} public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
} public String getValidationQuery() {
return validationQuery;
} public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
} public Boolean getTestWhileIdle() {
return testWhileIdle;
} public void setTestWhileIdle(Boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
} public Boolean getTestOnBorrow() {
return testOnBorrow;
} public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
} public Boolean getTestOnReturn() {
return testOnReturn;
} public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
} public Boolean getPoolPreparedStatements() {
return poolPreparedStatements;
} public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
} public Integer getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
} public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
} public String getFilters() {
return filters;
} public void setFilters(String filters) {
this.filters = filters;
}
}

annotation类作用于强制写库或者读库

package com.testdatasource.common.annotation;

import com.testdatasource.common.enums.DatasourceType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; /**
* ClassName:DS
* @Description :自定义切换数据源注解
* @version
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
DatasourceType value() default DatasourceType.write;
}

切面类DynamicDataSourceAspect 用于辅助自定义切面注解切换数据源

package com.testdatasource.third.aspect;

import com.testdatasource.common.annotation.DS;
import com.testdatasource.third.configuration.datasource.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* ClassName:DynamicDataSourceAspect
*
* @Description : 自定义切面注解切换数据源
* @version
*/
@Aspect
@Component
@Order(value = 2)
public class DynamicDataSourceAspect { @Pointcut("@annotation(com.testdatasource.common.annotation.DS)")
public void ds() { } @Before("ds()")
public void beforeSwitchDS(JoinPoint point) { // 获得当前访问的class
Class<?> className = point.getTarget().getClass(); // 获得访问的方法名
String methodName = point.getSignature().getName();
// 得到方法的参数的类型
Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass); // 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value().getName();
}
} catch (Exception e) {
e.printStackTrace();
} // 切换数据源
DataSourceContextHolder.setDB(dataSource); } @After("ds()")
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDB();
}
}

yml配置

spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
read_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
read_username: root
read_password: root
write_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
write_username: root
write_password: root

好了 这样配置就没问题了。

Spring 数据库读写分离的更多相关文章

  1. [转]Spring数据库读写分离

    数据库的读写分离简单的说是把对数据库的读和写操作分开对应不同的数据库服务器,这样能有效地减轻数据库压力,也能减轻io压力. 主(master)数据库提供写操作,从(slave)数据库提供读操作,其实在 ...

  2. Spring aop应用之实现数据库读写分离

    Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ...

  3. 161220、使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  4. spring+mybatis利用interceptor(plugin)兑现数据库读写分离

    使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...

  5. Spring + Mybatis项目实现数据库读写分离

    主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...

  6. Spring+MyBatis实现数据库读写分离方案

    推荐第四种:https://github.com/shawntime/shawn-rwdb 方案1 通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactor ...

  7. 170301、使用Spring AOP实现MySQL数据库读写分离案例分析

    使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...

  8. 使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  9. 在应用层通过spring特性解决数据库读写分离

    如何配置mysql数据库的主从? 单机配置mysql主从:http://my.oschina.net/god/blog/496 常见的解决数据库读写分离有两种方案 1.应用层 http://neore ...

随机推荐

  1. 多线程统计次数问题:即count++

    场景:日志需要统计每天数据上传的次数和上传的数据量. 如果是单线程可以使用简单的int count = 0:count++,但很多情况都是多线程环境所以就不能单纯的使用count++了!!! 多线程环 ...

  2. Nginx多虚拟主机下泛域名配置

    http://www.tuicool.com/articles/F3Azuq 近上一个应用,让用户可以自定义二级域名,所以要配置一个泛域名来解析用户的自定义域名.  首先来说说nginx下的泛域名配置 ...

  3. Python 枚举

    1. 枚举的定义 首先,定义枚举要导入enum模块.枚举定义用class关键字,继承Enum类.用于定义枚举的class和定义类的class是有区别. 示例代码: from enum import E ...

  4. celery学习笔记2

    1.定义: Celery是一个异步的任务队列(也叫做分布式任务队列) 2.工作结构 Celery分为3个部分 (1)worker部分负责任务的处理,即工作进程(我的理解工作进程就是你写的python代 ...

  5. testng生成自定义html报告

    转自:https://blog.csdn.net/kdslkd/article/details/51198433 testng原生的或reportng的报告总有些不符合需要,尝试生成自定义测试报告,用 ...

  6. BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法

    BZOJ_1712_[Usaco2007 China]Summing Sums 加密_矩阵乘法 Description     那N只可爱的奶牛刚刚学习了有关密码的许多算法,终于,她们创造出了属于奶牛 ...

  7. 用PCA(主成分分析法)进行信号滤波

    用PCA(主成分分析法)进行信号滤波 此文章从我之前的C博客上导入,代码什么的可以参考matlab官方帮助文档 现在网上大多是通过PCA对数据进行降维,其实PCA还有一个用处就是可以进行信号滤波.网上 ...

  8. Java I/O流详解与应用

    注:学习之前,需要自己思考,不要盲目的去看!!!!!不然没有任何意义 I/O流(一) 1.定义:实现设备之间的传输 2.作用:实现数据的读入和写出 3.分类:根据操作分为:输入流和输出流 根据类型分为 ...

  9. java可用与串口通信的一些库

    java原生对串口的支持只有javax.comm,javax.comm比较老了,而且不支持64位系统,我在看jlibmodbus(一个java实现的modbus协议栈)的时候发现了几个可供使用的jav ...

  10. Python练习:九九乘法表

    打印 n * n 的乘法表 #打印 9*9 乘法表 def Multiplication(n): # n - 定义打印的行数 max_len = len(str((n)**2)) #计算最大值的占位( ...