时代网站管理系统怎么做网站,个人介绍微电影网站模板,慈溪建设集团网站,服装定制官网1. 引言读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行#xff0c;至于谁来做选择数据库这件事儿#xff0c;无非两个#xff0c;要么中间件帮我们做#xff0c;要么程序自己做。因此#xff0c;一般来讲#xff0c;读写分离有两种实现方式。第一种是依靠中间…1. 引言读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行至于谁来做选择数据库这件事儿无非两个要么中间件帮我们做要么程序自己做。因此一般来讲读写分离有两种实现方式。第一种是依靠中间件(比如MyCat)也就是说应用程序连接到中间件中间件帮我们做SQL分离第二种是应用程序自己去做分离。这里我们选择程序自己来做主要是利用Spring提供的路由数据源以及AOP然而应用程序层面去做读写分离最大的弱点(不足之处)在于无法动态增加数据库节点因为数据源配置都是写在配置中的新增数据库意味着新加一个数据源必然改配置并重启应用。当然好处就是相对简单。2. AbstractRoutingDataSource基于特定的查找key路由到特定的数据源。它内部维护了一组目标数据源并且做了路由key与目标数据源之间的映射提供基于key查找数据源的方法。3. 实践3.1. maven依赖xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd4.0.0com.cjs.examplecjs-datasource-demo0.0.1-SNAPSHOTjarcjs-datasource-demoorg.springframework.bootspring-boot-starter-parent2.0.5.RELEASEUTF-8UTF-81.8org.springframework.bootspring-boot-starter-aoporg.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-weborg.mybatis.spring.bootmybatis-spring-boot-starter1.3.2org.apache.commonscommons-lang33.8mysqlmysql-connector-javaruntimeorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-plugin3.2. 数据源配置application.ymlspring:datasource:master:jdbc-url: jdbc:mysql://192.168.102.31:3306/testusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverslave1:jdbc-url: jdbc:mysql://192.168.102.56:3306/testusername: pig # 只读账户password: 123456driver-class-name: com.mysql.jdbc.Driverslave2:jdbc-url: jdbc:mysql://192.168.102.36:3306/testusername: pig # 只读账户password: 123456driver-class-name: com.mysql.jdbc.Driver多数据源配置package com.cjs.example.config;import com.cjs.example.bean.MyRoutingDataSource;import com.cjs.example.enums.DBTypeEnum;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;/*** 关于数据源配置参考SpringBoot官方文档第79章《Data Access》* 79. Data Access* 79.1 Configure a Custom DataSource* 79.2 Configure Two DataSources*/Configurationpublic class DataSourceConfig {BeanConfigurationProperties(spring.datasource.master)public DataSource masterDataSource() {return DataSourceBuilder.create().build();}BeanConfigurationProperties(spring.datasource.slave1)public DataSource slave1DataSource() {return DataSourceBuilder.create().build();}BeanConfigurationProperties(spring.datasource.slave2)public DataSource slave2DataSource() {return DataSourceBuilder.create().build();}Beanpublic DataSource myRoutingDataSource(Qualifier(masterDataSource) DataSource masterDataSource,Qualifier(slave1DataSource) DataSource slave1DataSource,Qualifier(slave2DataSource) DataSource slave2DataSource) {Map targetDataSources new HashMap();targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);MyRoutingDataSource myRoutingDataSource new MyRoutingDataSource();myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);myRoutingDataSource.setTargetDataSources(targetDataSources);return myRoutingDataSource;}}这里我们配置了4个数据源1个master2两个slave1个路由数据源。前3个数据源都是为了生成第4个数据源而且后续我们只用这最后一个路由数据源。MyBatis配置package com.cjs.example.config;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;import javax.sql.DataSource;EnableTransactionManagementConfigurationpublic class MyBatisConfig {Resource(name myRoutingDataSource)private DataSource myRoutingDataSource;Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(myRoutingDataSource);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath:mapper/*.xml));return sqlSessionFactoryBean.getObject();}Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(myRoutingDataSource);}}由于Spring容器中现在有4个数据源所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。3.3 设置路由key / 查找数据源目标数据源就是那前3个这个我们是知道的但是使用的时候是如果查找数据源的呢首先我们定义一个枚举来代表这三个数据源package com.cjs.example.enums;public enum DBTypeEnum {MASTER, SLAVE1, SLAVE2;}接下来通过ThreadLocal将数据源设置到每个线程上下文中package com.cjs.example.bean;import com.cjs.example.enums.DBTypeEnum;import java.util.concurrent.atomic.AtomicInteger;public class DBContextHolder {private static final ThreadLocal contextHolder new ThreadLocal();private static final AtomicInteger counter new AtomicInteger(-1);public static void set(DBTypeEnum dbType) {contextHolder.set(dbType);}public static DBTypeEnum get() {return contextHolder.get();}public static void master() {set(DBTypeEnum.MASTER);System.out.println(切换到master);}public static void slave() {// 轮询int index counter.getAndIncrement() % 2;if (counter.get() 9999) {counter.set(-1);}if (index 0) {set(DBTypeEnum.SLAVE1);System.out.println(切换到slave1);}else {set(DBTypeEnum.SLAVE2);System.out.println(切换到slave2);}}}获取路由keypackage com.cjs.example.bean;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.lang.Nullable;public class MyRoutingDataSource extends AbstractRoutingDataSource {NullableOverrideprotected Object determineCurrentLookupKey() {return DBContextHolder.get();}}设置路由key默认情况下所有的查询都走从库插入/修改/删除走主库。我们通过方法名来区分操作类型(CRUD)package com.cjs.example.aop;import com.cjs.example.bean.DBContextHolder;import org.apache.commons.lang3.StringUtils;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.springframework.stereotype.Component;AspectComponentpublic class DataSourceAop {Pointcut(!annotation(com.cjs.example.annotation.Master) (execution(* com.cjs.example.service..*.select*(..)) || execution(* com.cjs.example.service..*.get*(..))))public void readPointcut() {}Pointcut(annotation(com.cjs.example.annotation.Master) || execution(* com.cjs.example.service..*.insert*(..)) || execution(* com.cjs.example.service..*.add*(..)) || execution(* com.cjs.example.service..*.update*(..)) || execution(* com.cjs.example.service..*.edit*(..)) || execution(* com.cjs.example.service..*.delete*(..)) || execution(* com.cjs.example.service..*.remove*(..)))public void writePointcut() {}Before(readPointcut())public void read() {DBContextHolder.slave();}Before(writePointcut())public void write() {DBContextHolder.master();}/*** 另一种写法if...else... 判断哪些需要读从数据库其余的走主数据库*/// Before(execution(* com.cjs.example.service.impl.*.*(..)))// public void before(JoinPoint jp) {// String methodName jp.getSignature().getName();//// if (StringUtils.startsWithAny(methodName, get, select, find)) {// DBContextHolder.slave();// }else {// DBContextHolder.master();// }// }}有一般情况就有特殊情况特殊情况是某些情况下我们需要强制读主库针对这种情况我们定义一个主键用该注解标注的就读主库package com.cjs.example.annotation;public interface Master {}例如假设我们有一张表memberpackage com.cjs.example.service.impl;import com.cjs.example.annotation.Master;import com.cjs.example.entity.Member;import com.cjs.example.entity.MemberExample;import com.cjs.example.mapper.MemberMapper;import com.cjs.example.service.MemberService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;Servicepublic class MemberServiceImpl implements MemberService {Autowiredprivate MemberMapper memberMapper;TransactionalOverridepublic int insert(Member member) {return memberMapper.insert(member);}MasterOverridepublic int save(Member member) {return memberMapper.insert(member);}Overridepublic List selectAll() {return memberMapper.selectByExample(new MemberExample());}MasterOverridepublic String getToken(String appId) {// 有些读操作必须读主数据库// 比如获取微信access_token因为高峰时期主从同步可能延迟// 这种情况下就必须强制从主数据读return null;}}4. 测试package com.cjs.example;import com.cjs.example.entity.Member;import com.cjs.example.service.MemberService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;RunWith(SpringRunner.class)SpringBootTestpublic class CjsDatasourceDemoApplicationTests {Autowiredprivate MemberService memberService;Testpublic void testWrite() {Member member new Member();member.setName(zhangsan);memberService.insert(member);}Testpublic void testRead() {for (int i 0; i 4; i) {memberService.selectAll();}}Testpublic void testSave() {Member member new Member();member.setName(wangwu);memberService.save(member);}Testpublic void testReadFromMaster() {memberService.getToken(1234);}}查看控制台5. 工程结构6. 参考https://www.jianshu.com/p/f2f4256a2310http://www.cnblogs.com/gl-developer/p/6170423.htmlhttps://www.cnblogs.com/huangjuncong/p/8576935.htmlhttps://blog.csdn.net/liu976180578/article/details/77684583推荐学习资料分享12 套 微服务、Spring Boot、Spring Cloud 核心技术资料这是部分资料目录Spring Security 认证与授权Spring Boot 项目实战(中小型互联网公司后台服务架构与运维架构)Spring Boot 项目实战(企业权限管理项目))Spring Cloud 微服务架构项目实战(分布式事务解决方案)...公众号后台回复arch028获取资料