数据库部署图
使用方式
提供方法级别的注解@TargetDataSource来自动切换MySQL的主库和从库,默认主库
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "master";
}
默认使用主库,如果需要切换从库,请在方法上添加@TargetDataSource("slave")注解
例子
/**
* 从库,方法执行完自动切换为默认主库
*/
@TargetDataSource("slave")
public void method1() {
// TODO 数据库操作
}
/**
* 主库
*/
@TargetDataSource("master")
public void method2() {
// TODO 数据库操作
}
/**
* 主库
*/
@TargetDataSource
public void method3() {
// TODO 数据库操作
}
/**
* 主库
*/
public void method4() {
// TODO 数据库操作
}
实现方式
在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。
// 获取connection
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
// 确定目标数据源
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
// 子类需要实现的抽象方法,返回目前数据源的LookupKey
@Nullable
protected abstract Object determineCurrentLookupKey();
不生效的情况
类内方法嵌套调用
// 调用test1方法切换数据源不生效,走主库
public void test1() {
test2();
}
// 直接调用test2方法生效,走从库
@TargetDataSource("slave")
public void test2() {
// TODO 数据库操作
}
private方法
// 不生效走从库
@TargetDataSource("slave")
private void test2() {
// TODO 数据库操作
}
类间调用
@Service
@AllArgsConstructor
public class DsTestService {
private UserDao userDao;
@TargetDataSource("master")
public void query() {
userDao.selectByPrimaryKey(1915L);
}
}
@RestController
@RequestMapping("/ds/")
@AllArgsConstructor
public class DsTestController {
private DsTestService dsTestService;
private UserDao userDao;
@RequestMapping("/query")
@TargetDataSource("slave")
public void query() {
// 走主库
dsTestService.query();
// 走主库
userDao.selectByPrimaryKey(1915L);
}
}
扩展
Spring AOP
两种实现方式,默认proxy
public enum AdviceMode {
PROXY,
ASPECTJ;
private AdviceMode() {
}
}
Proxy(动态代理)
两种实现方式,默认JDK proxy
- JDK Proxy
- 代理类继承了Proxy类并且实现了要代理的接口,由于Java不支持多继承,所以JDK动态代理不能代理类。
- CGLIB
- 基于类的代理,不需要实现接口,会生成目标对象的子对象
- 基于ASM框架操作字节码
如何让类内嵌套调用生效
- 自己注入自己
@Service
public class TestService {
@Autowired
private TestService testService;
// 切换到从库
public void test1() {
// 调用增强后的代理类的test2方法
testService.test2();
}
@TargetDataSource("slave")
public void test2() {
// 数据库操作
}
}
- AopContext.currentProxy()
// exposeProxy设置为true,可以在运行时拿到自己的代理类对象
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
// 调用test1方法切换数据源生效
public void test1() {
((A)AopContext.currentProxy()).test2();
}
@TargetDataSource("slave")
public void test2() {
// TODO 数据库操作
}
AspectJ(静态织入)
三种织入方式
- 编译期织入:利用ajc编译器替代javac编译器,直接将源文件(Java和Aspect)编译成class文件并将切面织入代码中
- 编译后织入:利用ajc编译器将javac编译器编译后的class文件或者jar文件织入切面代码
- 加载时织入:不使用ajc编译器,利用aspectjweaver.jar工具,使用java agent代理在类加载阶段将切面织入代码