1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關(guān)咨詢
      選擇下列產(chǎn)品馬上在線溝通
      服務(wù)時(shí)間:8:30-17:00
      你可能遇到了下面的問題
      關(guān)閉右側(cè)工具欄

      新聞中心

      這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
      讀寫分離很難嗎?springboot結(jié)合aop簡(jiǎn)單就實(shí)現(xiàn)了

      前言

      創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的麥蓋提網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!

      前幾天終于有時(shí)間研究下公司舊項(xiàng)目的代碼。在研究代碼的過程中,發(fā)現(xiàn)項(xiàng)目里用到了Spring Aop來實(shí)現(xiàn)數(shù)據(jù)庫的讀寫分離,本著自己愛學(xué)習(xí)(我自己都不信...)的性格,決定寫個(gè)實(shí)例工程來實(shí)現(xiàn)spring aop讀寫分離的效果。

      環(huán)境部署

      數(shù)據(jù)庫:MySQL

      庫數(shù)量:2個(gè),一主一從

      開始項(xiàng)目

      首先,毫無疑問,先開始搭建一個(gè)SpringBoot工程,然后在pom文件中引入如下依賴:

      
       
       com.alibaba
       druid-spring-boot-starter
       1.1.10
       
       
       org.mybatis.spring.boot
       mybatis-spring-boot-starter
       1.3.2
       
       
       tk.mybatis
       mapper-spring-boot-starter
       2.1.5
       
       
       mysql
       mysql-connector-java
       8.0.16
       
       
       
       org.springframework.boot
       spring-boot-starter-jdbc
       provided
       
       
       org.springframework.boot
       spring-boot-starter-aop
       provided
       
       
       
       org.springframework.boot
       spring-boot-starter-web
       
       
       org.projectlombok
       lombok
       true
       
       
       com.alibaba
       fastjson
       1.2.4
       
       
       org.springframework.boot
       spring-boot-starter-test
       test
       
       
       org.springframework.boot
       spring-boot-starter-data-jpa
       
       

      目錄結(jié)構(gòu)

      引入基本的依賴后,整理一下目錄結(jié)構(gòu),完成后的項(xiàng)目骨架大致如下:
      讀寫分離很難嗎?springboot結(jié)合aop簡(jiǎn)單就實(shí)現(xiàn)了

      建表

      創(chuàng)建一張表user,在主庫執(zhí)行sql語句同時(shí)在從庫生成對(duì)應(yīng)的表數(shù)據(jù)

      DROP TABLE IF EXISTS `user`;
      CREATE TABLE `user` (
       `user_id` bigint(20) NOT NULL COMMENT '用戶id',
       `user_name` varchar(255) DEFAULT '' COMMENT '用戶名稱',
       `user_phone` varchar(50) DEFAULT '' COMMENT '用戶手機(jī)',
       `address` varchar(255) DEFAULT '' COMMENT '住址',
       `weight` int(3) NOT NULL DEFAULT '1' COMMENT '權(quán)重,大者優(yōu)先',
       `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
       `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
       PRIMARY KEY (`user_id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      INSERT INTO `user` VALUES ('1196978513958141952', '測(cè)試1', '18826334748', '廣州市海珠區(qū)', '1', '2019-11-20 10:28:51', '2019-11-22 14:28:26');
      INSERT INTO `user` VALUES ('1196978513958141953', '測(cè)試2', '18826274230', '廣州市天河區(qū)', '2', '2019-11-20 10:29:37', '2019-11-22 14:28:14');
      INSERT INTO `user` VALUES ('1196978513958141954', '測(cè)試3', '18826273900', '廣州市天河區(qū)', '1', '2019-11-20 10:30:19', '2019-11-22 14:28:30');

      主從數(shù)據(jù)源配置

      application.yml,主要信息是主從庫的數(shù)據(jù)源配置

      server:
       port: 8001
      spring:
       jackson:
       date-format: yyyy-MM-dd HH:mm:ss
       time-zone: GMT+8
       datasource:
       type: com.alibaba.druid.pool.DruidDataSource
       driver-class-name: com.mysql.cj.jdbc.Driver
       master:
       url: jdbc:mysql://127.0.0.1:3307/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
       username: root
       password:
       slave:
       url: jdbc:mysql://127.0.0.1:3308/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
       username: root
       password:
      因?yàn)橛幸恢饕粡膬蓚€(gè)數(shù)據(jù)源,我們用枚舉類來代替,方便我們使用時(shí)能對(duì)應(yīng)
      
      @Getter
      public enum DynamicDataSourceEnum {
       MASTER("master"),
       SLAVE("slave");
       private String dataSourceName;
       DynamicDataSourceEnum(String dataSourceName) {
       this.dataSourceName = dataSourceName;
       }
      }

      數(shù)據(jù)源配置信息類 DataSourceConfig ,這里配置了兩個(gè)數(shù)據(jù)源,masterDb和slaveDb

      @Configuration
      @MapperScan(basePackages = "com.xjt.proxy.mapper", sqlSessionTemplateRef = "sqlTemplate")
      public class DataSourceConfig {
      
       // 主庫
       @Bean
       @ConfigurationProperties(prefix = "spring.datasource.master")
       public DataSource masterDb() {
       return DruidDataSourceBuilder.create().build();
       }
      
       /**
       * 從庫
       */
       @Bean
       @ConditionalOnProperty(prefix = "spring.datasource", name = "slave", matchIfMissing = true)
       @ConfigurationProperties(prefix = "spring.datasource.slave")
       public DataSource slaveDb() {
       return DruidDataSourceBuilder.create().build();
       }
      
       /**
       * 主從動(dòng)態(tài)配置
       */
       @Bean
       public DynamicDataSource dynamicDb(@Qualifier("masterDb") DataSource masterDataSource,
       @Autowired(required = false) @Qualifier("slaveDb") DataSource slaveDataSource) {
       DynamicDataSource dynamicDataSource = new DynamicDataSource();
       Map targetDataSources = new HashMap<>();
       targetDataSources.put(DynamicDataSourceEnum.MASTER.getDataSourceName(), masterDataSource);
       if (slaveDataSource != null) {
       targetDataSources.put(DynamicDataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource);
       }
       dynamicDataSource.setTargetDataSources(targetDataSources);
       dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
       return dynamicDataSource;
       }
       @Bean
       public SqlSessionFactory sessionFactory(@Qualifier("dynamicDb") DataSource dynamicDataSource) throws Exception {
       SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
       bean.setMapperLocations(
       new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
       bean.setDataSource(dynamicDataSource);
       return bean.getObject();
       }
       @Bean
       public SqlSessionTemplate sqlTemplate(@Qualifier("sessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
       }
       @Bean(name = "dataSourceTx")
       public DataSourceTransactionManager dataSourceTx(@Qualifier("dynamicDb") DataSource dynamicDataSource) {
       DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
       dataSourceTransactionManager.setDataSource(dynamicDataSource);
       return dataSourceTransactionManager;
       }
      }

      設(shè)置路由

      設(shè)置路由的目的為了方便查找對(duì)應(yīng)的數(shù)據(jù)源,我們可以用ThreadLocal保存數(shù)據(jù)源的信息到每個(gè)線程中,方便我們需要時(shí)獲取

      public class DataSourceContextHolder {
       private static final ThreadLocal DYNAMIC_DATASOURCE_CONTEXT = new ThreadLocal<>();
       public static void set(String datasourceType) {
       DYNAMIC_DATASOURCE_CONTEXT.set(datasourceType);
       }
       public static String get() {
       return DYNAMIC_DATASOURCE_CONTEXT.get();
       }
       public static void clear() {
       DYNAMIC_DATASOURCE_CONTEXT.remove();
       }
      }

      獲取路由

      public class DynamicDataSource extends AbstractRoutingDataSource {
       @Override
       protected Object determineCurrentLookupKey() {
       return DataSourceContextHolder.get();
       }
      }

      AbstractRoutingDataSource的作用是基于查找key路由到對(duì)應(yīng)的數(shù)據(jù)源,它內(nèi)部維護(hù)了一組目標(biāo)數(shù)據(jù)源,并且做了路由key與目標(biāo)數(shù)據(jù)源之間的映射,提供基于key查找數(shù)據(jù)源的方法。

      數(shù)據(jù)源的注解

      為了可以方便切換數(shù)據(jù)源,我們可以寫一個(gè)注解,注解中包含數(shù)據(jù)源對(duì)應(yīng)的枚舉值,默認(rèn)是主庫,

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      @Documented
      public @interface DataSourceSelector {
      
       DynamicDataSourceEnum value() default DynamicDataSourceEnum.MASTER;
       boolean clear() default true;
      }

      aop切換數(shù)據(jù)源

      到這里,aop終于可以現(xiàn)身出場(chǎng)了,這里我們定義一個(gè)aop類,對(duì)有注解的方法做切換數(shù)據(jù)源的操作,具體代碼如下:

      @Slf4j
      @Aspect
      @Order(value = 1)
      @Component
      public class DataSourceContextAop {
      
       @Around("@annotation(com.xjt.proxy.dynamicdatasource.DataSourceSelector)")
       public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable {
       boolean clear = true;
       try {
       Method method = this.getMethod(pjp);
       DataSourceSelector dataSourceImport = method.getAnnotation(DataSourceSelector.class);
       clear = dataSourceImport.clear();
       DataSourceContextHolder.set(dataSourceImport.value().getDataSourceName());
       log.info("========數(shù)據(jù)源切換至:{}", dataSourceImport.value().getDataSourceName());
       return pjp.proceed();
       } finally {
       if (clear) {
       DataSourceContextHolder.clear();
       }
      
       }
       }
       private Method getMethod(JoinPoint pjp) {
       MethodSignature signature = (MethodSignature)pjp.getSignature();
       return signature.getMethod();
       }
      
      }

      到這一步,我們的準(zhǔn)備配置工作就完成了,下面開始測(cè)試效果。

      先寫好Service文件,包含讀取和更新兩個(gè)方法,

      @Service
      public class UserService {
      
       @Autowired
       private UserMapper userMapper;
      
       @DataSourceSelector(value = DynamicDataSourceEnum.SLAVE)
       public List listUser() {
       List users = userMapper.selectAll();
       return users;
       }
      
       @DataSourceSelector(value = DynamicDataSourceEnum.MASTER)
       public int update() {
       User user = new User();
       user.setUserId(Long.parseLong("1196978513958141952"));
       user.setUserName("修改后的名字2");
       return userMapper.updateByPrimaryKeySelective(user);
       }
      
       @DataSourceSelector(value = DynamicDataSourceEnum.SLAVE)
       public User find() {
       User user = new User();
       user.setUserId(Long.parseLong("1196978513958141952"));
       return userMapper.selectByPrimaryKey(user);
       }
      }

      根據(jù)方法上的注解可以看出,讀的方法走從庫,更新的方法走主庫,更新的對(duì)象是userId為 1196978513958141953 的數(shù)據(jù),

      然后我們寫個(gè)測(cè)試類測(cè)試下是否能達(dá)到效果,

      @RunWith(SpringRunner.class)
      @SpringBootTest
      class UserServiceTest {
      
       @Autowired
       UserService userService;
      
       @Test
       void listUser() {
       List users = userService.listUser();
       for (User user : users) {
       System.out.println(user.getUserId());
       System.out.println(user.getUserName());
       System.out.println(user.getUserPhone());
       }
       }
       @Test
       void update() {
       userService.update();
       User user = userService.find();
       System.out.println(user.getUserName());
       }
      }

      測(cè)試結(jié)果:

      1、讀取方法
      讀寫分離很難嗎?springboot結(jié)合aop簡(jiǎn)單就實(shí)現(xiàn)了

      2、更新方法
      讀寫分離很難嗎?springboot結(jié)合aop簡(jiǎn)單就實(shí)現(xiàn)了

      執(zhí)行之后,比對(duì)數(shù)據(jù)庫就可以發(fā)現(xiàn)主從庫都修改了數(shù)據(jù),說明我們的讀寫分離是成功的。當(dāng)然,更新方法可以指向從庫,這樣一來就只會(huì)修改到從庫的數(shù)據(jù),而不會(huì)涉及到主庫。

      注意

      上面測(cè)試的例子雖然比較簡(jiǎn)單,但也符合常規(guī)的讀寫分離配置。值得說明的是,讀寫分離的作用是為了緩解寫庫,也就是主庫的壓力,但一定要基于數(shù)據(jù)一致性的原則,就是保證主從庫之間的數(shù)據(jù)一定要一致。 如果一個(gè)方法涉及到寫的邏輯,那么該方法里所有的數(shù)據(jù)庫操作都要走主庫。

      假設(shè)寫的操作執(zhí)行完后數(shù)據(jù)有可能還沒同步到從庫,然后讀的操作也開始執(zhí)行了,如果這個(gè)讀取的程序走的依然是從庫的話,那么就會(huì)出現(xiàn)數(shù)據(jù)不一致的現(xiàn)象了,這是我們不允許的。


      文章名稱:讀寫分離很難嗎?springboot結(jié)合aop簡(jiǎn)單就實(shí)現(xiàn)了
      網(wǎng)站路徑:http://www.ef60e0e.cn/article/pjossi.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        廊坊市| 隆回县| 西乌| 江川县| 阜南县| 余庆县| 娄烦县| 光泽县| 洪雅县| 皮山县| 石嘴山市| 乃东县| 盖州市| 博兴县| 孟村| 子洲县| 突泉县| 靖江市| 大荔县| 酉阳| 九龙城区| 贡山| 碌曲县| 平昌县| 洛宁县| 深圳市| 广东省| 乌鲁木齐县| 睢宁县| 万源市| 新化县| 福海县| 囊谦县| 乐东| 团风县| 贡觉县| 新巴尔虎左旗| 天长市| 交城县| 招远市| 福贡县|