JUST DO IT

  • 首页
  • 归档

  • 搜索
极客时间

动态数据源自定义注解的使用说明

发表于 2020-12-13 | 分类于 原创 | 0 | 阅读次数 617

数据库部署图

image-20201110113823430

使用方式

提供方法级别的注解@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代理在类加载阶段将切面织入代码
  • 本文作者: jkl_yuiop
  • 本文链接: https://leeshengis.com/archives/dong-tai-shu-ju-yuan-zi-ding-yi-zhu-jie-de-shi-yong-shuo-ming
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 极客时间
XXL-JOB在少儿英语中的实践-1
(转)软件架构探索:The Fenix Project
  • 文章目录
  • 站点概览
jkl_yuiop

jkl_yuiop

1,383 日志
37 分类
1 标签
RSS
E-mail
Creative Commons
Links
  • 学习笔记
© 2023 jkl_yuiop

如需同步其他《极客时间》专栏,请邮件leeshengis@163.com(免费)