springboot读写分离
主要根据填充Spring Boot + Mybatis多数据源和动态数据源配置 文章中动态部分坑
读写分离,多数据源,主从库
##多数据源
datasource:
#主库
master:
jdbcUrl: jdbc:mysql://localhost:3306/pa_yqs_game?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
#从库
slave:
#并非url而是jdbcUrl(因为这个在获取数据源时一直报错,看了DataSource的属性才知道是jdbcUrl)
jdbcUrl: jdbc:mysql://localhost:3306/data_count?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
##mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.*.domain.pojo
check-config-location: true
config-location: classpath:mybatis-config.xml
/**
* **在最外侧,即包含所有子包
* @ClassName Application
* @Description 入口,启动类
* @author lide
* @date 2018年2月9日 下午2:47:04
*/
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
@EnableTransactionManagement(order = 2) //设置事务执行顺序(需要在切换数据源之后,否则只走默认库)
@MapperScan(basePackages = "com.example.*.domain.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1. 需要设置事务(本质也是AOP)执行的顺序,否则事务的执行顺序高于后续的AOP,会导致动态切换数据源失效
public enum DataSourceType {
// 主表
Master("master"),
// 从表
Slave("slave");
private String name;
private DataSourceType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class JdbcContextHolder {
private final static ThreadLocal local = new ThreadLocal<>();
public static void putDataSource(String name) {
local.set(name);
}
public static String getDataSource() {
return local.get();
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected Object determineCurrentLookupKey() {
logger.info("数据源为{}",JdbcContextHolder.getDataSource());
return JdbcContextHolder.getDataSource();
}
}
@Aspect
@Order(1) //设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)
@Component
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//切点
@Pointcut("execution(* com.example.*.service..*.*(..)))")
public void aspect() { }
@Before("aspect()")
private void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class> classz = target.getClass();
Class>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(MyDataSource.class)) {
MyDataSource data = m.getAnnotation(MyDataSource.class);
JdbcContextHolder.putDataSource(data.value().getName());
logger.info("===============上下文赋值完成:{}",data.value().getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyDataSource {
DataSourceType value() default DataSourceType.Master; //默认主表
}
注意@Primary标注多数据源,否则会产生实现类冲突。
@Primary和@Qualifier这两个注解的意思:
@Configuration
public class DataSourceConfig {
@Bean(name = "master")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource dataSource1() {
System.out.println("主配");
return DataSourceBuilder.create().build();
}
@Bean(name = "slave")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource dataSource2() {
System.out.println("从配");
return DataSourceBuilder.create().build();
}
@Bean(name="dynamicDataSource")
@Primary //优先使用,多数据源
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
DataSource master = dataSource1();
DataSource slave = dataSource2();
//设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(master);
//配置多数据源
Map