背景說明
數(shù)據(jù)庫基礎(chǔ)配置如數(shù)據(jù)源配置,mybatis配置,sharding相關(guān)配置等在多個(gè)項(xiàng)目中如果都進(jìn)行配置,大致會(huì)有以下幾個(gè)問題
- 需要編寫代碼(雖然可以復(fù)制,還是有些東西需要改),調(diào)試等,需要耗費(fèi)一定的時(shí)間
- 不同項(xiàng)目的這些基礎(chǔ)配置不一致,會(huì)帶來一些問題(我們總是希望同一體系的這些通用組件盡量一致)
- 當(dāng)有些內(nèi)容需要修改時(shí),需要識(shí)別哪些項(xiàng)目使用并一一修改.耗費(fèi)時(shí)間的同時(shí),也帶來一定風(fēng)險(xiǎn).如遺漏等
這些都是通用基礎(chǔ)配置普遍的問題,解決方案很自然的想到抽離封裝成通用組件.
本案例采用方式為抽離封裝成自定義springboot-starter組件.
starter組件說明
先看使用步驟以及效果
- 引入自定義starter依賴
<dependency>
<groupId>com.jiujiu</groupId>
<artifactId>jiu-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 啟動(dòng)類添加數(shù)據(jù)庫開關(guān)注解,指定需要注冊(cè)的數(shù)據(jù)庫列表.
如@EnableDB(value = {DBConfigEnum.TEST_DB, DBConfigEnum.ZCJ_DB})
@SpringBootApplication
@EnableDB(value = {DBConfigEnum.TEST_DB, DBConfigEnum.ZCJ_DB})
public class JiuDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JiuDemoApplication.class, args);
}
}
- 配置文件指定加載starter項(xiàng)目配置文件以及指定mapper的位置(包含mapper和對(duì)應(yīng)的xml)
spring:
application:
name: jiu-demo
profiles:
# 指定加載starter項(xiàng)目配置文件,這個(gè)順序會(huì)以排在后面的為準(zhǔn)
active: starter,starter-dev,dev
db:
mapperLocation:
test_db: com.example.jiudemo.mapper.testdb
zcj_db: com.example.jiudemo.mapper.zcjdb
mapperXmlLocation:
test_db: classpath:mapper/testdb/*.xml
zcj_db: classpath:mapper/zcjdb/*.xml
image.png
測(cè)試結(jié)果
調(diào)用mapper,返回?cái)?shù)據(jù)
image.png
自定義組件jiu-spring-boot-starter核心代碼(如何自定義starter網(wǎng)上很多資料)
依賴在文章末尾
@EnableDB
/**
* 通過定義枚舉列表動(dòng)態(tài)注冊(cè)數(shù)據(jù)庫
*/
@Retention(RUNTIME)
@Target(TYPE)
@Import({DBCommonAutoConfiguration.class,
// DBSelector.class
DBRegistrar.class
})
public @interface EnableDB {
DBConfigEnum[] value() default {};
}
- 配置類DBCommonAutoConfiguration主要解決MapperScannerConfigurer讀取不到配置文件屬性的問題
@Configuration
@ConditionalOnMissingBean(value = {DBCommonAutoConfiguration.class})
@EnableAutoConfiguration(exclude= {DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class, MybatisAutoConfiguration.class})
// 解決MapperScannerConfigurer讀取不到配置文件屬性的問題
@Import(value = {CustomPropertySourcesPlaceholder.class})
public class DBCommonAutoConfiguration {
}
- 配置類DBRegistrar或者DBSelector(實(shí)現(xiàn)動(dòng)態(tài)注冊(cè)bean的不同方式,選其一即可)
@Slf4j
public class DBRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 根據(jù)注解的枚舉值,注入對(duì)應(yīng)的XXXAutoConfiguration
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableDB.class.getName());
DBConfigEnum[] values = (DBConfigEnum[]) annotationAttributes.get("value");
if (ArrayUtils.isEmpty(values)) {
return;
}
List<String> dbList = Arrays.stream(values).map(DBConfigEnum::getDbName).collect(Collectors.toList());
log.info("準(zhǔn)備注冊(cè)數(shù)據(jù)庫,dbList->{}", dbList);
for (int i = 0; i < values.length; i++) {
DBConfigEnum dbConfigEnum = values[i];
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(dbConfigEnum.getConfigClassName());
registry.registerBeanDefinition(dbConfigEnum.getConfigClassName(), beanDefinition);
}
}
}
@Slf4j
public class DBSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根據(jù)注解的枚舉值,注入對(duì)應(yīng)的XXXAutoConfiguration
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableDB.class.getName());
DBConfigEnum[] values = (DBConfigEnum[]) annotationAttributes.get("value");
if (ArrayUtils.isEmpty(values)) {
return new String[0];
}
String[] array = new String[values.length];
List<String> dbList = Arrays.stream(values).map(DBConfigEnum::getDbName).collect(Collectors.toList());
log.info("準(zhǔn)備注冊(cè)數(shù)據(jù)庫,dbList->{}", dbList);
for (int i = 0; i < values.length; i++) {
DBConfigEnum dbConfigEnum = values[i];
array[i] = dbConfigEnum.getConfigClassName();
}
return array;
}
}
- DBConfigEnum
public enum DBConfigEnum {
TEST_DB("test_db", TestDBAutoConfiguration.class.getName()),
ZCJ_DB("zcj_db", ZcjDBAutoConfiguration.class.getName())
;
private String dbName;
private String configClassName;
DBConfigEnum(String dbName, String configClassName) {
this.dbName = dbName;
this.configClassName = configClassName;
}
public String getDbName() {
return dbName;
}
public String getConfigClassName() {
return configClassName;
}
}
- TestDBAutoConfiguration
@Configuration
// 指定配置文件
@ImportResource(locations = {"classpath:jdbc-testdb.xml"})
public class TestDBAutoConfiguration {
}
- jdbc-testdb.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xmlns:master-slave="http://shardingsphere.apache.org/schema/shardingsphere/masterslave"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd
http://shardingsphere.apache.org/schema/shardingsphere/masterslave
http://shardingsphere.apache.org/schema/shardingsphere/masterslave/master-slave.xsd">
<context:annotation-config />
<context:component-scan base-package="io.shardingsphere.example.spring.namespace.jpa" />
<context:property-placeholder location="classpath:**/*.yml" ignore-unresolvable="true"/>
<bean id="ds-testdb-master" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" >
<property name="name" value="ds-testdb-master"/>
<property name="driverClassName" value="${db.driver-class-name}"/>
<property name="url" value="${db.testdb-master.url}"/>
<property name="username" value="${db.testdb-master.username}"/>
<property name="password" value="${db.testdb-master.password}"/>
<!-- 啟動(dòng)程序時(shí)皂甘,在連接池中初始化多少個(gè)連接 -->
<property name="initialSize" value="10"/>
<!-- 啟動(dòng)程序時(shí)蓖议,在連接池中初始化多少個(gè)連接 -->
<property name="minIdle" value="5"/>
<!-- 連接池中最多支持多少個(gè)活動(dòng)會(huì)話 -->
<property name="maxActive" value="300"/>
<!-- 程序向連接池中請(qǐng)求連接時(shí),超過maxWait的值后捺弦,認(rèn)為本次請(qǐng)求失敗犯犁,即連接池沒有可用連接,單位毫秒桃犬,設(shè)置-1時(shí)表示無限等待 -->
<property name="maxWait" value="60000"/>
<property name="queryTimeout" value="600000"/>
<property name="socketTimeout" value="600000"/>
<!-- 檢查空閑連接的頻率颅悉,單位毫秒, 非正整數(shù)時(shí)表示不進(jìn)行檢查 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 池中某個(gè)連接的空閑時(shí)長達(dá)到 N 毫秒后, 連接池在下次檢查空閑連接時(shí)嚣镜,將回收該連接,要小于防火墻超時(shí)設(shè)置 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="transactionQueryTimeout" value="600000"/>
<!-- 用來檢測(cè)連接是否有效的sql -->
<property name="validationQuery" value="SELECT 'x'"/>
<!-- 空間時(shí)執(zhí)行 validationQuery -->
<property name="testWhileIdle" value="true"/>
<!-- 程序 申請(qǐng) 連接時(shí),進(jìn)行連接有效性檢查(低效,影響性能) -->
<property name="testOnBorrow" value="false"/>
<!-- 程序 返還 連接時(shí),進(jìn)行連接有效性檢查(低效辰狡,影響性能) -->
<property name="testOnReturn" value="false"/>
<!-- 別名的方式配置擴(kuò)展插件. stat:監(jiān)控統(tǒng)計(jì) -->
<property name="filters" value="stat"/>
</bean>
<bean id="ds-testdb-slave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" >
<property name="name" value="ds-testdb-slave"/>
<property name="driverClassName" value="${db.driver-class-name}"/>
<property name="url" value="${db.testdb-slave.url}"/>
<property name="username" value="${db.testdb-slave.username}"/>
<property name="password" value="${db.testdb-slave.password}"/>
<!-- 啟動(dòng)程序時(shí)锋叨,在連接池中初始化多少個(gè)連接 -->
<property name="initialSize" value="10"/>
<!-- 啟動(dòng)程序時(shí),在連接池中初始化多少個(gè)連接 -->
<property name="minIdle" value="5"/>
<!-- 連接池中最多支持多少個(gè)活動(dòng)會(huì)話 -->
<property name="maxActive" value="300"/>
<!-- 程序向連接池中請(qǐng)求連接時(shí),超過maxWait的值后宛篇,認(rèn)為本次請(qǐng)求失敗娃磺,即連接池沒有可用連接,單位毫秒叫倍,設(shè)置-1時(shí)表示無限等待 -->
<property name="maxWait" value="60000"/>
<property name="queryTimeout" value="600000"/>
<property name="socketTimeout" value="600000"/>
<!-- 檢查空閑連接的頻率偷卧,單位毫秒, 非正整數(shù)時(shí)表示不進(jìn)行檢查 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 池中某個(gè)連接的空閑時(shí)長達(dá)到 N 毫秒后, 連接池在下次檢查空閑連接時(shí),將回收該連接,要小于防火墻超時(shí)設(shè)置 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="transactionQueryTimeout" value="600000"/>
<!-- 用來檢測(cè)連接是否有效的sql -->
<property name="validationQuery" value="SELECT 'x'"/>
<!-- 空間時(shí)執(zhí)行 validationQuery -->
<property name="testWhileIdle" value="true"/>
<!-- 程序 申請(qǐng) 連接時(shí),進(jìn)行連接有效性檢查(低效吆倦,影響性能) -->
<property name="testOnBorrow" value="false"/>
<!-- 程序 返還 連接時(shí),進(jìn)行連接有效性檢查(低效听诸,影響性能) -->
<property name="testOnReturn" value="false"/>
<!-- 別名的方式配置擴(kuò)展插件. stat:監(jiān)控統(tǒng)計(jì) -->
<property name="filters" value="stat"/>
</bean>
<!-- 讀寫分離 -->
<master-slave:load-balance-algorithm id="randomStrategy" type="RANDOM" />
<sharding:inline-strategy id="orderTableStrategy" sharding-column="account_no" algorithm-expression="product_order_$->{ account_no % 2 }" />
<sharding:data-source id="testdb-shardingDataSource">
<sharding:sharding-rule data-source-names="ds-testdb-master,ds-testdb-slave">
<sharding:master-slave-rules>
<sharding:master-slave-rule id="ds_testdb_ms" master-data-source-name="ds-testdb-master" slave-data-source-names="ds-testdb-slave" strategy-ref="randomStrategy" />
</sharding:master-slave-rules>
<sharding:table-rules>
<sharding:table-rule logic-table="product_order" actual-data-nodes="ds_testdb_ms.product_order_$->{0..1}" table-strategy-ref="orderTableStrategy" />
</sharding:table-rules>
</sharding:sharding-rule>
<sharding:props>
<prop key="sql.show">true</prop>
</sharding:props>
</sharding:data-source>
<!-- myBatis文件 -->
<bean id="sqlSessionFactory-testdb" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="testdb-shardingDataSource"/>
<!-- mapperXml路徑 -->
<property name="mapperLocations" value="${db.mapperXmlLocation.test_db}"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<bean id="mapperScan-testdb" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- mapper路徑 -->
<property name="basePackage" value="${db.mapperLocation.test_db}"/>
<!-- <property name="basePackage" value="com.example.jiudemo.mapper.testdb"/>-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory-testdb"/>
</bean>
<bean id="sqlSessionTemplate-zcjdb" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory-testdb"/>
</bean>
<!-- 事務(wù) -->
<bean id="transactionManager-testdb" name="transactionManager-testdb" primary="false" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="testdb-shardingDataSource"/>
</bean>
<!-- 配置 Annotation 驅(qū)動(dòng),掃描@Transactional注解的類定義事務(wù) -->
<!-- <tx:annotation-driven transaction-manager="transactionManager-testdb" proxy-target-class="true"/>-->
</beans>
-
配置文件
image.png
db:
driver-class-name: com.mysql.cj.jdbc.Driver
maxActive: 30
zcjdb:
url: jdbc:mysql://${instance.ip}:3306/zcjdb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&useSSL=false
username: root
password: xxxxxx
testdb-master:
url: jdbc:mysql://${instance.ip}:3306/testdb_master?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&useSSL=false
username: root
password: xxxxxx
testdb-slave:
url: jdbc:mysql://${instance.ip}:3306/testdb_slave?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8&useSSL=false
username: root
password: xxxxxx
- 相關(guān)依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jiujiu</groupId>
<artifactId>jiu-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jiu-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 配置自動(dòng)提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!--數(shù)據(jù)庫連接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- pagehelper 分頁插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring-boot-starter</artifactId>
<groupId>org.mybatis.spring.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.1.1</version>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
</dependency>
<!--用于加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.69</version>
</dependency>
</dependencies>
</project>