原文:https://blog.csdn.net/wsbgmofo/article/details/79260896

1,数据源配置文件,如下

datasource.readSize=1
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 主数据源,默认的
spring.master.driver-class-name=com.mysql.jdbc.Driver
spring.master.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.master.username=root
spring.master.password=root
spring.master.initialSize=5
spring.master.minIdle=5
spring.master.maxActive=50
spring.master.maxWait=60000
spring.master.timeBetweenEvictionRunsMillis=60000
spring.master.minEvictableIdleTimeMillis=300000
spring.master.poolPreparedStatements=true
spring.master.maxPoolPreparedStatementPerConnectionSize=20

# 从数据源
spring.slave.driver-class-name=com.mysql.jdbc.Driver
spring.slave.url=jdbc:mysql://localhost:3306/data_source_02?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.slave.username=root
spring.slave.password=root
spring.slave.initialSize=5
spring.slave.minIdle=5
spring.slave.maxActive=50
spring.slave.maxWait=60000
spring.slave.timeBetweenEvictionRunsMillis=60000
spring.slave.minEvictableIdleTimeMillis=300000
spring.slave.poolPreparedStatements=true
spring.slave.maxPoolPreparedStatementPerConnectionSize=20

2,新建数据库配置类DataSourceConfiguration,如下

package com.aop.writeAndRead.config;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
@Configuration
public class DataSourceConfiguration {
private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
@Primary
@ConfigurationProperties(prefix = "spring.master")
public DataSource writeDataSource() {
log.info("-------------------- writeDataSource init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 有多少个从库就要配置多少个
* @return
*/
@Bean(name = "readDataSource", destroyMethod = "close", initMethod="init")
@ConfigurationProperties(prefix = "spring.slave")
public DataSource readDataSourceOne(){
log.info("-------------------- readDataSourceOne init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 这里的list是多个从库的情况下为了实现简单负载均衡
* @return
* @throws SQLException
*/
@Bean("readDataSources")
public List<DataSource> readDataSources() throws SQLException{
List<DataSource> dataSources=new ArrayList<>();
dataSources.add(readDataSourceOne());
return dataSources;
}
}

3,新建DataSourceContextHolder类,根据ThreadLocal来实现数据源的动态改变,如下

package com.aop.writeAndRead.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSourceContextHolder {
private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
private static final ThreadLocal<String> local = new ThreadLocal<String>();
public static ThreadLocal<String> getLocal() {
return local;
}
/**
* 读可能是多个库
*/
public static void read() {
local.set(DataSourceType.read.getType());
System.out.println("==:" + DataSourceType.read.getType());
log.info("数据库切换到读库...");
}
/**
* 写只有一个库
*/
public static void write() {
local.set(DataSourceType.write.getType());
log.info("数据库切换到写库...");
}
public static String getJdbcType() {
return local.get();
}
}

4,新建一个枚举类DataSourceType,如下

package com.aop.writeAndRead.config;
public enum DataSourceType {
read("read", "从库"),
write("write", "主库");
private String type;
private String name;
DataSourceType(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

5,新建MybatisConfiguration类,如下

package com.aop.writeAndRead.config;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataSourceConfiguration.class})
@MapperScan(basePackages={"com.aop.writeAndRead.mapper"})
public class MybatisConfiguration {
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Value("${datasource.readSize}")
private String dataSourceSize;
// @Resource(name = "writeDataSource")
// private DataSource writeDataSource;
// @Qualifier("readDataSource")
// private DataSource readDataSource;
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(ApplicationContext ac) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy(ac));
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mappings/**/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.aop.writeAndRead.entity");
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sqlSessionFactoryBean.getObject();
}
/**
* 有多少个数据源就要配置多少个bean
* @return
*/
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy(ApplicationContext ac) {
int size = Integer.parseInt(dataSourceSize);
System.out.println("size:" + size);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
//多个读数据库时
DataSource writeDataSource = (DataSource)ac.getBean("writeDataSource");
List<DataSource> readDataSources = (List<DataSource>)ac.getBean("readDataSources");
for (int i = 0; i < size; i++) {
targetDataSources.put(i, readDataSources.get(i));
}
proxy.setDefaultTargetDataSource(writeDataSource);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
}
把第2步注册的bean放入一个map里面,后面就可以动态从这个map里面获取对应的数据源

注意:之前报错的地方就是在这里,用@Resource和@Qualifier这两种方式都无法获取到第2步注册的bean,只能是通过applicationContext上下文获取,应该是跟注解的优先级有关,Resource和Qualifier先执行,这个时候第2步的bean还未注册,所以娶不到,如果有知道更详细原因的朋友,请留言告知

6,,新建MyAbstractRoutingDataSource,如下

package com.aop.writeAndRead.config;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource{
private final int dataSourceNumber;
private AtomicInteger count = new AtomicInteger(0);
public MyAbstractRoutingDataSource(int dataSourceNumber) {
this.dataSourceNumber = dataSourceNumber;
}
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DataSourceContextHolder.getJdbcType();
if (typeKey.equals(DataSourceType.write.getType())) {
return DataSourceType.write.getType();
}
// 读 简单负载均衡
int number = count.getAndAdd(1);
int lookupKey = number % dataSourceNumber;
return new Integer(lookupKey);
}
}
这里的determineCurrentLookupKey方法是根据DataSourceContextHolder这个类所改变的数据源而返回对应的bean的key,
这里的key要跟第5步放入map里面的key对应上

7,新建springAOP类,如下

package com.aop.writeAndRead.config;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAop {
private static Logger log = LoggerFactory.getLogger(DataSourceAop.class);
@Pointcut("@annotation(com.aop.writeAndRead.config.WriteDataSource)")
public void writeMethod(){}
@Pointcut("@annotation(com.aop.writeAndRead.config.ReadDataSource)")
public void readMethod(){}
@Before("writeMethod()")
public void beforeWrite(JoinPoint point) {
DataSourceContextHolder.write();
String className = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
System.out.println("开始执行:"+className+"."+methodName+"()方法...");
log.info("dataSource切换到:write");
}
@Before("readMethod()")
public void beforeRead(JoinPoint point) throws ClassNotFoundException {
//设置数据库为读数据
DataSourceContextHolder.read();

/*spring AOP测试代码*/
String currentClassName = point.getTarget().getClass().getName();//根据切点获取当前调用的类名
String methodName = point.getSignature().getName();//根据切点获取当前调用的类方法
Object[] args = point.getArgs();//根据切点获取当前类方法的参数
System.out.println("开始执行:"+currentClassName+"."+methodName+"()方法...");
Class reflexClassName = Class.forName(currentClassName);//根据反射获取当前调用类的实例
Method[] methods = reflexClassName.getMethods();//获取该实例的所有方法
for(Method method : methods){
if(method.getName().equals(methodName)){
String desrciption = method.getAnnotation(ReadDataSource.class).description();//获取该实例方法上注解里面的描述信息
System.out.println("desrciption:" + desrciption);
}
}

log.info("dataSource切换到:Read");
}
}
利用springAOP对方法的切入,在方法执行前判断使用哪个数据源

@Pointcut("@annotation(com.aop.writeAndRead.config.WriteDataSource)")
这里是对自定义注解作切点,双引号里面也可以换成对方法,但是个人觉得如果对方法作切点的话,如果方法多了这里写的就很长了

8,新建注解类,如下

package com.aop.writeAndRead.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadDataSource {
String description() default "";
}

package com.aop.writeAndRead.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteDataSource {
String description() default "";
}
这样在对需要控制数据源的方法前加上这个注解,springAOP就能控制这个方法,先选择数据源再执行方法

测试:

在方法上加入注解,如下

package com.aop.writeAndRead.service;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.aop.writeAndRead.config.ReadDataSource;
import com.aop.writeAndRead.config.WriteDataSource;
import com.aop.writeAndRead.entity.User;
import com.aop.writeAndRead.mapper.UserMapper;

@Service
public class UserService {

@Autowired UserMapper userMapper;

@WriteDataSource(description="WRITE")
public void writeUser(User user){
userMapper.writeUser(user);
}

@ReadDataSource(description="READ")
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true)
public Map<String, String> readUser(){
return userMapper.readUser();
}
}
接口分别调用writeUser跟readUser,如下

9,MySQL数据同步

修改主库的配置文件my.ini,在末尾加上

#数据库ID号, 为1时表示为Master,其中master_id必须为1到232–1之间的一个正整数值;
server-id = 1
#启用二进制日志;
log-bin=mysql-bin
#需要同步的二进制数据库名;
binlog-do-db=minishop
#不同步的二进制数据库名,如果不设置可以将其注释掉;
binlog-ignore-db=information_schema
binlog-ignore-db=mysql
binlog-ignore-db=personalsite
binlog-ignore-db=test
#设定生成的log文件名;
log-bin="D:/Database/materlog"
#把更新的记录写到二进制文件中;
log-slave-updates
修改从库的配置文件my.ini,在文件末尾加上

#如果需要增加Slave库则,此id往后顺延;
server-id = 2
log-bin=mysql-bin
#主库host
master-host = 192.168.168.253
#在主数据库服务器中建立的用于该从服务器备份使用的用户
master-user = forslave
master-password = ******
master-port = 3306
#如果发现主服务器断线,重新连接的时间差;
master-connect-retry=60
#不需要备份的数据库;
replicate-ignore-db=mysql
#需要备份的数据库
replicate-do-db=minishop
log-slave-update

---------------------
作者:猴样鬼相
来源:CSDN
原文:https://blog.csdn.net/wsbgmofo/article/details/79260896
版权声明:本文为博主原创文章,转载请附上博文链接!

springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14的更多相关文章

  1. centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数据库读写分离 双主搭建 mysql.history 第二十九节课

    centos  MySQL主从配置 ntsysv   chkconfig  setup命令  配置MySQL 主从 子shell  MySQL备份  kill命令  pid文件  discuz!论坛数 ...

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

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

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

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

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

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

  5. 数据库读写分离Master-Slave

    数据库读写分离Master-Slave 一个平台或系统随着时间的推移和用户量的增多,数据库操作往往会变慢,这时我们需要一些有效的优化手段来提高数据库的执行速度:如SQL优化.表结构优化.索引优化.引擎 ...

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

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

  7. CYQ.Data V5 数据库读写分离功能介绍

    前言 好多年没写关于此框架的新功能的介绍了,这些年一直在默默地更新,从Nuget上的记录就可以看出来: 这几天在看Java的一些东西,除了觉的Java和.NET的相似度实在太高之外,就是Java太原始 ...

  8. ThinkPHP v3.2.3 数据库读写分离,开启事务时报错:There is no active transaction

    如题:ThinkPHP v3.2.3 数据库读写分离,开启事务时报错: ERR: There is no active transaction 刚开始以为是数据表引擎不对造成的,因为 有几张表的引擎是 ...

  9. EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制

    回到目录 上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Co ...

随机推荐

  1. 发一个比trace功能更强大debug工具,MonterDebugger

    经常看到兄弟说trace不出东西啊,这样给你调试会带来很多不便:加入说我们需要将运行时的debug信息和之前某个版本的进行比对:又加入说我们需要在运行时通过debug动态调整显示对象的属性:查看当前整 ...

  2. Delphi XE8 TStyleBook的使用

    Delphi XE8来了,FMX的性能有了巨大的提升,比如:XE7下ListBox上下滑动的卡顿已经不复存在,直接用xe8编译后,再上下划动ListBox,已经变的非常流畅.另外,也见到有网友说,通过 ...

  3. 通过HTTP发包工具了解HTTP协议

    一.HTTP.pl功能简介 HTTP.pl perl编写的发包工具,简化版本curl,像curl致敬(唉,“致敬”都被于妈玩坏了).   该发包工具支持HEAD,GET,METHOD三种基本请求方法, ...

  4. mac osx加入全局启动terminal快捷键

    尽管有非常多第三方工具(Alfred.keyboad Maestro)能够设置全局启动terminal快捷键,但怎么感觉都不如native的好,呵呵.本文就使用mac 自带的Automator来创建一 ...

  5. idea autoscroll from source

  6. 绿化和卸载 DOS 批处理

    @ECHO OFF&PUSHD %~DP0 &TITLE 绿化和选项 mode con cols= lines= color 2F Rd >NUL Md >NUL||(Ec ...

  7. 图解aclocal、autoconf、automake、autoheader、configure

    http://www.laruence.com/2008/11/11/606.html 本文地址: http://www.laruence.com/2008/11/11/606.html 转载文章 原 ...

  8. android wifi调试(无线调试) 一步到位

    没有数据线时候,怎么进行调试开发?只要在一个局域网内,最好选择wifi调试! 网上有很多这样的教程,但是有很多步.很繁琐.最近我在gp上下载了一个软件可以实现点击一步就可以了.不需要在手机上输入任何命 ...

  9. gcc编译选项汇集

    gcc -g 调试选项(DEBUGGING OPTION)GNU CC拥有许多特别选项,既可以调试用户的程序,也可以对GCC排错: -g 以操作系统的本地格式(stabs, COFF, XCOFF,或 ...

  10. 用C开发PHP扩展 实例(基础版)

    第一步:建立扩展骨架. cd /usr/local/src/php-5.3.6/ext/ ./ext_skel --extname=laiwenhui 第二步:修改编译参数. cd php-5.3.6 ...