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