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メソッドに注入します.
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
, 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();
}
}
}
。