From e0454693e484d7d209efb98c2a5f20383bd08f35 Mon Sep 17 00:00:00 2001 From: fxb Date: Sat, 8 Sep 2018 22:22:05 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=BB=E5=86=99=E5=88=86=E7=A6=BB=E6=A0=B7?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dxfl/pom.xml | 73 +++++++++++++ .../com/example/dxfl/DxflApplication.java | 14 +++ .../example/dxfl/config/DbContextHolder.java | 37 +++++++ .../config/MyAbstractRoutingDataSource.java | 34 ++++++ .../com/example/dxfl/config/ReadOnly.java | 20 ++++ .../dxfl/config/ReadOnlyInterceptor.java | 37 +++++++ .../config/WriteOrReadDatabaseConfig.java | 103 ++++++++++++++++++ .../dxfl/controller/TestController.java | 44 ++++++++ .../java/com/example/dxfl/entity/Test.java | 28 +++++ .../com/example/dxfl/mapper/TestMapper.java | 26 +++++ .../com/example/dxfl/service/TestService.java | 63 +++++++++++ .../com/example/dxfl/util/NumberUtil.java | 14 +++ dxfl/src/main/resources/application.yml | 17 +++ dxfl/src/main/resources/mapper/test.xml | 14 +++ dxfl/src/main/resources/mybatis-config.xml | 36 ++++++ .../example/dxfl/DxflApplicationTests.java | 16 +++ .../com/example/dxfl/TestServiceTest.java | 32 ++++++ 17 files changed, 608 insertions(+) create mode 100644 dxfl/pom.xml create mode 100644 dxfl/src/main/java/com/example/dxfl/DxflApplication.java create mode 100644 dxfl/src/main/java/com/example/dxfl/config/DbContextHolder.java create mode 100644 dxfl/src/main/java/com/example/dxfl/config/MyAbstractRoutingDataSource.java create mode 100644 dxfl/src/main/java/com/example/dxfl/config/ReadOnly.java create mode 100644 dxfl/src/main/java/com/example/dxfl/config/ReadOnlyInterceptor.java create mode 100644 dxfl/src/main/java/com/example/dxfl/config/WriteOrReadDatabaseConfig.java create mode 100644 dxfl/src/main/java/com/example/dxfl/controller/TestController.java create mode 100644 dxfl/src/main/java/com/example/dxfl/entity/Test.java create mode 100644 dxfl/src/main/java/com/example/dxfl/mapper/TestMapper.java create mode 100644 dxfl/src/main/java/com/example/dxfl/service/TestService.java create mode 100644 dxfl/src/main/java/com/example/dxfl/util/NumberUtil.java create mode 100644 dxfl/src/main/resources/application.yml create mode 100644 dxfl/src/main/resources/mapper/test.xml create mode 100644 dxfl/src/main/resources/mybatis-config.xml create mode 100644 dxfl/src/test/java/com/example/dxfl/DxflApplicationTests.java create mode 100644 dxfl/src/test/java/com/example/dxfl/TestServiceTest.java diff --git a/dxfl/pom.xml b/dxfl/pom.xml new file mode 100644 index 0000000..61b9d6f --- /dev/null +++ b/dxfl/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.example + dxfl + 0.0.1-SNAPSHOT + jar + + dxfl + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 1.3.2 + + + com.alibaba + druid + 1.0.9 + + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/dxfl/src/main/java/com/example/dxfl/DxflApplication.java b/dxfl/src/main/java/com/example/dxfl/DxflApplication.java new file mode 100644 index 0000000..f28d3e0 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/DxflApplication.java @@ -0,0 +1,14 @@ +package com.example.dxfl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@EnableTransactionManagement(order = 10) +public class DxflApplication { + + public static void main(String[] args) { + SpringApplication.run(DxflApplication.class, args); + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/config/DbContextHolder.java b/dxfl/src/main/java/com/example/dxfl/config/DbContextHolder.java new file mode 100644 index 0000000..03b239e --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/config/DbContextHolder.java @@ -0,0 +1,37 @@ +package com.example.dxfl.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Description 这里切换读/写模式 + * 原理是利用ThreadLocal保存当前线程是否处于读模式(通过开始READ_ONLY注解在开始操作前设置模式为读模式, + * 操作结束后清除该数据,避免内存泄漏,同时也为了后续在该线程进行写操作时任然为读模式 + * @author fxb + * @date 2018-08-31 + */ +public class DbContextHolder { + + private static Logger log = LoggerFactory.getLogger(DbContextHolder.class); + public static final String WRITE = "write"; + public static final String READ = "read"; + + private static ThreadLocal contextHolder= new ThreadLocal<>(); + + public static void setDbType(String dbType) { + if (dbType == null) { + log.error("dbType为空"); + throw new NullPointerException(); + } + log.info("设置dbType为:{}",dbType); + contextHolder.set(dbType); + } + + public static String getDbType() { + return contextHolder.get() == null ? WRITE : contextHolder.get(); + } + + public static void clearDbType() { + contextHolder.remove(); + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/config/MyAbstractRoutingDataSource.java b/dxfl/src/main/java/com/example/dxfl/config/MyAbstractRoutingDataSource.java new file mode 100644 index 0000000..2cb490d --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/config/MyAbstractRoutingDataSource.java @@ -0,0 +1,34 @@ +package com.example.dxfl.config; + +import com.example.dxfl.util.NumberUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * Description + * + * @author fxb + * @date 2018-09-03 + */ +public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource { + + @Value("${mysql.datasource.num}") + private int num; + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Override + protected Object determineCurrentLookupKey() { + String typeKey = DbContextHolder.getDbType(); + if (typeKey == DbContextHolder.WRITE) { + log.info("使用了写库"); + return typeKey; + } + //使用随机数决定使用哪个读库 + int sum = NumberUtil.getRandom(1, num); + log.info("使用了读库{}", sum); + return DbContextHolder.READ + sum; + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/config/ReadOnly.java b/dxfl/src/main/java/com/example/dxfl/config/ReadOnly.java new file mode 100644 index 0000000..ea455a0 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/config/ReadOnly.java @@ -0,0 +1,20 @@ +package com.example.dxfl.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description 通过该接口注释的service使用读模式,其他使用写模式 + * + * 接口注释只是一种办法,如果项目已经有代码了,通过注释可以不修改任何业务代码加持读写分离 + * 也可以通过切面根据方法开头来设置读写模式,例如getXXX()使用读模式,其他使用写模式 + * + * @author fxb + * @date 2018-08-31 + */ +@Target({ElementType.METHOD,ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReadOnly { +} diff --git a/dxfl/src/main/java/com/example/dxfl/config/ReadOnlyInterceptor.java b/dxfl/src/main/java/com/example/dxfl/config/ReadOnlyInterceptor.java new file mode 100644 index 0000000..cd971ec --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/config/ReadOnlyInterceptor.java @@ -0,0 +1,37 @@ +package com.example.dxfl.config; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; + +/** + * Description + * + * @author fxb + * @date 2018-08-31 + */ +@Aspect +@Component +public class ReadOnlyInterceptor implements Ordered { + private static final Logger log= LoggerFactory.getLogger(ReadOnlyInterceptor.class); + + @Around("@annotation(readOnly)") + public Object setRead(ProceedingJoinPoint joinPoint,ReadOnly readOnly) throws Throwable{ + try{ + DbContextHolder.setDbType(DbContextHolder.READ); + return joinPoint.proceed(); + }finally { + DbContextHolder.clearDbType(); + log.info("清除threadLocal"); + } + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/config/WriteOrReadDatabaseConfig.java b/dxfl/src/main/java/com/example/dxfl/config/WriteOrReadDatabaseConfig.java new file mode 100644 index 0000000..074ac5d --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/config/WriteOrReadDatabaseConfig.java @@ -0,0 +1,103 @@ +package com.example.dxfl.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Value; +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.core.io.support.ResourcePatternResolver; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * Description + * + * @author fxb + * @date 2018-09-03 + */ +@Configuration +@MapperScan(basePackages = "com.example.dxfl.mapper", sqlSessionFactoryRef = "sqlSessionFactory") +public class WriteOrReadDatabaseConfig { + + @Value("${mysql.datasource.type-aliases-package}") + private String typeAliasesPackage; + + @Value("${mysql.datasource.mapper-locations}") + private String mapperLocation; + + @Value("${mysql.datasource.config-location}") + private String configLocation; + + + /** + * 写数据源 + * + * @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。 + * 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean + */ + @Primary + @Bean + @ConfigurationProperties(prefix = "mysql.datasource.write") + public DataSource writeDataSource() { + return new DruidDataSource(); + } + + /** + * 读数据源 + */ + @Bean + @ConfigurationProperties(prefix = "mysql.datasource.read") + public DataSource read1() { + return new DruidDataSource(); + } + + + /** + * 多数据源需要自己设置sqlSessionFactory + */ + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(routingDataSource()); + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + // 实体类对应的位置 + bean.setTypeAliasesPackage(typeAliasesPackage); + // mybatis的XML的配置 + bean.setMapperLocations(resolver.getResources(mapperLocation)); + bean.setConfigLocation(resolver.getResource(configLocation)); + return bean.getObject(); + } + + /** + * 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理 + */ + @Bean + public DataSourceTransactionManager dataSourceTransactionManager() { + return new DataSourceTransactionManager(routingDataSource()); + } + + /** + * 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源 + */ + @Bean + public AbstractRoutingDataSource routingDataSource() { + MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(); + Map targetDataSources = new HashMap<>(2); + targetDataSources.put(DbContextHolder.WRITE, writeDataSource()); + targetDataSources.put(DbContextHolder.READ+"1", read1()); + proxy.setDefaultTargetDataSource(writeDataSource()); + proxy.setTargetDataSources(targetDataSources); + return proxy; + } + + +} diff --git a/dxfl/src/main/java/com/example/dxfl/controller/TestController.java b/dxfl/src/main/java/com/example/dxfl/controller/TestController.java new file mode 100644 index 0000000..1337f67 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/controller/TestController.java @@ -0,0 +1,44 @@ +package com.example.dxfl.controller; + +import com.example.dxfl.service.TestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Description + * + * @author fxb + * @date 2018-09-04 + */ +@RestController +public class TestController { + + private final TestService testService; + + @Autowired + public TestController(TestService testService) { + this.testService = testService; + } + + @GetMapping("/getAll") + public Object getAll(){ + return testService.getAll(); + } + + @GetMapping("insertOne") + public void insertOne(){ + testService.insertOne(); + } + + @GetMapping("readAndWrite") + public void readAndWrite(){ + testService.testReadAndWrite(); + } + + @GetMapping("transInsert") + public void transInsert(){ + testService.transInsert(); + } + +} diff --git a/dxfl/src/main/java/com/example/dxfl/entity/Test.java b/dxfl/src/main/java/com/example/dxfl/entity/Test.java new file mode 100644 index 0000000..48fc4d7 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/entity/Test.java @@ -0,0 +1,28 @@ +package com.example.dxfl.entity; + +/** + * Description + * + * @author fxb + * @date 2018-09-04 + */ +public class Test { + private Integer id; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/mapper/TestMapper.java b/dxfl/src/main/java/com/example/dxfl/mapper/TestMapper.java new file mode 100644 index 0000000..49bb95e --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/mapper/TestMapper.java @@ -0,0 +1,26 @@ +package com.example.dxfl.mapper; + +import com.example.dxfl.entity.Test; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * Description + * + * @author fxb + * @date 2018-09-04 + */ +public interface TestMapper { + /** + * 获取全部数据 + * @return List + */ + List getAll(); + + /** + * 插入一条数据 + * @param test 数据 + */ + void insertOne(@Param("test") Test test); +} diff --git a/dxfl/src/main/java/com/example/dxfl/service/TestService.java b/dxfl/src/main/java/com/example/dxfl/service/TestService.java new file mode 100644 index 0000000..d98a720 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/service/TestService.java @@ -0,0 +1,63 @@ +package com.example.dxfl.service; + +import com.example.dxfl.config.ReadOnly; +import com.example.dxfl.entity.Test; +import com.example.dxfl.mapper.TestMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * Description + * + * @author fxb + * @date 2018-09-04 + */ +@Service +public class TestService { + + private final TestMapper testMapper; + + @Autowired + public TestService(TestMapper testMapper) { + this.testMapper = testMapper; + } + + /** + * 测试读取,应该使用读库 + */ + @ReadOnly + public List getAll() { + return testMapper.getAll(); + } + + /** + * 测试读取和插入,应该使用写库 + */ + public void testReadAndWrite() { + this.getAll(); + this.insertOne(); + } + + /** + * 测试插入数据,应该使用写库 + */ + public void insertOne() { + Test test = new Test(); + test.setName("fxb"); + testMapper.insertOne(test); + } + + /** + * 测试事务能否正常工作 + */ + @Transactional(rollbackFor = RuntimeException.class) + public void transInsert(){ + Test test = new Test(); + test.setName("heiheihei"); + testMapper.insertOne(test); + throw new RuntimeException("测试事务"); + } +} diff --git a/dxfl/src/main/java/com/example/dxfl/util/NumberUtil.java b/dxfl/src/main/java/com/example/dxfl/util/NumberUtil.java new file mode 100644 index 0000000..dde6f94 --- /dev/null +++ b/dxfl/src/main/java/com/example/dxfl/util/NumberUtil.java @@ -0,0 +1,14 @@ +package com.example.dxfl.util; + +/** + * Description + * + * @author fxb + * @date 2018-09-07 + */ +public class NumberUtil { + + public static int getRandom(int min, int max) { + return (int) Math.floor(Math.random() * (max - min + 1)) + min; + } +} diff --git a/dxfl/src/main/resources/application.yml b/dxfl/src/main/resources/application.yml new file mode 100644 index 0000000..9fc7883 --- /dev/null +++ b/dxfl/src/main/resources/application.yml @@ -0,0 +1,17 @@ +mysql: + datasource: + #读库数目 + num: 1 + type-aliases-package: com.example.dxfl.dao + mapper-locations: classpath:/mapper/*.xml + config-location: classpath:/mybatis-config.xml + write: + url: jdbc:mysql://192.168.226.5:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + read: + url: jdbc:mysql://192.168.226.6:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver diff --git a/dxfl/src/main/resources/mapper/test.xml b/dxfl/src/main/resources/mapper/test.xml new file mode 100644 index 0000000..f82cf13 --- /dev/null +++ b/dxfl/src/main/resources/mapper/test.xml @@ -0,0 +1,14 @@ + + + + + + + + insert into test value (#{test.id},#{test.name}) + + + + diff --git a/dxfl/src/main/resources/mybatis-config.xml b/dxfl/src/main/resources/mybatis-config.xml new file mode 100644 index 0000000..5caa634 --- /dev/null +++ b/dxfl/src/main/resources/mybatis-config.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dxfl/src/test/java/com/example/dxfl/DxflApplicationTests.java b/dxfl/src/test/java/com/example/dxfl/DxflApplicationTests.java new file mode 100644 index 0000000..44b2808 --- /dev/null +++ b/dxfl/src/test/java/com/example/dxfl/DxflApplicationTests.java @@ -0,0 +1,16 @@ +package com.example.dxfl; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class DxflApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/dxfl/src/test/java/com/example/dxfl/TestServiceTest.java b/dxfl/src/test/java/com/example/dxfl/TestServiceTest.java new file mode 100644 index 0000000..68b8dd5 --- /dev/null +++ b/dxfl/src/test/java/com/example/dxfl/TestServiceTest.java @@ -0,0 +1,32 @@ +package com.example.dxfl; + +import com.example.dxfl.service.TestService; +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; + +/** + * Description + * + * @author fxb + * @date 2018-09-05 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class TestServiceTest { + + @Autowired + private TestService testService; + + @Test + public void insertOne(){ + testService.insertOne(); + } + + @Test + public void getAll(){ + testService.getAll(); + } +}