springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14
原文: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的更多相关文章
- centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数据库读写分离 双主搭建 mysql.history 第二十九节课
centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数 ...
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...
- 使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- 数据库读写分离Master-Slave
数据库读写分离Master-Slave 一个平台或系统随着时间的推移和用户量的增多,数据库操作往往会变慢,这时我们需要一些有效的优化手段来提高数据库的执行速度:如SQL优化.表结构优化.索引优化.引擎 ...
- Spring aop应用之实现数据库读写分离
Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ...
- CYQ.Data V5 数据库读写分离功能介绍
前言 好多年没写关于此框架的新功能的介绍了,这些年一直在默默地更新,从Nuget上的记录就可以看出来: 这几天在看Java的一些东西,除了觉的Java和.NET的相似度实在太高之外,就是Java太原始 ...
- ThinkPHP v3.2.3 数据库读写分离,开启事务时报错:There is no active transaction
如题:ThinkPHP v3.2.3 数据库读写分离,开启事务时报错: ERR: There is no active transaction 刚开始以为是数据表引擎不对造成的,因为 有几张表的引擎是 ...
- EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制
回到目录 上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Co ...
随机推荐
- 【web 回车】web项目 注册或登录页面 回车登录无效,解决方案
解决方案: /** * 登陆按钮的点击事件 */ $("#loginID").click(function(){ var username = $("#u"). ...
- !!! # @ --- ODATA 云驱动 --- 数据库发布 RESTFUL API
Cloud Drivers ODATA 云驱动 http://www.cdata.com/cloud/ Makes on-premise & cloud data sources ea ...
- [转]Understanding Integration Services Package Configurations
本文转自:http://msdn.microsoft.com/en-us/library/cc895212.aspx Introduction With the 2008 release, SQL S ...
- (转)Android技术积累:图片异步加载
当在ListView或GridView中要加载很多图片时,很容易出现滑动时的卡顿现象,以及出现OOM导致FC(Force Close). 会出现卡顿现象主要是因为加载数据慢,要等数据加载完才能显示出来 ...
- MongoDB分片集群节点状态stateStr:RECOVERING解决
1.关闭一直处于RECOVERING状态的mongodb server /opt/mongodb/mongodb-linux-x86_64-2.4.8/bin/mongo 127.0.0.1:220 ...
- 使用LNMP常见问题解答
使用LNMP常见问题解答 一.LNMP的安装过程详解,注:绿色文字为注释,实际使用过程中没有. ##先要下载,并根据不同系统进行安装: wget -c http://soft.vpser.net/ln ...
- EEPlat 与 SOA
EEPlat具有良好的可集成性和高度的系统开放性. EEPlat中系统级的服务由业务对象的服务通过规则引擎定义的业务逻辑组织建立起来.系统级的服务和业务对象的服务都可以直接公布为WebService供 ...
- vue - check-versions.js for child_process
webpack之类的配置文件. webpack.base.conf.js
- com.android.support冲突的解决办法
All com.android.support libraries must use the exact same version specification (mixing versions can ...
- 萌新学习Python爬取B站弹幕+R语言分词demo说明
代码地址如下:http://www.demodashi.com/demo/11578.html 一.写在前面 之前在简书首页看到了Python爬虫的介绍,于是就想着爬取B站弹幕并绘制词云,因此有了这样 ...