Springboot入門シリーズチュートリアル(4)-データベース操作のmybatis(マルチデータソースを含む自動切り替えスキーム)


一、mybatisの使用を紹介する前に、前のJPAの使用に続き、両者の関連を簡単に比較する.
1、mybatisの利点は、受信結果セットの属性が対応するデータベースに戻るフィールドが見つからない場合、エラーが報告されず、空の値が割り当てられ、JPAがエラーを報告することです.
2、mybatisは@Restultで単独で結果セットデータベースフィールドとクラスオブジェクト属性のマッピングを行うことができる.
3、mybatisはJPAのように、受信オブジェクトの中にもう一つのオブジェクト属性を書いて他のテーブルのデータを受信することはできません.
4、mybatisの欠点は、結果セットを返すことがデータベースの元のフィールドであり、受信した結果セットの属性はデータベースの元のフィールドに直接対応しなければならない.もちろん、アルパカに名前を付けたり、sqlに別名を返したり、mapフィールドマッピングを返したりすることができる.
5、mybatisがデータを受け入れるクラスすべての属性は基礎データまたは基礎データの包装クラスでなければならず、カスタムオブジェクトではない
6、insert ignore intoプロパティを有効にするには、重複できないフィールドにインデックスの一意性制約(unique)を追加する必要があります.
7,@ResourceはJDKオリジナルの注釈であり,デフォルトはbeanのクラス名によって識別され,@Autowiredはspringフレームワークの注釈であり,デフォルトはタイプによって識別される.mybatisはmapper注入時に@Resourceで導入する.もちろんmapperにサービスの注釈を追加して@Autowiredでタイプ注入することもできます.
 
二、デュアルデータソースまたはマルチデータソースを構成するには、一般的に2つの方法がある.1つは、データソースを動的に設定し、1つはパケット化してデータソースとmapperファイルを書き込みます.
1デュアルデータソースの設定後、デフォルト構成のデータソースが間違って報告されます.同じタイプのbeanが複数存在するため、プログラムが認識できません.データソースに注釈別名を個別に構成する必要があります.2新しいデータソースプロファイルの注釈では、データソーススキャンを変更するMapperファイルを構成する必要があります.そうしないと、mapperファイルもそのデータソースを使うことを知らないので、3新しいデータソースプロファイルの注釈では、sqlSessionFactoryのbean名前とsqlTempleteのbean名前を構成する必要があります.4注記で参照されているsqlSessionFactoryとsqlTempleteは認識できません.そのうちの1つを参照すればいいだけです.もちろん2つを参照しても問題ありません.ログには重複参照のアラーム情報があります.5つ以上のフレームワークを同時に使用する場合も注意してください.DataSocurceというBeanが複数インスタンス化された後、データソースに別名の注釈を付けると、認識できなくなります.例えば、私の次の例では、hibernateとmybatisの依存性が実際に同時に入っており、必要に応じて呼び出すことができます.6 Springboot 2.2.5以降のクラスでは、データソースがコンストラクタモードに変更され、直接注釈で属性をインポートしてデータソースオブジェクトにマッピングすることはできません.手動でインポートした属性を1つずつ設定する必要があります.hirikaプールのデータソース後者druidプールのデータソースを直接返すこともできます.これにより、newインスタンス化が必要なオブジェクトのデータソースごとに、注釈構成のプロパティを直接インポートできます.7 mybatisデュアルデータソース構成が完了したら、対応するmapperファイルをそれぞれ書き、データソース構成ファイルにmapperscanが適用するmapperが必要になります.その後、アプリケーションにmapperを注入して8 mybatisデュアルデータ構成を呼び出すと、元のmybatisの構成も失効します.この場合、org.apache.ibatis.session.configurationを再継承し、既存のymlファイルの構成をインポートする必要があります.次に、この構成を新しいデータ・ソース構成ファイルのsqlSessionFactory、特にsqlSessionFactoryBean.SetConfigurationメソッドに注入します.
 
 、   AOP           
1   ,                   bean,      null  。     thread , thread     ,                  IOC    bean。   run      ,              ,new                 。

2             AbstractRoutingDataSource   ,  determineCurrentLookupKey()            key           。

3   AbstractRoutingDataSource ,         bean,         ,           ,        HashMap        。   Bean              ,       SET           

4          ,              ,             ,          sessionFactory    bean。  factory tempelte bean                  MapperScan    。@MapperScan(basePackages = "com.ywcai.demo.doubleDs.aop", sqlSessionFactoryRef = "aopSqlSessionFactory", sqlSessionTemplateRef = "aopSqlSessionTemplate")。           ,         。                  3         。       mybatis     ,        ,    bean     。

5       ,        mybatis       。

  aopSqlSessionFactoryBean.setDataSource(aopRoutingDataSource);

  aopSqlSessionFactoryBean.setConfiguration(myBatisGlobalConfig);

6                ,          bean,   bean   DBConetxtHolder。    1       , bean                    ,                 ,                 。     bean            ,     ThreadLocal,      ThreadLocal              。

7   AOP Mapper    ,     get              。 setSlave  ===>>> DBConetxtHolder  ThreadLocal contextHolder = new ThreadLocal<>()  contextHolder      DBTypeEnum.Slave。       contextHolder           ,             。

8      AOP            。 aop    mapper     。     ,   *             。   *              。   *         ,()             。            2                       

9 mapper        ,     mapper  ,bean            ,     @service  @Commpanet ,  type    。          @Autowired          。

10 springboot 2.2.5     hikariPool    ,         ,           ,          。      hikariPool        ,    。

11               hikari          

12            ,         ,          ,            。

13         countdownlatch      ,             。           ,        。     CompletionService   ExecutorCompletionService     callable。 ExecutorCompletionService     take().get()              。           ,        ,              。
 
 、     AOP           hikari   ,                   mybatis       。
1、        ,           。


    mysql

    mysql-connector-java





    org.mybatis.spring.boot

    mybatis-spring-boot-starter

    2.1.2

 
2、yml       ,              ,                          。
#    mybatis     ,              ,            。 

mybatis:

  configuration:

    map-underscore-to-camel-case: true


#                     。

#hikari        .springboot2.0      hikari   

#                       hikari    

ywcai:

  master:

    datasource:

      driver-class-name: com.mysql.cj.jdbc.Driver

      username: root

      password: Jdsj2019!

      jdbc-url: jdbc:mysql://47.108.130.221:3306/ywcai?characterEncoding=utf-8&serverTimezone=UTC
 #      ,hikari              jdbcUrl,          jdbc-url    url

      maximum-pool-size: 20   #   -1       。

      auto-commit: true

      minimum-idle: 5

      pool-name: masterPools



  slave:

    datasource:

      driver-class-name: com.mysql.cj.jdbc.Driver

      username: root

      password: Jdsj2019!

      jdbc-url: jdbc:mysql://47.108.130.221:3306/ywcai?characterEncoding=utf-8&serverTimezone=UTC

      maximum-pool-size: 20   #   -1       。

      auto-commit: true

      minimum-idle: 5

      pool-name: slavePools
 
 
3、    、         

       
package com.ywcai.demo.doubleDs;


@Slf4j

@Configuration

@MapperScan(basePackageClasses = {MasterMapper.class}

        , sqlSessionTemplateRef = "masterSqlSessionTemplate")

public class MasterDsConfig {

//   mybatis             master salve       。
    @Autowired
    MyBatisGlobalConfig myBatisGlobalConfig;

    @Bean(name = "masterDataSource")
    @Qualifier(value = "masterDataSource")
    @ConfigurationProperties(prefix = "ywcai.master.datasource")
    public HikariDataSource dataSource() {
        return  new HikariDataSource();
    }


    //           
    @Bean(name = "masterSqlSessionFactory")
    @Qualifier(value = "masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(masterDataSource);
        //      mybatis   
        bean.setConfiguration(myBatisGlobalConfig);
        //      ,         xml     。        mapper    
//        bean.setMapperLocations(
//                //   mybatis xml    
//                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test01/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "masterSqlSessionTemplate")
    @Qualifier(value = "masterSqlSessionTemplate")
    public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory")
                                                               SqlSessionFactory masterSqlSessionFactory) {
        return new SqlSessionTemplate(masterSqlSessionFactory);
    }

    @Bean(name = "masterTransactionManager")
    @Qualifier(value = "masterTransactionManager")
    public DataSourceTransactionManager transactionManager
(@Qualifier("masterDataSource") DataSource masterDataSource) {
        return new DataSourceTransactionManager(masterDataSource);
    }
}
 
Slave       
package com.ywcai.demo.doubleDs;


@Slf4j
@Configuration
@MapperScan(basePackageClasses = {SlaveMapper.class}
        , sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDsConfig {


//  mybatis    ,    
    @Autowired
    MyBatisGlobalConfig myBatisGlobalConfig;

    @Bean(name = "slaveDataSource")
    @Qualifier(value = "slaveDataSource")
    @ConfigurationProperties(prefix = "ywcai.slave.datasource")
    public HikariDataSource dataSource() {
        return new HikariDataSource();
    }

    //           
    @Bean(name = "slaveSqlSessionFactory")
    @Qualifier(value = "slaveSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource slaveDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(slaveDataSource);
        bean.setConfiguration(myBatisGlobalConfig);
        //      ,         xml     。        mapper    
//        bean.setMapperLocations(
//                //   mybatis xml    
//                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test01/*.xml"));
        return bean.getObject();
    }


    @Bean(name = "slaveSqlSessionTemplate")
    @Qualifier(value = "slaveSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate
(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory slaveSqlSessionFactory) {
        return new SqlSessionTemplate(slaveSqlSessionFactory);
    }

    @Bean(name = "slaveTransactionManager")
    @Qualifier(value = "slaveTransactionManager")
    public DataSourceTransactionManager transactionManager
(@Qualifier("slaveDataSource") DataSource slaveDataSource) {
        return new DataSourceTransactionManager(slaveDataSource);
    }

}
            MyBatisGlobalConfig.class
package com.ywcai.demo.doubleDs;


//       ,    ,   bean  
@Configuration
@ConfigurationProperties("mybatis.configuration")
public class MyBatisGlobalConfig extends org.apache.ibatis.session.Configuration {

    /**

     * @           ,     yml       batis         。

     * @    jimi

     * @  

     * @   

     * @     2020/3/15

     */
}
       
package com.ywcai.demo.doubleDs.aop;


//        ,    AbstractRoutingDataSource ,   determineCurrentLookupKey()
//            
@Configuration
@Slf4j
public class AopRoutingDataSource extends AbstractRoutingDataSource {
    @Autowired
    DBContextHolder dbContextHolder;
    @Override
    protected Object determineCurrentLookupKey() {
        return dbContextHolder.get();
    }

    @Autowired
    public AopRoutingDataSource(
@Qualifier(value = "masterDataSource") HikariDataSource  masterDataSource,
@Qualifier(value = "slaveDataSource") HikariDataSource slaveDataSource) {
        setDefaultTargetDataSource(masterDataSource);
        log.info("masterDataSource  {}", masterDataSource.getHikariPoolMXBean());
        log.info("slaveDataSource  {}", slaveDataSource);
        Map targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource);
        setTargetDataSources(targetDataSources);
    }

}
 
                      ,  threadLocal             ,                  。DBContextHolder.class
package com.ywcai.demo.doubleDs.aop;


@Service
@Slf4j
public class DBContextHolder {

    private ThreadLocal contextHolder = new ThreadLocal<>();
    //                   ,         。 Aop      。
    private AtomicInteger counter = new AtomicInteger(-1);
    public void set(DBTypeEnum type) {
        contextHolder.set(type);
    }

    public DBTypeEnum get() {
        return contextHolder.get();
    }



   //       ,         
    public void setMaster() {
        set(DBTypeEnum.MASTER);
    }

    //       ,        、     ,     
    public void setSlave() {
        //      -1,       ,    2  ,       
        int a = counter.incrementAndGet();
        if (a % 2 == 0) {
            log.info("=============select master ============{}", a);
            set(DBTypeEnum.MASTER);
        } else {
            log.info("=============select slave ============{}", a);
            set(DBTypeEnum.SLAVE);
        }
    }
}
 
            
package com.ywcai.demo.doubleDs.aop;
public enum DBTypeEnum {
    MASTER, SLAVE;
}
 
   Mapper  
package com.ywcai.demo.doubleDs.aop;
@Service
@Mapper
public interface AopMapper {

    @Select("SELECT * FROM user ")
    List getAllUserInfo();

    @Delete("delete from roles where roles.user_id!=#{userId}")
    int deleteRole(@Param(value = "userId") long userId);

}
 
            
TestRole.class
package com.ywcai.demo.doubleDs.aop;
import lombok.Data;

@Data
public class TestRole {
    long id;
    String roleName;
    long userId;
}
 
TestUser.class
package com.ywcai.demo.doubleDs.aop;
import lombok.Data;

@Data
public class TestUser {
    long id;
    String username;
    String password;
}

 
AOP    DBAspect.class
package com.ywcai.demo.doubleDs.aop;
@Aspect
@Component
@Slf4j
public class DBAspect {

    @Autowired
    DBContextHolder dbContextHolder;
    // aop    mapper     
    //     ,   *             
    //   *             
    //   *         ,()             

    @Pointcut(value = 
"(execution(public * com.ywcai.demo.doubleDs.aop.AopMapper.get*(..)))||
(execution(public * com.ywcai.demo.doubleDs.aop.AopMapper.select*(..)))")
    public void setSlave() {
    }

    //JoinPoint           ,         。           
    //        ,                ,            。
  get         ,      。
    @Before("setSlave()")
    public void beforeSlave(JoinPoint joinPoint) {
        //         select  get ,      , SLAVE,           
        // SLAVE   ,    DEMO       ,         ,   SLAVE,   MASTER,   0  。
        dbContextHolder.setSlave();
    }

}
 
 

package com.ywcai.demo.doubleDs.aop;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
class AopDsTest {

    @Autowired
    AopMapper aopMapper;

    @Test
    void testAopGet() {
        // CountDownLatch,            ,       .
        ExecutorService exec = Executors.newFixedThreadPool(5);
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            AopThread aopThread = new AopThread(countDownLatch);
            exec.submit(aopThread);
        }

        //    ,   ,               ,  jvm            。
        //    countDownlatch            
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("exe complete !");

    }

    //                list        
    @Test
    void testAopGet3() {
        LinkedBlockingDeque linkedBlockingDeque = new LinkedBlockingDeque(100);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            Callable callable = new CallableTask();
            //         future   
//            Future future = executorService.submit(callable);
            //    callable   FutureTask,      ,      
            FutureTask futureTask = new FutureTask(callable);
            executorService.submit(futureTask);
//            futureTask.run();
            try {
                linkedBlockingDeque.putLast(futureTask);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //             ,         。       get    
        }

        //    ,   ,               ,  jvm            。
        //       ,     get   ,      ,             
        //                        ,             
        while (linkedBlockingDeque.size() > 0) {
            try {
                log.info("this result {}", ((FutureTask) linkedBlockingDeque.pollFirst()).get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }





    //  ExecutorCompletionService                  。    take().get()      
    @Test
    void testAopGet4() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CompletionService completionService = new ExecutorCompletionService(executorService);
        for (int i = 0; i < 10; i++) {
            Callable callable = new CallableTask();
            completionService.submit(callable);
            //             ,         。       get    

        }
        //        completionService   callable   。
        for (int i = 0; i < 10; i++) {
            try {
                log.info("the list size is {}", completionService.take().get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

//    delete  ,   master     。
    @Test
    void testAopDelete() {
        log.info("aopMapper delete role info {}", aopMapper.deleteRole(11l));
    }
}
 
 
 
Callable        
package com.ywcai.demo.doubleDs.aop;

@Slf4j
public class CallableTask implements Callable {


    @Override
    public Integer call() {
        AopMapper aopMapper = GetBeanUtil.getBean(AopMapper.class);
        int i = aopMapper.getAllUserInfo().size();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Thread ID is {}", Thread.currentThread().getId());
        return i;
    }
}
 
  countdowncatch          
AopThread.class
package com.ywcai.demo.doubleDs.aop;

@Slf4j
public class AopThread extends Thread {
    AopMapper aopMapper;
    CountDownLatch countDownLatch;
    public AopThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
        aopMapper = GetBeanUtil.getBean(AopMapper.class);
    }

    @Override
    public void run() {
        super.run();
        try {
            log.info("userinfo : {}", aopMapper.getAllUserInfo());
        } catch (Exception e) {
            log.error("AopThread run err : {}", e);
        } finally {
            countDownLatch.countDown();
        }
    }
}