背景说明
数据库基础配置如数据源配置,mybatis配置,sharding相关配置等在多个项目中如果都进行配置,大致会有以下几个问题
- 需要编写代码(虽然可以复制,还是有些东西需要改),调试等,需要耗费一定的时间
- 不同项目的这些基础配置不一致,会带来一些问题(我们总是希望同一体系的这些通用组件尽量一致)
- 当有些内容需要修改时,需要识别哪些项目使用并一一修改.耗费时间的同时,也带来一定风险.如遗漏等
这些都是通用基础配置普遍的问题,解决方案很自然的想到抽离封装成通用组件.
本案例采用方式为抽离封装成自定义springboot-starter组件.
starter组件说明
先看使用步骤以及效果
- 引入自定义starter依赖
<dependency>
<groupId>com.jiujiu</groupId>
<artifactId>jiu-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 启动类添加数据库开关注解,指定需要注册的数据库列表.
如@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项目配置文件以及指定mapper的位置(包含mapper和对应的xml)
spring:
application:
name: jiu-demo
profiles:
# 指定加载starter项目配置文件,这个顺序会以排在后面的为准
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
测试结果
调用mapper,返回数据
自定义组件jiu-spring-boot-starter核心代码(如何自定义starter网上很多资料)
依赖在文章末尾
@EnableDB
/**
* 通过定义枚举列表动态注册数据库
*/
@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(实现动态注册bean的不同方式,选其一即可)
@Slf4j
public class DBRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 根据注解的枚举值,注入对应的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("准备注册数据库,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) {
// 根据注解的枚举值,注入对应的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("准备注册数据库,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}"/>
<!-- 启动程序时,在连接池中初始化多少个连接 -->
<property name="initialSize" value="10"/>
<!-- 启动程序时,在连接池中初始化多少个连接 -->
<property name="minIdle" value="5"/>
<!-- 连接池中最多支持多少个活动会话 -->
<property name="maxActive" value="300"/>
<!-- 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,单位毫秒,设置-1时表示无限等待 -->
<property name="maxWait" value="60000"/>
<property name="queryTimeout" value="600000"/>
<property name="socketTimeout" value="600000"/>
<!-- 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接,要小于防火墙超时设置 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="transactionQueryTimeout" value="600000"/>
<!-- 用来检测连接是否有效的sql -->
<property name="validationQuery" value="SELECT 'x'"/>
<!-- 空间时执行 validationQuery -->
<property name="testWhileIdle" value="true"/>
<!-- 程序 申请 连接时,进行连接有效性检查(低效,影响性能) -->
<property name="testOnBorrow" value="false"/>
<!-- 程序 返还 连接时,进行连接有效性检查(低效,影响性能) -->
<property name="testOnReturn" value="false"/>
<!-- 别名的方式配置扩展插件. stat:监控统计 -->
<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}"/>
<!-- 启动程序时,在连接池中初始化多少个连接 -->
<property name="initialSize" value="10"/>
<!-- 启动程序时,在连接池中初始化多少个连接 -->
<property name="minIdle" value="5"/>
<!-- 连接池中最多支持多少个活动会话 -->
<property name="maxActive" value="300"/>
<!-- 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,单位毫秒,设置-1时表示无限等待 -->
<property name="maxWait" value="60000"/>
<property name="queryTimeout" value="600000"/>
<property name="socketTimeout" value="600000"/>
<!-- 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接,要小于防火墙超时设置 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="transactionQueryTimeout" value="600000"/>
<!-- 用来检测连接是否有效的sql -->
<property name="validationQuery" value="SELECT 'x'"/>
<!-- 空间时执行 validationQuery -->
<property name="testWhileIdle" value="true"/>
<!-- 程序 申请 连接时,进行连接有效性检查(低效,影响性能) -->
<property name="testOnBorrow" value="false"/>
<!-- 程序 返还 连接时,进行连接有效性检查(低效,影响性能) -->
<property name="testOnReturn" value="false"/>
<!-- 别名的方式配置扩展插件. stat:监控统计 -->
<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>
<!-- 事务 -->
<bean id="transactionManager-testdb" name="transactionManager-testdb" primary="false" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="testdb-shardingDataSource"/>
</bean>
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<!-- <tx:annotation-driven transaction-manager="transactionManager-testdb" proxy-target-class="true"/>-->
</beans>
-
配置文件
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
- 相关依赖
<?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>
<!-- 配置自动提示 -->
<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>
<!--数据库连接-->
<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>