日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置

瀏覽:243日期:2023-05-20 18:55:21

什么事讀寫分離

讀寫分離,基本的原理是讓主數(shù)據(jù)庫處理事務(wù)性增、改、刪操作(INSERT、UPDATE、DELETE),而從數(shù)據(jù)庫處理SELECT查詢操作。數(shù)據(jù)庫復(fù)制被用來把事務(wù)性操作導(dǎo)致的變更同步到集群中的從數(shù)據(jù)庫。

為什么要實(shí)現(xiàn)讀寫分離

增加冗余

增加了機(jī)器的處理能力

對于讀操作為主的應(yīng)用,使用讀寫分離是最好的場景,因?yàn)榭梢源_保寫的服務(wù)器壓力更小,而讀又可以接受點(diǎn)時(shí)間上的延遲。

實(shí)現(xiàn)

本文介紹利用spring aop來動(dòng)態(tài)切換數(shù)據(jù)源來實(shí)現(xiàn)讀寫分離。

先建一個(gè)maven項(xiàng)目,導(dǎo)入springBoot依賴。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version></parent><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!-- mysql connector--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector.version}</version> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>

然后在配置文件application.yml中自定義數(shù)據(jù)源配置項(xiàng)

server: port: 8080logging: level: org.springframework: INFO com.qiang: DEBUGspring: output: ansi: enabled: always datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db_area?characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root db: readsize: 2 read0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db_area?characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root read1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db_area?characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: root aop: auto: true proxy-target-class: true

配置Druid

package com.qiang.config;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;/** * @author gengqiang * @date 2018/5/3 */@Configurationpublic class DruidConfig { private Logger logger = LoggerFactory.getLogger(DruidConfig.class /** * 主據(jù)源 * @return */ @Primary @Bean(name = 'dataSource') @ConfigurationProperties(prefix = 'spring.datasource') public DataSource dataSource() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); } /** * 從數(shù)據(jù)源1 * @return */ @Bean(name = 'readDataSource0') @ConfigurationProperties(prefix = 'spring.db.read0') public DataSource readDataSource0() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); } /** * 從數(shù)據(jù)源2 * @return */ @Bean(name = 'readDataSource1') @ConfigurationProperties(prefix = 'spring.db.read1') public DataSource readDataSource1() { return DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build(); }}

配置Mybaits

package com.qiang.config;import com.qiang.config.db.DataSourceType;import com.qiang.config.db.RoutingDataSource;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;import org.springframework.beans.BeansException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.Resource;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.core.io.support.ResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import org.springframework.transaction.annotation.TransactionManagementConfigurer;import javax.sql.DataSource;import java.io.IOException;import java.util.HashMap;import java.util.Map;/** * @author gengqiang * @date 2018/5/3 */@Configuration@EnableTransactionManagement(order = 2)@MapperScan(basePackages = {'com.qiang.demo.mapper'})public class MybatisConfig implements TransactionManagementConfigurer, ApplicationContextAware { private static ApplicationContext context; /** * 寫庫數(shù)據(jù)源 */ @Autowired private DataSource dataSource; /** * 讀數(shù)據(jù)源數(shù)量 */ @Value('${spring.db.readsize}') private Integer readsize; /** * 數(shù)據(jù)源路由代理 * * @return */ @Bean public AbstractRoutingDataSource routingDataSouceProxy() { RoutingDataSource proxy = new RoutingDataSource(readsize); Map<Object, Object> targetDataSources = new HashMap<>(readsize + 1); targetDataSources.put(DataSourceType.WRITE.getType(), dataSource); for (int i = 0; i < readsize; i++) { DataSource d = context.getBean('readDataSource' + i, DataSource.class); targetDataSources.put(i, d); } proxy.setDefaultTargetDataSource(dataSource); proxy.setTargetDataSources(targetDataSources); return proxy; } @Bean @ConditionalOnMissingBean public SqlSessionFactoryBean sqlSessionFactory() throws IOException { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(routingDataSouceProxy()); bean.setVfs(SpringBootVFS.class); bean.setTypeAliasesPackage('com.qiang'); Resource configResource = new ClassPathResource('/mybatis-config.xml'); bean.setConfigLocation(configResource); ResourcePatternResolver mapperResource = new PathMatchingResourcePatternResolver(); bean.setMapperLocations(mapperResource.getResources('classpath*:mapper/**/*.xml')); return bean; } @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(routingDataSouceProxy()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (context == null) { context = applicationContext; } }}

其中實(shí)現(xiàn)數(shù)據(jù)源切換的功能就是自定義一個(gè)類擴(kuò)展AbstractRoutingDataSource抽象類,就是代碼中的定義的RoutingDataSource,其實(shí)該相當(dāng)于數(shù)據(jù)源DataSourcer的路由中介,可以實(shí)現(xiàn)在項(xiàng)目運(yùn)行時(shí)根據(jù)相應(yīng)key值切換到對應(yīng)的數(shù)據(jù)源DataSource上。

RoutingDataSource.class

package com.qiang.config.db;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import java.util.concurrent.atomic.AtomicInteger;/** * 數(shù)據(jù)源路由 * * @author gengqiang */public class RoutingDataSource extends AbstractRoutingDataSource { private AtomicInteger count = new AtomicInteger(0); private int readsize; public RoutingDataSource(int readsize) { this.readsize = readsize; } @Override protected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getJdbcType(); if (typeKey == null) { logger.error('無法確定數(shù)據(jù)源'); } if (typeKey.equals(DataSourceType.WRITE.getType())) { return DataSourceType.WRITE.getType(); } //讀庫進(jìn)行負(fù)載均衡 int a = count.getAndAdd(1); int lookupkey = a % readsize; return lookupkey; }}

其中用到了2個(gè)輔助類

package com.qiang.config.db;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 全局?jǐn)?shù)據(jù)源 * * @author gengqiang */public class DataSourceContextHolder { private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class); private final static ThreadLocal<String> local = new ThreadLocal<>(); public static ThreadLocal<String> getLocal() { return local; } public static void read() { logger.debug('切換至[讀]數(shù)據(jù)源'); local.set(DataSourceType.READ.getType()); } public static void write() { logger.debug('切換至[寫]數(shù)據(jù)源'); local.set(DataSourceType.WRITE.getType()); } public static String getJdbcType() { return local.get(); }}

package com.qiang.config.db;/** * @author gengqiang */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; }}

最后通過aop設(shè)置切面,攔截讀寫來動(dòng)態(tài)的設(shè)置數(shù)據(jù)源

package com.qiang.config.aop;import com.qiang.config.db.DataSourceContextHolder;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;/** * 攔截?cái)?shù)據(jù)庫讀寫 * * @author gengqiang */@Aspect@Component@Order(1)public class DataSourceAspect { Logger logger = LoggerFactory.getLogger(getClass()); @Before('execution(* com.qiang..*.*ServiceImpl.find*(..)) ' + '|| execution(* com.qiang..*.*ServiceImpl.count*(..))' + '|| execution(* com.qiang..*.*ServiceImpl.sel*(..))' + '|| execution(* com.qiang..*.*ServiceImpl.get*(..))' ) public void setReadDataSourceType() { logger.debug('攔截[read]方法'); DataSourceContextHolder.read(); } @Before('execution(* com.qiang..*.*ServiceImpl.insert*(..)) ' + '|| execution(* com.qiang..*.*ServiceImpl.save*(..))' + '|| execution(* com.qiang..*.*ServiceImpl.update*(..))' + '|| execution(* com.qiang..*.*ServiceImpl.set*(..))' + '|| execution(* com.qiang..*.*ServiceImpl.del*(..))') public void setWriteDataSourceType() { logger.debug('攔截[write]操作'); DataSourceContextHolder.write(); }}

主要的代碼就寫好了,下面來測試一下是否讀寫分離。

寫一個(gè)測試類:

package com.qiang;import com.qiang.demo.entity.Area;import com.qiang.demo.service.AreaService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;/** * @author gengqiang * @date 2018/5/4 */@RunWith(SpringRunner.class)@SpringBootTestpublic class TestApplication { @Autowired private AreaService areaService; @Test public void test() { Area area = new Area(); area.setDistrictId('0000'); area.setName('test'); area.setParentId(0); area.setLevel(1); areaService.insert(area); } @Test public void test2() { areaService.selectByPrimaryKey(1); }}

其中第一個(gè)測試插入數(shù)據(jù),第二個(gè)測試查詢。

第一測試結(jié)果:

使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置

第二個(gè)測結(jié)果:

使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置

從結(jié)果看出來第一個(gè)走的寫數(shù)據(jù)源,就是主數(shù)據(jù)源,第二個(gè)的走讀數(shù)據(jù)源,就是從數(shù)據(jù)源。

然后我們在測試一下事物,看遇到異常是否會(huì)滾。

測試:

@Test public void contextLoads() throws Exception { try { areaService.insertBack(); } catch (Exception e) {// e.printStackTrace(); } System.out.println(areaService.count(new Area())); }

其中service:

@Override@Transactional(rollbackFor = Exception.class)public void insertBack() { Area area = new Area(); area.setDistrictId('0000'); area.setName('test'); area.setParentId(0); area.setLevel(1); mapper.insert(area); throw new RuntimeException();}

方法上加@Transactional,聲明一個(gè)事物。

使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置

看一下運(yùn)行結(jié)果,雖然運(yùn)行插入的時(shí)候,sql是運(yùn)行了,但最后查詢的時(shí)候數(shù)量為0,說明會(huì)滾了。

配置事物

使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置

第一步需要加一個(gè)注解@EnableTransactionManagement,后面的參數(shù)是為了區(qū)分aop和事物執(zhí)行的順序。

然后在需要會(huì)滾的方法上加一個(gè)注解@Transactional。

源代碼地址

以上這篇使用springboot aop來實(shí)現(xiàn)讀寫分離和事物配置就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Spring
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
三级欧美在线一区| 日本欧美韩国一区三区| 欧美一区网站| 婷婷亚洲精品| 日韩成人一级| 日本午夜精品视频在线观看| 中文字幕日韩亚洲| 亚洲另类黄色| 午夜精品福利影院| 综合一区二区三区| 欧美日韩亚洲三区| 国产精成人品2018| 国产不卡一区| 日韩av首页| 午夜精品网站| 婷婷亚洲成人| 精品国产麻豆| 国产白浆在线免费观看| 黑人精品一区| 国产韩日影视精品| 亚洲精品一级二级三级| 国产毛片久久久| 国产成人久久| 亚洲特色特黄| 石原莉奈在线亚洲二区| 国产精品羞羞答答在线观看| 国产在线观看91一区二区三区| 国产精品13p| 午夜日韩av| 日韩精品亚洲专区| 国产日韩欧美中文在线| 电影91久久久| 亚洲手机视频| 日韩午夜视频在线| 久久一区视频| 欧美日韩国产欧| 欧美综合精品| 亲子伦视频一区二区三区| 美女精品在线观看| 国产精品国码视频| 999久久久免费精品国产| 蜜臀av亚洲一区中文字幕| 国产精品分类| 天堂网在线观看国产精品| 日韩av一二三| 日韩成人亚洲| 婷婷亚洲精品| 色网在线免费观看| 亚洲毛片在线| 日韩精品2区| 亚州精品视频| 久久精品动漫| 国产麻豆精品| 国产日韩专区| 国产成人a视频高清在线观看| 丝瓜av网站精品一区二区| 精品中国亚洲| 综合激情网站| 亚洲黄色网址| 91亚洲无吗| 午夜日韩在线| 国语精品一区| 亚洲专区视频| 久久人人精品| 久久av影视| 综合欧美精品| 久久网站免费观看| 国产精品一区2区3区| 黄色日韩在线| 国产一区二区三区久久| 蜜桃av一区二区在线观看| 国产第一亚洲| 日本va欧美va精品| 在线一区电影| 欧美好骚综合网| 日韩高清电影一区| 国产精品88久久久久久| 捆绑调教美女网站视频一区| 视频一区视频二区中文| 中文字幕成在线观看| 欧美午夜三级| 久久亚洲影院| 久久久久久久久久久妇女 | 亚洲精品自拍| 国产韩日影视精品| 91精品xxx在线观看| 国产精品日韩精品在线播放| 爽爽淫人综合网网站| 日韩国产网站| sm捆绑调教国产免费网站在线观看 | 久久久久久自在自线| 日韩高清不卡一区二区| 国产精品毛片一区二区三区| 久久精品国产网站| 久久国产人妖系列| 午夜亚洲福利| 久久av一区| 亚洲午夜视频| 成人欧美一区二区三区的电影| 国产精品调教| 日韩在线网址| 巨乳诱惑日韩免费av| 国模 一区 二区 三区| 久久久精品区| 国产精品自拍区| 日本v片在线高清不卡在线观看| 欧美日韩国产探花| 久久免费黄色| 99久久婷婷| 99精品视频在线观看免费播放| 国产盗摄——sm在线视频| 久久字幕精品一区| 免费观看亚洲天堂| 欧美激情久久久久久久久久久| 国产亚洲久久| 青草久久视频| 国产乱码精品一区二区三区四区| 日韩高清三区| 日韩精品视频网站| 亚洲+小说+欧美+激情+另类| 亚洲尤物av| 亚洲2区在线| 日韩av三区| 国产精品九九| 激情综合婷婷| 婷婷综合六月| 女人av一区| 老牛影视一区二区三区| 亚洲三级网址| 国产欧美在线| 精品午夜久久| 电影亚洲精品噜噜在线观看| 亚洲精品一区三区三区在线观看| se01亚洲视频| 五月天激情综合网| 久久一二三区| 日韩二区在线观看| 你懂的国产精品| 日本黄色精品| 久久中文字幕av| 在线综合视频| 免费成人性网站| 亚洲精品一二| 国产欧美亚洲精品a| 日韩黄色在线观看| 久久激情综合网| 精品一区不卡| 久久久久欧美精品| 日韩国产一区| 激情综合在线| 亚洲精品乱码日韩| 国产欧美综合一区二区三区| 麻豆精品久久久| 欧美天堂视频| 亚洲综合电影一区二区三区| 亚洲精品一二三**| 老司机免费视频一区二区三区| 欧美gv在线| 免费在线观看日韩欧美| 国产女人18毛片水真多18精品| 超碰成人av| 在线一区欧美| 国产精品高清一区二区| 美女av在线免费看| 亚洲在线观看| 国产精品视频一区二区三区综合| 手机在线电影一区| 99亚洲视频| 欧美亚洲福利| 欧美片第1页| 日韩在线播放一区二区| 国产精品久久乐| 国产亚洲一区二区手机在线观看 | 合欧美一区二区三区| 综合精品一区| 国产精品久久久一区二区| 99久久久久久中文字幕一区| 亚洲精品免费观看| av中文资源在线资源免费观看| 首页亚洲欧美制服丝腿| 精品资源在线| 日韩中文字幕麻豆| 国产精品二区不卡| 亚洲男人在线| 日韩国产一区| 91精品国产自产观看在线 | 日韩黄色大片网站| 亚洲三级av| 精品免费视频| 午夜一级久久| 久久久久免费| 中文字幕av亚洲精品一部二部| 国产在视频一区二区三区吞精| 久久蜜桃精品| 欧美一区二区三区久久| 亚洲不卡系列| 日韩国产在线不卡视频| 亚洲精品**中文毛片| 亚洲涩涩av| 中文在线免费视频|