Spring AOP从入门到放弃之多数据源读写动态切换

项目中如果需要由多个数据源,比如3个,一个主两个从。主库主要是写操作,两个从库做读操作。
那么在spring boot中怎么使用AOP判断程序是读还是写,并且分配到不同的数据源中呢?

本文重要是 的代码是使用 spring boot 、druid、mybatis、mybatis plus等技术做支持的。

逻辑步骤

大概的逻辑为,
1、引入durid
2、配置三个数据源,1个写,2个读,两个从库实现简单的负载功能。
3、配置mybatis
4、配置mybatis plus
5、配置aop
6、定义却点 读(select)的方法操作,使用读库的数据源,其他的 update、delete、insert等使用写库的数据源
7、给写库配置spring 的事务,出现异常的时候回滚。

引入jar

这里不累赘写 mysql 、druid等jar包的引入。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

配置Druid

@Configuration
public class DruidConfig {
    /**
     * 注册DruidServlet
     * http://localhost:8080/druid/datasource.html查看监控信息
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServletRegistrationBean() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        servletRegistrationBean.setServlet(new StatViewServlet());
        servletRegistrationBean.addUrlMappings("/druid/*");
        return servletRegistrationBean;
    }

    /**
     * 注册DruidFilter拦截
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean duridFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        Map<String, String> initParams = new HashMap<String, String>();
        //设置忽略请求
        initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/monitor/druid/*");
        filterRegistrationBean.setInitParameters(initParams);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}

配置yml


writedatasource:
  url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false
  driverClass: com.mysql.jdbc.Driver
  username: xx
  password: cdd
  initialSize: 1
  minIdle: 1
  maxActive: 20
  testOnBorrow: true
  timeBetweenEvictionRunsMillis: 60000
  minEvictableIdleTimeMillis: 300000
  validationQuery: SELECT 1 FROM DUAL
  testWhileIdle: true
  testOnReturn: false
  poolPreparedStatements: true
  maxPoolPreparedStatementPerConnectionSize: 20
  filters: stat,wall,logback
  #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  useGlobalDataSourceStat: true

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.slife.entity

readdatasource01:
  url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false
  driverClass: com.mysql.jdbc.Driver
  username: xx
  password: cdd
  initialSize: 1
  minIdle: 1
  maxActive: 20
  testOnBorrow: true
  timeBetweenEvictionRunsMillis: 60000
  minEvictableIdleTimeMillis: 300000
  validationQuery: SELECT 1 FROM DUAL
  testWhileIdle: true
  testOnReturn: false
  poolPreparedStatements: true
  maxPoolPreparedStatementPerConnectionSize: 20
  filters: stat,wall,logback
  #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  useGlobalDataSourceStat: true

readdatasource02:
  url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false
  driverClass: com.mysql.jdbc.Driver
  username: xx
  password: cdd
  initialSize: 1
  minIdle: 1
  maxActive: 20
  testOnBorrow: true
  timeBetweenEvictionRunsMillis: 60000
  minEvictableIdleTimeMillis: 300000
  validationQuery: SELECT 1 FROM DUAL
  testWhileIdle: true
  testOnReturn: false
  poolPreparedStatements: true
  maxPoolPreparedStatementPerConnectionSize: 20
  filters: stat,wall,logback
  #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  useGlobalDataSourceStat: true

加载配置文件数据

/**
 * 只提供了常用的属性,如果有需要,自己添加
 *
 */
@Component
@ConfigurationProperties(prefix = "writedatasource")
public class WriteProperties extends DataProperties{

}

/**
 * 只提供了常用的属性,如果有需要,自己添加
 *
 */
@Component
@ConfigurationProperties(prefix = "readdatasource02")
public class ReadProperties2 extends DataProperties{

}

@ConfigurationProperties(prefix = "druid")
public class DruidProperties {
    private String url;
    private String username;
    private String password;
    private String driverClass;

    private int maxActive;
    private int minIdle;
    private int initialSize;
    private boolean testOnBorrow;
    private String filters;
    。
    。
    。
    。
get  set  省略

3、定义数据源

public enum DataSourceType {
    read("read", "从库"),
    write("write", "主库");

    private String type;

    private String name;

    DataSourceType(String type, String name) {
        this.type = type;
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

简单的负载均衡配置

public class BlifeAbstractRoutingDataSource extends AbstractRoutingDataSource {
    private  int dataSourceNumber;
    private AtomicInteger count = new AtomicInteger(0);

    public BlifeAbstractRoutingDataSource(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String typeKey = DataSourceContextHolder.getJdbcType();
        if (DataSourceType.write.getType().equals(typeKey)){
            return DataSourceType.write.getType();
        }
        // 读 简单负载均衡
        int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}

把数据源加ThreadLocal中

public class DataSourceContextHolder {
    private static final ThreadLocal<String> LOCAL = new ThreadLocal<String>();

    private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);

    public static ThreadLocal<String> getLocal() {
        return LOCAL;
    }

    /**
     * 读可能是多个库
     */
    public static void read() {

        LOCAL.set(DataSourceType.read.getType());
    }

    /**
     * 写只有一个库
     */
    public static void write() {
        logger.debug("writewritewrite");
        LOCAL.set(DataSourceType.write.getType());
    }

    public static String getJdbcType() {
        return LOCAL.get();
    }
}

配置写库的事物

读库 一般没配置事物的需求,当然配置了只读会有更好的效果。

@Configuration
@EnableTransactionManagement
public class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration {

    private Logger logger= LoggerFactory.getLogger(getClass());

    /**
     * 自定义事务
     * MyBatis自动参与到spring事务管理中,无需额外配置,
     * 只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与
     * DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
     * @return
     */
    @Resource(name = "writeDataSource1")
    private DataSource dataSource;

    @Bean(name = "transactionManager")
    public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManagers() {
        logger.info("-------------------- transactionManager init ---------------------");
        return new org.springframework.jdbc.datasource.DataSourceTransactionManager(dataSource);
    }
}

约定方法,读写动态切换

@Aspect
@Component
@Order(-100)// 保证该AOP在@Transactional之前执行
public class DataSourceAop {
    private  Logger logger = LoggerFactory.getLogger(getClass());

    @Before("execution(* com.slife.dao..*.select*(..)) || execution(* com.slife.dao..*.get*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.read();
        logger.info("dataSource切换到:Read");
    }

    @Before("execution(* com.slife.dao..*.*insert*(..)) || execution(* com.slife.dao..*.*update*(..))")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.write();
        logger.info("dataSource切换到:write");
    }
}

运行项目,执行结果

点击获取阿里云优惠券

我的官网

我的官网http://guan2ye.com
我的CSDN地址http://blog.csdn.net/chenjianandiyi
我的简书地址http://www.jianshu.com/u/9b5d1921ce34
我的githubhttps://github.com/javanan
我的码云地址https://gitee.com/jamen/
阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld

时间: 2024-08-01 16:14:45

Spring AOP从入门到放弃之多数据源读写动态切换的相关文章

Spring AOP从入门到放弃之概念以及Spring Boot AOP demo

本文小福利 点我获取阿里云优惠券 AOP核心概念 1.横切关注点 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点 2.切面(aspect)->(通知+切点) 类是对物体特征的抽象,切面就是对横切关注点的抽象. 通知+切点 意思就是所有要被应用到增强(advice)代码的地方.(包括方法的方位信息) 3.连接点(joinpoint)->(被拦截的方法) 被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截的方法,实际上连接点还可以是字段或

请教:spring配置mysql/access多数据源的动态切换问题,急用,十分感谢!

问题描述 请教:spring配置mysql/access多数据源的动态切换问题,急用,十分感谢! 请教:spring配置mysql/access多数据源在切换到access数据源时抛No Session found for current thread错误是怎么回事?请大侠详加指导,十分感谢,急用!我的配置文件如下: <?xml version="1.0" encoding="UTF-8"?> xsi:schemaLocation="http:

ssh 多数据源问题!-spring多数据源动态切换问题

问题描述 spring多数据源动态切换问题 一个总公司托管了N个子公司,每个子公司对应自己的数据库,我的程序实现每个子公司根据用户名登录的时候,只访问自己的数据库,而且公司的数目还在增加,这就涉及到了动态切换多数据源的问题!,还涉及到多个用户登录的时候的并发问题!急求解决,谢谢! 解决方案 目前很多项目中只能配置单个数据源,那么如果有多个数据源肿么办?Spring提供了一个抽象类AbstractRoutingDataSource,为我们很方便的解决了这个问题.1.写一个DynamicDataSo

Spring AOP源码分析(一)AOP介绍和aspectj、SpringAOP入门

首先说说三个名词,面向对象.面向接口编程.面向切面编程(Aspect Oriented Programming 即AOP).  针对java来说  面向对象:是对现实世界的描述,是后两者的基础,大前提.  面向接口编程:接口就是一组规则的集合,这组规则可以有不同的实现方式.如JDBC,它本身仅仅是接口规范,不同的数据库有不同的实现,我们通过JDBC这样的接口编程,就无需关心我们使用的是哪种数据库,可以方便的实现数据库的切换而不必改动代码.  面向切面编程:是对面向对象的补充,主要应用的场景为: 

J2EE中使用Spring AOP框架和EJB组件

j2ee 快速发展的开发人员社区.对各种后端技术(包括JMS.JTA.JDO.Hibernate.iBATIS等等)的支持,以及(更为重要的)非侵入性的轻量级IoC容器和内置的AOP运行时,这些因素使得Spring Framework对于J2EE应用程序开发十分具有吸引力.Spring托管的组件(POJO)可以与EJB共存,并允许使用AOP方法来处理企业应用程序中的横切方面--从监控和审计.缓存及应用程序级的安全性开始,直到处理特定于应用程序的业务需求. 本文将向您介绍Spring的AOP框架在

s2sh框架搭建(基于spring aop)

对于spring aop 是如何管理事务的,请看一下:http://bbs.csdn.net/topics/290021423 1.applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3

Spring AOP框架

AOP正在成为软件开发的下一个圣杯.使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect.AOP可以防止代码混乱. 为了理解AOP如何做到这点,考虑一下记日志的工作.日志本身不太可能是你开发的主程序的主要任务.如果能将"不可见的".通用的日志代码注入主程序中,那该多好啊.AOP可以帮助你做到. Spring framework是很有前途的AOP技术.作为一种非侵略性的,轻型的AOP framework,你无需使用预编译器或其他的元标签,

spring AOP的方式监控方法的执行时间

前段时间有几个同行跟我吐槽说系统响应越来越慢,优化不知道从何入手!今天写写使用spring的aop来实现方法级的执行时间的记录监控,以此来评估方法的性能以及针对性的对已存在的方法进行优化. 对于监控,我们比较关注监控的可靠性和性能,准确,高效,这才能在不影响整体性能的情况下对我们的系统性能有个较准确的认识. 对于spring aop这个我就不多介绍了,网上一搜一大把,使用过spring的人都知道spring的ioc和aop.ioc我们常用,但在我们自己的系统中,aop的使用几乎为零,除了这个监控

基于Annotation拦截的Spring AOP权限验证方法

在 Web 开发过程中,一个非常理想的开发过程是,开发人员在开发中并不需要关心权限问题,不需要在 Java 方法中写 很多逻辑判断去判断用户是否具有合适的角色和权限,这样开发会花费非常多的人力成本,因为所有的开发人员都需要了解 关于权限的详细内容,也非常不容易进行后期维护.我们希望有专门的很少数量的开发人员了解权限内容,并且可以随时方 便的修改和配置.于是,我们使用 Annotation,在 Java 方法之前使用 Annotation 可以非常方便的添加,修改和删除对 于权限的管理功能. 本文