項目中若是須要由多個數據源,好比3個,一個主兩個從。主庫主要是寫操做,兩個從庫作讀操做。 那麼在spring boot中怎麼使用AOP判斷程序是讀仍是寫,而且分配到不一樣的數據源中呢?css
本文重要是 的代碼是使用 spring boot 、druid、mybatis、mybatis plus等技術作支持的。html
大概的邏輯爲, 一、引入durid 二、配置三個數據源,1個寫,2個讀,兩個從庫實現簡單的負載功能。 三、配置mybatis 四、配置mybatis plus 五、配置aop 六、定義卻點 讀(select)的方法操做,使用讀庫的數據源,其餘的 update、delete、insert等使用寫庫的數據源 七、給寫庫配置spring 的事務,出現異常的時候回滾。java
這裏不累贅寫 mysql 、druid等jar包的引入。mysql
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
@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; } }
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 省略
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); } }
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(); } }
讀庫 通常沒配置事物的需求,固然配置了只讀會有更好的效果。git
@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"); } }
個人官網 github
個人官網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=vf2b5zldspring