提交 35c970e1 编写于 作者: yesgohome123's avatar yesgohome123

2023年6月15日22:32:55

上级 bb400a0e
...@@ -13,21 +13,9 @@ ...@@ -13,21 +13,9 @@
<artifactId>design-pattern</artifactId> <artifactId>design-pattern</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<version>1.2.83</version>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project> </project>
\ No newline at end of file
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSONArray;
import java.util.*;
public class Test {
public static void main(String[] args) {
List<User> sourceList = new ArrayList<>();
for(int i=0;i<10;i++){
User user = new User();
user.setId(i);
user.setName("a"+i);
user.setOrg("d"+i);
user.setParentName("p"+i);
sourceList.add(user);
}
List<User> targetList = new ArrayList<>();
for(int i=2;i<12;i++){
User user = new User();
user.setId(i);
user.setName("a"+i);
user.setOrg("d"+i);
user.setParentName("p"+i);
if(i==5 || i ==8){
user.setParentName(user.getParentName()+"aa");
}
targetList.add(user);
}
// 取到相同的数据,需要得到哪些字段变了
Collection<User> usersTarget = CollUtil.intersection(sourceList, targetList);
Collection<User> usersSource = CollUtil.intersection(targetList, sourceList);
// 变化字段
List<String> fiels =new ArrayList<>();
fiels.add("parentName");
fiels.add("name");
Map<Integer,List<String>> fieldsMap =new HashMap<>();
usersTarget.stream().forEach(t->{
usersSource.stream().forEach(s->{
if(t.equals(s)){
//对比字段
List<String> change =new ArrayList<>();
fiels.stream().forEach(field->{
Object targetValue = ReflectUtil.getFieldValue(t, field);
Object sourceValue = ReflectUtil.getFieldValue(s, field);
if(!targetValue.equals(sourceValue)){
change.add(field);
}
});
if(change.size()>0){
fieldsMap.put(t.getId(),change);
}
}
});
});
Collection<User> subtract = CollUtil.subtract(targetList, sourceList);
System.out.println(JSONArray.toJSONString(subtract));
}
}
class User{
private int id;
private String name;
private String org;
private String parentName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrg() {
return org;
}
public void setOrg(String org) {
this.org = org;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
@Override
public int hashCode() {
return this.getName().hashCode();
}
@Override
public boolean equals(Object obj) {
User target = (User) obj;
if(this.getName().equals(target.getName())&& this.getOrg().equals(target.getOrg())){
return true;
}
return false;
}
}
\ No newline at end of file
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cases</artifactId> <artifactId>eweb-cases</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-app</artifactId> <artifactId>chrm-auth-app</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-client</artifactId> <artifactId>chrm-auth-client</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-controller</artifactId> <artifactId>chrm-auth-controller</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-domain</artifactId> <artifactId>chrm-auth-domain</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-infrastructure</artifactId> <artifactId>chrm-auth-infrastructure</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
...@@ -29,6 +29,11 @@ ...@@ -29,6 +29,11 @@
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.eweb.frame</groupId>
<artifactId>eweb-sqltoy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
......
package cn.eweb.chrm.auth.common.handler;
import org.sagacity.sqltoy.plugins.IUnifyFieldsHandler;
public class SqlToyUnifyFieldsHandler implements IUnifyFieldsHandler {
}
<?xml version="1.0" encoding="utf-8"?>
<sqltoy xmlns="http://www.sagframe.com/schema/sqltoy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sagframe.com/schema/sqltoy http://www.sagframe.com/schema/sqltoy/sqltoy.xsd">
<!-- 根据实体参数获取查询用户 -->
<sql id="system_user_findByEntityParam">
<value>
<![CDATA[
select su.*, sr.ROLE_NAME, sr.ID as roleId
from sys_user su
left join sys_role sr on sr.ID in (select ROLE_ID from sys_user_role where USER_ID=su.ID)
where
#[and su.ID = :id]
#[and su.USERNAME = :username]
#[and su.NICKNAME = :nickname]
#[and su.PASSWORD = :password]
and su.DELETE_STATUS=1
]]>
</value>
</sql>
<!-- 根据实体参数分页查询用户 -->
<sql id="system_user_findByPageParam">
<value>
<![CDATA[
select su.*, sr.ROLE_NAME, sr.ID as roleId
from sys_user su
left join sys_role sr on sr.ID in (select ROLE_ID from sys_user_role where USER_ID=su.ID)
where
#[and (su.NICKNAME like :nickname or su.USERNAME like :username)]
and su.DELETE_STATUS=1
]]>
</value>
</sql>
</sqltoy>
\ No newline at end of file
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
<artifactId>chrm-auth-start</artifactId> <artifactId>chrm-auth-start</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -24,7 +24,7 @@ sra: ...@@ -24,7 +24,7 @@ sra:
spring: spring:
sqltoy: sqltoy:
# 配置sql文件路径,多个用逗号分割 # 配置sql文件路径,多个用逗号分割
sqlResourcesDir: classpath:mapper sqlResourcesDir: classpath:ewebmapper
# 默认为false,debug模式将打印执行sql,并自动检测sql文件更新并重新加载 # 默认为false,debug模式将打印执行sql,并自动检测sql文件更新并重新加载
debug: true debug: true
datasource: datasource:
......
...@@ -74,7 +74,7 @@ spring: ...@@ -74,7 +74,7 @@ spring:
min-idle: 0 min-idle: 0
# sqltoy框架相关配置 # sqltoy框架相关配置
sqltoy: sqltoy:
unify-fields-handler: com.jwss.sra.config.sqltoy.SqlToyUnifyFieldsHandler unify-fields-handler: cn.eweb.chrm.auth.common.handler.SqlToyUnifyFieldsHandler
jackson: jackson:
date-format: yyyy-MM-dd HH:mm:ss date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 time-zone: GMT+8
......
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
<module>chrm-auth-start</module> <module>chrm-auth-start</module>
</modules> </modules>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-chrm-office</artifactId> <artifactId>eweb-chrm-office</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-chrm-resources</artifactId> <artifactId>eweb-chrm-resources</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-chrm-tool</artifactId> <artifactId>eweb-chrm-tool</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 基础框架 --> <!-- 基础框架 -->
...@@ -22,20 +22,34 @@ ...@@ -22,20 +22,34 @@
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-data-redis.version>2.7.12</spring-data-redis.version> <spring-data-redis.version>2.7.12</spring-data-redis.version>
<spring-data-mongo.version>3.4.12</spring-data-mongo.version> <spring-data-mongo.version>3.4.12</spring-data-mongo.version>
<mongo.version>3.12.14</mongo.version>
<druid.version>1.2.18</druid.version>
<ojdbc.version>21.10.0.0</ojdbc.version>
<taos-jdbcdriver.version>3.2.2</taos-jdbcdriver.version>
<h2.version>2.1.214</h2.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version> <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<taos-jdbcdriver.version>3.2.1</taos-jdbcdriver.version>
<ojdbc.version>21.9.0.0</ojdbc.version>
<elastic-rest-client.version>8.8.0</elastic-rest-client.version>
<transmittable-thread-local>2.14.2</transmittable-thread-local>
<mongo.version>3.12.13</mongo.version>
<ehcache.version>3.10.8</ehcache.version> <ehcache.version>3.10.8</ehcache.version>
<caffeine.version>2.9.3</caffeine.version> <caffeine.version>2.9.3</caffeine.version>
<fastjson.version>1.2.83_noneautotype</fastjson.version>
<httpclient.version>4.5.14</httpclient.version>
<httpclient-core.version>4.4.16</httpclient-core.version>
<httpclient-httpmime.version>4.5.14</httpclient-httpmime.version>
<elastic-rest-client.version>8.8.1</elastic-rest-client.version>
<fastjson.version>2.0.33</fastjson.version>
<slf4j.version>1.7.36</slf4j.version>
<transmittable-thread-local>2.14.2</transmittable-thread-local>
<hutool.version>5.8.15</hutool.version>
<mapstruct.version>1.5.3.Final</mapstruct.version> <mapstruct.version>1.5.3.Final</mapstruct.version>
<lombok.version>1.18.28</lombok.version> <lombok.version>1.18.28</lombok.version>
<lombok-mapstruct.version>0.2.0</lombok-mapstruct.version> <lombok-mapstruct.version>0.2.0</lombok-mapstruct.version>
<hutool.version>5.8.15</hutool.version>
<junit-jupiter.version>5.9.3</junit-jupiter.version> <junit-jupiter.version>5.9.3</junit-jupiter.version>
<junit-platform.version>1.9.3</junit-platform.version> <junit-platform.version>1.9.3</junit-platform.version>
</properties> </properties>
...@@ -48,6 +62,16 @@ ...@@ -48,6 +62,16 @@
<artifactId>eweb-common-utils</artifactId> <artifactId>eweb-common-utils</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>cn.eweb.frame</groupId>
<artifactId>eweb-sqltoy-orm</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.eweb.frame</groupId>
<artifactId>eweb-sqltoy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Spring Framework 依赖 --> <!-- Spring Framework 依赖 -->
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
...@@ -100,69 +124,12 @@ ...@@ -100,69 +124,12 @@
<scope>test</scope> <scope>test</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <version>8.0.33</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- mp -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 类型转换处理器 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- 类型转换 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!--
https://mvnrepository.com/artifact/com.taosdata.jdbc/taos-jdbcdriver -->
<dependency> <dependency>
<groupId>com.taosdata.jdbc</groupId> <groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId> <artifactId>taos-jdbcdriver</artifactId>
...@@ -183,10 +150,13 @@ ...@@ -183,10 +150,13 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!-- 20220829 增加对h2数据库的驱动 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.h2database</groupId>
<artifactId>transmittable-thread-local</artifactId> <artifactId>h2</artifactId>
<version>${transmittable-thread-local}</version> <version>${h2.version}</version>
<scope>test</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
...@@ -210,72 +180,79 @@ ...@@ -210,72 +180,79 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-beans</artifactId> <artifactId>spring-data-redis</artifactId>
<version>${spring-framework.version}</version> <version>${spring-data-redis.version}</version>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.mongodb</groupId>
<artifactId>spring-core</artifactId> <artifactId>mongo-java-driver</artifactId>
<version>${spring-framework.version}</version> <version>${mongo.version}</version>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.slf4j</groupId>
<artifactId>spring-context</artifactId> <artifactId>slf4j-api</artifactId>
<version>${spring-framework.version}</version> <version>${slf4j.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>com.alibaba</groupId>
<artifactId>spring-jdbc</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
<version>${spring-framework.version}</version> <version>${druid.version}</version>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>com.alibaba</groupId>
<artifactId>spring-context-support</artifactId> <artifactId>druid</artifactId>
<version>${spring-framework.version}</version> <version>${druid.version}</version>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- mp -->
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>com.baomidou</groupId>
<artifactId>spring-data-redis</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>${spring-data-redis.version}</version> <version>${mybatis-plus.version}</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>cn.hutool</groupId>
<artifactId>spring-data-mongodb</artifactId> <artifactId>hutool-all</artifactId>
<version>${spring-data-mongo.version}</version> <version>${hutool.version}</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>com.google.guava</groupId>
<artifactId>mongo-java-driver</artifactId> <artifactId>guava</artifactId>
<version>${mongo.version}</version> <version>31.1-jre</version>
<scope>provided</scope> </dependency>
<optional>true</optional>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${transmittable-thread-local}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
...@@ -324,6 +301,66 @@ ...@@ -324,6 +301,66 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- 类型转换处理器 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- 类型转换 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring-framework.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.19.0</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cola-catchlog</artifactId> <artifactId>eweb-cola-catchlog</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cola-domain</artifactId> <artifactId>eweb-cola-domain</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cola-dto</artifactId> <artifactId>eweb-cola-dto</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cola-extension</artifactId> <artifactId>eweb-cola-extension</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-cola-statemachine</artifactId> <artifactId>eweb-cola-statemachine</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
<artifactId>eweb-cola</artifactId> <artifactId>eweb-cola</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-common-core</artifactId> <artifactId>eweb-common-core</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-common-utils</artifactId> <artifactId>eweb-common-utils</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
</modules> </modules>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-domain-event</artifactId> <artifactId>eweb-domain-event</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
package cn.eweb.frame.event;
import cn.eweb.frame.event.disruptor.EventDisruptor;
import com.lmax.disruptor.dsl.Disruptor;
import lombok.Getter;
import lombok.Setter;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
/**
* 负责 Disruptor 的管理
*
*/
public class DisruptorManager {
/**
* 事件主题
* <pre>
* key : topic
* value : 事件订阅
* </pre>
*/
private final Map<Class<?>, Disruptor<EventDisruptor>> topicMap = new ConcurrentHashMap<>();
/** 领域事件线程名前缀 */
@Setter
@Getter
private String threadNamePrefix = "iohao.domain";
/**
* 获取所有Disruptor
*
* @return Disruptor集合
*/
Collection<Disruptor<EventDisruptor>> listDisruptor() {
return topicMap.values();
}
void forEach(Consumer<Disruptor<EventDisruptor>> action) {
listDisruptor().forEach(action);
}
/**
* 获取领域消息主题对应的Disruptor
*
* @param topic 领域消息主题
* @return Disruptor
*/
Disruptor<EventDisruptor> getDisruptor(final Class<?> topic) {
return topicMap.get(topic);
}
void put(Class<?> topic, Disruptor<EventDisruptor> disruptor) {
topicMap.putIfAbsent(topic, disruptor);
}
private DisruptorManager() {
}
public static DisruptorManager me() {
return Holder.ME;
}
/** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */
private static class Holder {
static final DisruptorManager ME = new DisruptorManager();
}
}
package cn.eweb.frame.event;
import cn.eweb.frame.event.disruptor.ConsumeEventHandler;
import cn.eweb.frame.event.disruptor.DisruptorCreate;
import cn.eweb.frame.event.disruptor.EventDisruptor;
import cn.eweb.frame.event.message.DomainEventHandler;
import com.lmax.disruptor.dsl.Disruptor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* 领域事件上下文
*
* @author 渔民小镇
* @date 2021-12-26
*/
@Slf4j
public class DomainEventContext {
final DomainEventContextParam param;
public DomainEventContext(DomainEventContextParam param) {
this.param = param;
}
/**
* <pre>
* 初始化配置,Disruptor
* 一个topic只能对应一个disruptor,且只有第一个有效,后面添加的将无效。
* 如果并发与队列事件同时存在(就是topic是同一个名字),则优先保存并发的topic的。队列的将无效
* </pre>
*
* @return true 成功启动
*/
@SuppressWarnings("unchecked")
public boolean startup() {
AtomicBoolean init = param.getInit();
if (!init.compareAndSet(false, true)) {
return true;
}
Set<DomainEventHandler<?>> domainEventHandlerSet = param.domainEventHandlerSet;
domainEventHandlerSet.stream().collect(Collectors.groupingBy(o -> {
// 一个key对应多个value. key 是从领域事件中的接口类型中查找领域实体类型
ParameterizedType parameterizedType = (ParameterizedType) o.getClass().getGenericInterfaces()[0];
Type type = parameterizedType.getActualTypeArguments()[0];
try {
String name = type.getTypeName();
return Class.forName(name);
} catch (ClassNotFoundException e) {
log.error(e.getMessage(), e);
return null;
}
})).forEach((Class<?> topic, List<DomainEventHandler<?>> eventHandlers) -> {
// 创建 disruptor;并发事件消费 - 无顺序的执行事件消费
DisruptorCreate disruptorCreate = param.disruptorCreate;
Disruptor<EventDisruptor> disruptor = disruptorCreate.createDisruptor(topic, param);
DisruptorManager.me().put(topic, disruptor);
if (Objects.nonNull(param.exceptionHandler)) {
disruptor.setDefaultExceptionHandler(param.exceptionHandler);
}
// disruptor 绑定领域事件消费接口,事件消费绑定
eventHandlers.forEach(eventHandler -> {
// 事件消费绑定
var consumeEventHandler = new ConsumeEventHandler(eventHandler);
disruptor.handleEventsWith(consumeEventHandler);
});
});
// 启动disruptor
DisruptorManager.me().forEach(Disruptor::start);
domainEventHandlerSet.clear();
return init.get();
}
/**
* 停止Disruptor
* <pre>
* 把环形数组中的事件执行完后停止, 不在接受新的事件
* </pre>
*
* @return true 停止成功
*/
public boolean stop() {
DisruptorManager.me().listDisruptor().removeIf(disruptor -> {
disruptor.shutdown();
return true;
});
return true;
}
}
package cn.eweb.frame.event;
import cn.eweb.frame.event.disruptor.DefaultDisruptorCreate;
import cn.eweb.frame.event.disruptor.DisruptorCreate;
import cn.eweb.frame.event.exception.DefaultDomainEventExceptionHandler;
import cn.eweb.frame.event.message.DomainEventHandler;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.ProducerType;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@Getter
@Setter
@Accessors(chain = true)
public class DomainEventContextParam {
/** 领域事件消费 */
final Set<DomainEventHandler<?>> domainEventHandlerSet = new HashSet<>();
/**
* 等待策略
* <pre>
*
* 措施 适用场景 名称
*
* 加锁 CPU资源紧缺,吞吐量和延迟并不重要的场景 {@link BlockingWaitStrategy}
* 自旋 通过不断重试,减少切换线程导致的系统调用,而降低延迟。推荐在线程绑定到固定的CPU的场景下使用 {@link BusySpinWaitStrategy}
* 自旋 + yield + 自定义策略 CPU资源紧缺,吞吐量和延迟并不重要的场景 {@link PhasedBackoffWaitStrategy}
* 自旋 + yield + sleep 性能和CPU资源之间有很好的折中。延迟不均匀 {@link SleepingWaitStrategy}
* 加锁,有超时限制 CPU资源紧缺,吞吐量和延迟并不重要的场景 {@link TimeoutBlockingWaitStrategy}
* 自旋 + yield + 自旋 性能和CPU资源之间有很好的折中。延迟比较均匀 {@link YieldingWaitStrategy}
*
*
* </pre>
*/
WaitStrategy waitStrategy = new LiteBlockingWaitStrategy();
ProducerType producerType = ProducerType.MULTI;
int ringBufferSize = 1024;
/** 异常处理 */
ExceptionHandler exceptionHandler = new DefaultDomainEventExceptionHandler();
/** true 初始化完成 */
private final AtomicBoolean init = new AtomicBoolean(false);
/** 创建 disruptor */
DisruptorCreate disruptorCreate = new DefaultDisruptorCreate();
/**
* 添加领域事件消费者,主题默认使用接口实现类的T类型
*
* @param domainEventHandler 领域事件消费者
*/
public DomainEventContextParam addEventHandler(DomainEventHandler<?> domainEventHandler) {
if (Objects.nonNull(domainEventHandler)) {
domainEventHandlerSet.add(domainEventHandler);
}
return this;
}
}
package cn.eweb.frame.event;
import cn.eweb.frame.event.disruptor.DomainEventSource;
import cn.eweb.frame.event.disruptor.EventDisruptor;
import cn.eweb.frame.event.message.Topic;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import lombok.experimental.UtilityClass;
import java.util.Objects;
/**
* 事件发布器
*
*/
@UtilityClass
public class DomainEventPublish {
/**
* 发送领域事件,这个方法是不会提供返回值的
*
* @param domainSource 领域消息
*/
public void send(final DomainEventSource domainSource) {
publishDomainEvent(domainSource, domainSource.getTopic(), true);
}
/**
* 普通对象
* <pre>
* 发送领域事件
* </pre>
*
* @param domainSource 领域消息
*/
public void send(final Object domainSource) {
if (domainSource instanceof DomainEventSource domainEventSource) {
send(domainEventSource);
} else {
// 获取主题
Class<?> topic = domainSource instanceof Topic ? ((Topic) domainSource).getTopic() : domainSource.getClass();
publishDomainEvent(domainSource, topic, false);
}
}
private void publishDomainEvent(final Object domainSource, Class<?> topic, boolean eventSource) {
// 查找 DomainEvent 对应的 disruptor, 通过事件主题(类信息)获取事件处理 ringBuffers
final Disruptor<EventDisruptor> disruptor = DisruptorManager.me().getDisruptor(topic);
if (Objects.isNull(disruptor)) {
throw new NullPointerException("没有配置处理 : " + topic + " 的领域事件. 请配置");
}
// 环形数组
final RingBuffer<EventDisruptor> ringBuffer = disruptor.getRingBuffer();
final long sequence = ringBuffer.next();
try {
/*
* 用上面sequence的索引取出一个空的事件用于填充,可以重复使用是因为得益于环形数组
* 这样避免了重复创建对象的消耗
*/
final EventDisruptor eventDisruptor = ringBuffer.get(sequence);
// 设置领域事件
if (eventSource) {
eventDisruptor.setDomainEventSource((DomainEventSource) domainSource);
} else {
eventDisruptor.setValue(domainSource);
}
} finally {
//发布事件
ringBuffer.publish(sequence);
}
}
}
package cn.eweb.frame.event.annotation;
/**
* 领域事件注解
* <pre>
* 用于领域事件注册扫描 (需要自己实现, 这里只是一个标记)
* 如果没用到扫描的方式, 可以忽略
* </pre>
*
*/
public @interface DomainEvent {
}
package cn.eweb.frame.event.disruptor;
import cn.eweb.frame.event.message.DomainEventHandler;
import com.lmax.disruptor.EventHandler;
public record ConsumeEventHandler(DomainEventHandler<?> eventHandler) implements EventHandler<EventDisruptor> {
@Override
public void onEvent(EventDisruptor event, long sequence, boolean endOfBatch) {
if (event.isEventSource()) {
eventHandler.onEvent(event.getDomainEventSource(), sequence, endOfBatch);
} else {
eventHandler.onEvent(event.getValue(), sequence, endOfBatch);
}
}
}
package cn.eweb.frame.event.disruptor;
import cn.eweb.frame.event.DisruptorManager;
import cn.eweb.frame.event.DomainEventContextParam;
import com.lmax.disruptor.BatchEventProcessor;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 默认的 领域事件构建接口 实现类
*
*/
@Slf4j
public class DefaultDisruptorCreate implements DisruptorCreate {
static final AtomicInteger THREAD_INIT_NUMBER = new AtomicInteger(1);
@Override
public Disruptor<EventDisruptor> createDisruptor(Class<?> topic, DomainEventContextParam param) {
int ringBufferSize = param.getRingBufferSize();
ProducerType producerType = param.getProducerType();
WaitStrategy waitStrategy = param.getWaitStrategy();
// 自定义线程工厂
ThreadFactory threadFactory = createThreadFactory(topic);
return new Disruptor<>(EventDisruptor::new, ringBufferSize, threadFactory, producerType, waitStrategy);
}
private ThreadFactory createThreadFactory(Class<?> topic) {
return r -> {
String domainEventHandlerName = getName(r);
List<String> nameParamList = new ArrayList<>();
// 线程名前缀
nameParamList.add(DisruptorManager.me().getThreadNamePrefix());
// 主题名
nameParamList.add(topic.getSimpleName());
// 领域事件名
nameParamList.add(domainEventHandlerName);
// 编号
nameParamList.add(String.valueOf(THREAD_INIT_NUMBER.getAndIncrement()));
// 组合成线程名
String threadName = String.join("-", nameParamList);
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName(threadName);
return thread;
};
}
private String getName(Runnable r) {
String domainEventHandlerName = "";
if (r instanceof BatchEventProcessor eventProcessor) {
try {
Field eventHandler = BatchEventProcessor.class.getDeclaredField("eventHandler");
eventHandler.setAccessible(true);
Object o = eventHandler.get(eventProcessor);
if (o instanceof ConsumeEventHandler consumeEventHandler) {
domainEventHandlerName = consumeEventHandler.eventHandler().getName();
}
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error(e.getMessage(), e);
}
return domainEventHandlerName;
}
return domainEventHandlerName;
}
}
\ No newline at end of file
package cn.eweb.frame.event.disruptor;
import cn.eweb.frame.event.DomainEventContextParam;
import com.lmax.disruptor.dsl.Disruptor;
/**
* 领域事件构建接口
* <pre>
* 创建disruptor
* </pre>
*
*/
public interface DisruptorCreate {
/**
* 根据topic(领域消息主题)创建disruptor
*
* @param topic 主题
* @param param param
* @return Disruptor
*/
Disruptor<EventDisruptor> createDisruptor(Class<?> topic, DomainEventContextParam param);
}
package cn.eweb.frame.event.disruptor;
import cn.eweb.frame.event.message.Topic;
/**
* 领域事件接口 - 源事件源
*/
public interface DomainEventSource extends Topic {
/**
* 获取领域事件主题
*
* @return 领域事件主题
*/
@Override
default Class<?> getTopic() {
return this.getClass();
}
/**
* 获取事件源
*
* @param <T> source
* @return 事件源
*/
@SuppressWarnings("unchecked")
default <T> T getSource() {
return (T) this;
}
}
package cn.eweb.frame.event.disruptor;
import com.lmax.disruptor.RingBuffer;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
/**
* 事件订阅发送 {@link EventDisruptor} 给 {@link RingBuffer}
*
* @author 渔民小镇
* @date 2021-12-26
*/
@FieldDefaults(level = AccessLevel.PRIVATE)
public final class EventDisruptor {
/**
* 没有实现 {@link DomainEventSource} 接口的对象
*/
Object value;
/**
* 是否包装的领域事件
* <pre>
* true 实现了 {@link DomainEventSource} 接口的对象
* false 没有实现 {@link DomainEventSource} 接口的对象
* </pre>
*/
@Setter
@Getter
boolean eventSource = true;
/**
* 领域事件
*/
DomainEventSource domainEventSource;
/**
* 获取领域事件源对象
*
* @param <T> source
* @return 事件源对象
*/
@SuppressWarnings("unchecked")
public <T> T getDomainEventSource() {
return (T) domainEventSource;
}
/**
* 设置领域事件源
*
* @param domainEventSource 领域事件源
*/
public void setDomainEventSource(DomainEventSource domainEventSource) {
this.eventSource = true;
this.domainEventSource = domainEventSource;
}
/**
* 没有实现 {@link DomainEventSource} 接口的对象
*
* @param <T> T
* @return value
*/
@SuppressWarnings("unchecked")
public <T> T getValue() {
return (T) value;
}
/**
* 没有实现 {@link DomainEventSource} 接口的对象
*
* @param value value
*/
public void setValue(Object value) {
this.eventSource = false;
this.value = value;
}
}
package cn.eweb.frame.event.exception;
import com.lmax.disruptor.ExceptionHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 默认领域事件 异常处理类
*
* @author 渔民小镇
* @date 2022-01-14
*/
@Slf4j
public class DefaultDomainEventExceptionHandler implements ExceptionHandler {
@Override
public void handleEventException(Throwable ex, long sequence, Object event) {
log.error("{} - {}", ex, event);
}
@Override
public void handleOnStartException(Throwable ex) {
log.error(ex.getMessage(), ex);
}
@Override
public void handleOnShutdownException(Throwable ex) {
log.error(ex.getMessage(), ex);
}
}
package cn.eweb.frame.event.message;
/**
* 领域事件消费接口, 接收一个领域事件
*
* @param <T> T 领域实体
* @author 渔民小镇
* @date 2021-12-26
*/
@FunctionalInterface
public interface DomainEventHandler<T> {
/**
* 事件处理
*
* @param event 领域实体
* @param endOfBatch endOfBatch
*/
void onEvent(final T event, final boolean endOfBatch);
/**
* 事件处理
*
* @param event 领域实体
* @param sequence sequence
* @param endOfBatch endOfBatch
*/
default void onEvent(final T event, final long sequence, final boolean endOfBatch) {
this.onEvent(event, endOfBatch);
}
/**
* 获取领域事件名
*
* @return 领域事件名
*/
default String getName() {
return this.getClass().getSimpleName();
}
}
package cn.eweb.frame.event.message;
import cn.eweb.frame.event.DomainEventPublish;
import cn.eweb.frame.event.disruptor.DomainEventSource;
/**
* 领域事件的业务接口 (Event Object)
* <pre>
* 通常是业务数据载体实现的接口
* 实现该接口后,会得到领域事件发送的能力
* </pre>
*
*/
public interface Eo extends DomainEventSource {
/**
* 领域事件发送
*/
default void send() {
DomainEventPublish.send(this);
}
}
package cn.eweb.frame.event.message;
/**
* 主题
* <pre>
* 领域消息主题
* </pre>
*
* @author 渔民小镇
* @date 2021-12-26
*/
public interface Topic {
/**
* 获取主题
*
* @return 主题
*/
Class<?> getTopic();
}
\ No newline at end of file
/**
* 用户关注的接口
*
* @author 渔民小镇
* @date 2021-12-26
*/
package cn.eweb.frame.event.message;
\ No newline at end of file
package cn.eweb.frame.event;
/**
* 高性能的异步无阻塞领域驱动,可使代码更好的维护、可扩展。 <BR>
*
*/
\ No newline at end of file
package cn.eweb.frame.event.test;
import cn.eweb.frame.event.DomainEventContext;
import cn.eweb.frame.event.DomainEventContextParam;
import cn.eweb.frame.event.test.student.StudentEmailEventHandler1;
import cn.eweb.frame.event.test.student.StudentEo;
import cn.eweb.frame.event.test.student.StudentGoHomeEventHandler2;
import cn.eweb.frame.event.test.student.StudentSleepEventHandler3;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* <pre>
* 领域事件的启动与停止,整个系统的生命周期只需要做一次.
* 例如在你的应用中启动, 那么启动领取事件 DomainEventHandlerConfig.start();
* 应用关闭时可以调用:DomainEventHandlerConfig.stop()
* </pre>
*
* @author 渔民小镇
* @date 2021-12-26
*/
public class StudentDomainEventTest {
DomainEventContext domainEventContext;
@After
public void tearDown() throws Exception {
// 事件消费完后 - 事件停止
domainEventContext.stop();
}
@Before
public void setUp() {
// ======项目启动时配置一次(初始化)======
// 领域事件上下文参数
DomainEventContextParam contextParam = new DomainEventContextParam();
// 配置一个学生的领域事件消费 - 给学生发生一封邮件
contextParam.addEventHandler(new StudentEmailEventHandler1());
// 配置一个学生的领域事件消费 - 回家
contextParam.addEventHandler(new StudentGoHomeEventHandler2());
// 配置一个学生的领域事件消费 - 让学生睡觉
contextParam.addEventHandler(new StudentSleepEventHandler3());
// 启动事件驱动
domainEventContext = new DomainEventContext(contextParam);
domainEventContext.startup();
}
@Test
public void testEventSend() {
// 这里开始就是你的业务代码
StudentEo studentEo = new StudentEo(1);
/*
* 发送事件、上面只配置了一个事件。
* 如果将来还需要给学生发送一封email,那么直接配置。(可扩展)
* 如果将来还需要记录学生今天上了什么课程,那么也是直接配置 (可扩展) 这里的业务代码无需任何改动(松耦合)
* 如果将来又不需要给学生发送email的事件了,直接删除配置即可,这里还是无需改动代码。(高伸缩)
*/
studentEo.send();
}
}
/*
* ioGame
* Copyright (C) 2021 - 2023 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
* # iohao.com . 渔民小镇
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package cn.eweb.frame.event.test;
import cn.eweb.frame.event.DomainEventContext;
import cn.eweb.frame.event.DomainEventContextParam;
import cn.eweb.frame.event.test.student.StudentCountEventHandler;
import cn.eweb.frame.event.test.student.StudentEo;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
/**
* @author 渔民小镇
* @date 2022-09-02
*/
@Slf4j
public class StudentDomainEventTest2 {
DomainEventContext domainEventContext;
@After
public void tearDown() throws Exception {
// 事件消费完后 - 事件停止
domainEventContext.stop();
}
@Before
public void setUp() {
// ======项目启动时配置一次(初始化)======
// 领域事件上下文参数
DomainEventContextParam contextParam = new DomainEventContextParam();
contextParam.addEventHandler(new StudentCountEventHandler());
// 启动事件驱动
domainEventContext = new DomainEventContext(contextParam);
domainEventContext.startup();
}
@Test
public void testEventSendMulti2() throws InterruptedException {
StudentEo studentEo = new StudentEo(1);
ThreadUtil.concurrencyTest(100, () -> {
for (int j = 0; j < 20_000; j++) {
studentEo.send();
}
});
log.info("start");
/*
* 需要等待一下;
* 如果不等待,但在测试用例中又执行 domainEventContext.stop(); 方法
* disruptor 将不会接收事件了
*/
log.info("StudentCountEventHandler.longAdder : {}", StudentCountEventHandler.longAdder);
TimeUnit.SECONDS.sleep(1);
log.info("======== longAdder ========: {}", StudentCountEventHandler.longAdder);
}
@Test
public void testEventSendSingle() throws InterruptedException {
StudentEo studentEo = new StudentEo(1);
for (int i = 0; i < 2_000_000; i++) {
studentEo.send();
}
log.info("start");
TimeUnit.SECONDS.sleep(2);
log.info("StudentCountEventHandler.longAdder : {}", StudentCountEventHandler.longAdder);
}
}
package cn.eweb.frame.event.test;
import cn.eweb.frame.event.DomainEventContext;
import cn.eweb.frame.event.DomainEventContextParam;
import cn.eweb.frame.event.DomainEventPublish;
import cn.eweb.frame.event.test.user.UserLogin;
import cn.eweb.frame.event.test.user.UserLoginEmailEventHandler;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author 渔民小镇
* @date 2021-12-26
*/
public class UserLoginDomainEventTest {
DomainEventContext domainEventContext;
@After
public void tearDown() throws Exception {
// 事件消费完后 - 事件停止
domainEventContext.stop();
}
@Before
public void setUp() {
// ======项目启动时配置一次(初始化)======
// 领域事件上下文参数
DomainEventContextParam contextParam = new DomainEventContextParam();
// 用户登录就发送 email
contextParam.addEventHandler(new UserLoginEmailEventHandler());
// 启动事件驱动
domainEventContext = new DomainEventContext(contextParam);
domainEventContext.startup();
}
@Test
public void testEventSend() {
// 这里开始就是你的业务代码
UserLogin userLogin = new UserLogin(101, "塔姆");
/*
* 发送事件、上面只配置了一个事件。
* 如果将来还需要给用户登录 记录登录日志,那么直接配置。(可扩展)
* 如果将来还需要记录用户登录 今天上了什么课程,那么也是直接配置 (可扩展) 这里的业务代码无需任何改动(松耦合)
* 如果将来又不需要给用户登录发送email的事件了,直接删除配置即可,这里还是无需改动代码。(高伸缩)
*/
DomainEventPublish.send(userLogin);
}
}
package cn.eweb.frame.event.test.student;
import cn.eweb.frame.event.message.DomainEventHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StudentCountEventHandler implements DomainEventHandler<StudentEo> {
public static long longAdder = 0;
@Override
public void onEvent(StudentEo event, boolean endOfBatch) {
longAdder++;
if (longAdder > 1_999_998) {
log.info("longAdder : {}", longAdder);
}
}
}
package cn.eweb.frame.event.test.student;
import cn.eweb.frame.event.annotation.DomainEvent;
import cn.eweb.frame.event.message.DomainEventHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 给学生发送email事件
*/
@Slf4j
@DomainEvent
public final class StudentEmailEventHandler1 implements DomainEventHandler<StudentEo> {
@Override
public void onEvent(StudentEo studentEo, boolean endOfBatch) {
log.debug("给这个学生发送一个email消息: {}", studentEo);
}
}
package cn.eweb.frame.event.test.student;
import cn.eweb.frame.event.message.Eo;
/**
* 领域消息 - 学生
* <pre>
* 推荐定义领域事件实体类的时候都使用final
* 避免某个领域事件对该实体进行数据修改
* </pre>
*
* @author 渔民小镇
* @date 2021-12-26
*/
public record StudentEo(int id) implements Eo {
}
package cn.eweb.frame.event.test.student;
import cn.eweb.frame.event.annotation.DomainEvent;
import cn.eweb.frame.event.message.DomainEventHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 学生回家事件
*
* @author 渔民小镇
* @date 2021-12-26
*/
@Slf4j
@DomainEvent
public final class StudentGoHomeEventHandler2 implements DomainEventHandler<StudentEo> {
@Override
public void onEvent(StudentEo studentEo, boolean endOfBatch) {
log.debug("学生回家: {} , {}", studentEo, endOfBatch);
}
}
package cn.eweb.frame.event.test.student;
import cn.eweb.frame.event.annotation.DomainEvent;
import cn.eweb.frame.event.message.DomainEventHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 学生睡觉
*
* @author 渔民小镇
* @date 2021-12-26
*/
@Slf4j
@DomainEvent
public final class StudentSleepEventHandler3 implements DomainEventHandler<StudentEo> {
@Override
public void onEvent(StudentEo studentEo, boolean endOfBatch) {
log.debug("学生睡觉: {}", studentEo);
}
}
/**
* 自定义示例
*
* @author 渔民小镇
* @date 2021-12-26
*/
package cn.eweb.frame.event.test.student;
\ No newline at end of file
package cn.eweb.frame.event.test.user;
/**
* 用户登录
* <pre>
* 这个实体是模拟第三方库的实体
* 实际中我们是不能修改的
* </pre>
*
* @author 渔民小镇
* @date 2021-12-26
*/
public record UserLogin(int id, String name) {
}
package cn.eweb.frame.event.test.user;
import cn.eweb.frame.event.message.DomainEventHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 用户登录就发送 email
*
* @author 渔民小镇
* @date 2021-12-26
*/
@Slf4j
public class UserLoginEmailEventHandler implements DomainEventHandler<UserLogin> {
@Override
public void onEvent(UserLogin userLogin, boolean endOfBatch) {
log.info("给登录用户发送 email ! {}", userLogin);
}
}
/**
* 为第三方库 增加领域事件能力
*
* @author 渔民小镇
* @date 2021-12-26
*/
package cn.eweb.frame.event.test.user;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
Metadata DTD 1.1
-->
<!--
主节点
-->
<!ELEMENT metadata-config (entity)+>
<!--
default-parent - 全局实体的默认继承
schema-name-optimize - 是否启用 schema 名称优化(实体 ReferencesMapping 优化为 references_mapping,字段 uniqueId 优化为 UNIQUE_ID)
-->
<!ATTLIST metadata-config
default-parent CDATA #IMPLIED
schema-name-optimize (true | false | 1 | 0) "false"
>
<!--
实体定义
-->
<!ELEMENT entity (field+, index*)>
<!--
实体定义的属性列表
name - 实体名称
physical-name - 物理名称
type-code - 实体代号,3位数字
name-field - 名称字段
parent - 父实体
description - 描述
schema-name-optimize - 是否启用schema优化
main - 所属主实体
extra-attrs - 扩展属性(JSON格式)
creatable - 可创建
updatable - 可更新
queryable - 可查询
deletable - 可删除
-->
<!ATTLIST entity
name CDATA #REQUIRED
physical-name CDATA #IMPLIED
type-code CDATA #REQUIRED
name-field CDATA #IMPLIED
parent CDATA #IMPLIED
description CDATA #IMPLIED
schema-name-optimize (true | false | 1 | 0) "false"
main CDATA #IMPLIED
extra-attrs CDATA #IMPLIED
creatable (true | false | 1 | 0) "true"
updatable (true | false | 1 | 0) "true"
queryable (true | false | 1 | 0) "true"
deletable (true | false | 1 | 0) "true"
>
<!--
字段定义
-->
<!ELEMENT field EMPTY>
<!--
字段定义的属性列表
name - 字段名称
type - 字段类型
physical-name - 物理名称
ref-entity - 引用实体列表
max-length - 最大长度,只支持某些字段
cascade - 级联模式,只支持删除级联
auto-value - 是否含有自动值(如在MySQL中可能存在如自动增长或当前时间),如为true,则系统会忽略填充此值
description - 描述
decimal-scale - 小数位精度
default-value - 默认值,由数据库完成
extra-attrs - 扩展属性(JSON格式)
creatable - 可创建
updatable - 可更新
queryable - 可查询
nullable - 可为空
repeatable - 可重复
-->
<!ATTLIST field
name CDATA #REQUIRED
type (primary | reference | any-reference | reference-list | char | string | text | ntext
| int | small-int | double | decimal | long | date | timestamp | time | bool | binary) #REQUIRED
physical-name CDATA #IMPLIED
ref-entity CDATA #IMPLIED
max-length CDATA #IMPLIED
cascade (delete | remove-links | ignore) "remove-links"
auto-value (true | false | 1 | 0) "false"
description CDATA #IMPLIED
decimal-scale (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8) "4"
default-value CDATA #IMPLIED
extra-attrs CDATA #IMPLIED
creatable (true | false | 1 | 0) "true"
updatable (true | false | 1 | 0) "true"
queryable (true | false | 1 | 0) "true"
nullable (true | false | 1 | 0) "true"
repeatable (true | false | 1 | 0) "true"
>
<!--
索引定义(只为生成 SCHEMA 所用)
-->
<!ELEMENT index EMPTY>
<!--
字段定义的属性列表
type - 索引类型
field-list - 字段列表
-->
<!ATTLIST index
type (key | unique | fulltext) "key"
field-list CDATA #REQUIRED
>
\ No newline at end of file
package cn.eweb.frame.dynamic.orm;
import cn.eweb.frame.common.utils.xml.XmlHelper;
import cn.hutool.json.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.Element;
import org.junit.Test;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Slf4j
public class XmlTest {
@Test
public void ReadXml(){
URL url = getClass().getClassLoader().getResource("metadata-test.xml");
List<String> errors = new ArrayList<>();
Document document;
XmlHelper XML_HELPER = new XmlHelper();
try {
document = XML_HELPER.createSAXReader("",
errors, XmlHelper.DEFAULT_DTD_RESOLVER).read(Objects.requireNonNull(url).openStream());
Element root = document.getRootElement();
log.info("rootElement name:{}",root.getName());
List<Element> elementList = root.elements();
log.info("elements 数量:{}",elementList.size());
for (Element item : elementList) {
System.out.println("实体标签的子标签的名字是"+ item.getName());
List<Element> fields = item.elements();
for(Element field : fields){
log.info("name:{}",field.getName());
log.info("type:{}",field.attribute("type"));
}
}
} catch (Exception e) {
}
}
@Test
public void getJSON(){
JSONArray jsonArray = new JSONArray();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE metadata-config SYSTEM "metadata.dtd">
<metadata-config schema-name-optimize="true" default-parent="SystemCommon">
<entity name="SystemCommon" type-code="000">
<field name="commonReference" type="reference" ref-entity="TestAllType" description="引用"/>
</entity>
<entity name="TestAllType" type-code="100">
<field name="tPrimary" type="primary" description="主键" />
<field name="tReference" type="reference" ref-entity="TestAllType" description="引用"/>
<field name="tInt" type="int"/>
<field name="tSmallInt" type="small-int"/>
<field name="tDouble" type="double" decimal-scale="6"/>
<field name="tDecimal" type="decimal" decimal-scale="7" />
<field name="tLong" type="long" auto-value="true"/>
<field name="tDate" type="date" description="日期"/>
<field name="tTimestamp" type="timestamp" description="日期时间" />
<field name="tTime" type="time" description="时间" />
<field name="tChar" type="char" description="字符"/>
<field name="tString" type="string" description="文版"/>
<field name="tText" type="text" description="长文本"/>
<field name="tBool" type="bool" description="布尔"/>
<field name="tNtext" type="ntext" description="超大文本"/>
<field name="tBinary" type="binary" description="二进制数据"/>
<field name="tAnyReference" type="any-reference" ref-entity="*" description="任意引用"/>
<field name="tAnyReference2" type="any-reference" ref-entity="Test2,Test3" description="任意引用"/>
<field name="tReferenceList" type="reference-list" ref-entity="TestAllType" description="多引用"/>
<index type="fulltext" field-list="tText,tNtext" />
<index field-list="tReference" />
<index field-list="tAnyReference" type="unique" />
</entity>
<entity name="Test2" type-code="102">
<field name="t2Primary" type="primary" description="主键" />
<field name="t2Reference" type="reference" ref-entity="TestAllType" description="引用"/>
<field name="t2Int" type="int"/>
</entity>
<entity name="Test3" type-code="103">
<field name="t3Primary" type="primary" description="主键" />
<field name="t1Reference" type="reference" ref-entity="TestAllType" description="引用"/>
<field name="t2Reference" type="reference" ref-entity="Test2" description="引用"/>
<field name="sum" type="double"/>
<field name="count" type="double"/>
</entity>
<entity name="Test4" type-code="104">
<field name="t4Primary" type="primary" />
<field name="year" type="string"/>
<field name="quarter" type="string" />
<field name="month" type="string"/>
<field name="week" type="string" />
<field name="count" type="string"/>
</entity>
</metadata-config>
\ No newline at end of file
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>eweb-dynamic-orm</artifactId>
<groupId>cn.eweb.frame</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dynamic-orm-spring-boot-starter</artifactId>
<properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
\ No newline at end of file
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-spring-boot</artifactId> <artifactId>eweb-spring-boot</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<artifactId>eweb-spring-framework</artifactId> <artifactId>eweb-spring-framework</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
......
...@@ -3,33 +3,58 @@ ...@@ -3,33 +3,58 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>eweb-dynamic-orm</artifactId> <artifactId>eweb-sqltoy</artifactId>
<groupId>cn.eweb.frame</groupId> <groupId>cn.eweb.frame</groupId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>dynamic-orm-core</artifactId> <artifactId>eweb-sqltoy-orm</artifactId>
<properties> <properties>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:https://github.com/sagframe/sagacity-sqltoy.git</connection>
<url>https://github.com/sagframe/sagacity-sqltoy</url>
</scm>
<developers>
<developer>
<name>chenrenfei</name>
<id>zhongxuchen</id>
<email>zhongxuchen@gmail.com</email>
<roles>
<role>Developer</role>
</roles>
<timezone>+8</timezone>
</developer>
<developer>
<name>joliny</name>
<id>joliny</id>
<email>jbakwd@163.com</email>
<roles>
<role>Developer</role>
</roles>
<timezone>+8</timezone>
</developer>
</developers>
<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<dependencies> <dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
...@@ -54,9 +79,38 @@ ...@@ -54,9 +79,38 @@
<scope>test</scope> <scope>test</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!--
https://mvnrepository.com/artifact/com.taosdata.jdbc/taos-jdbcdriver -->
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>cn.eweb.frame</groupId> <groupId>com.oracle.database.jdbc</groupId>
<artifactId>eweb-common-utils</artifactId> <artifactId>ojdbc8</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
...@@ -66,7 +120,7 @@ ...@@ -66,7 +120,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId> <artifactId>spring-context</artifactId>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
...@@ -77,40 +131,89 @@ ...@@ -77,40 +131,89 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>org.springframework</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>spring-context-support</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>h2</artifactId> <artifactId>httpmime</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>druid</artifactId> <artifactId>httpcore</artifactId>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>com.alibaba</groupId>
<artifactId>junit</artifactId> <artifactId>fastjson</artifactId>
<scope>test</scope> <scope>provided</scope>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>antlr</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>antlr</artifactId> <artifactId>elasticsearch-rest-client</artifactId>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jaxen</groupId> <groupId>org.slf4j</groupId>
<artifactId>jaxen</artifactId> <artifactId>slf4j-api</artifactId>
</dependency>
<!-- 20220829 增加对h2数据库的驱动 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<resources> <resources>
<resource> <resource>
...@@ -150,8 +253,8 @@ ...@@ -150,8 +253,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version> <version>3.10.1</version>
<configuration> <configuration>
<source>${maven.compiler.source}</source> <source>1.8</source>
<target>${maven.compiler.target}</target> <target>1.8</target>
</configuration> </configuration>
</plugin> </plugin>
......
package org.sagacity.sqltoy;
import static java.lang.System.out;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sagacity.sqltoy.config.model.SqlExecuteLog;
import org.sagacity.sqltoy.config.model.SqlExecuteTrace;
import org.sagacity.sqltoy.model.OverTimeSql;
import org.sagacity.sqltoy.plugins.OverTimeSqlHandler;
import org.sagacity.sqltoy.plugins.formater.SqlFormater;
import org.sagacity.sqltoy.utils.DateUtil;
import org.sagacity.sqltoy.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* @project sagacity-sqltoy
* @description 提供sql执行超时统计和基本的sql输出功能
* @author zhongxuchen
* @version v1.0,Date:2015年6月12日
* @modify {Date:2020-06-15,改进sql日志输出,将条件参数带入到sql中输出,便于开发调试}
* @modify {Date:2020-08-12,为日志输出增加统一uid,便于辨别同一组执行语句}
*/
public class SqlExecuteStat {
/**
* 定义日志
*/
private final static Logger logger = LoggerFactory.getLogger(SqlExecuteStat.class);
/**
* 是否调试阶段
*/
private static boolean debug = false;
/**
* 打印慢sql(单位毫秒,默认超过18秒)
*/
private static int printSqlTimeoutMillis = 18000;
// 用于拟合sql中的条件值表达式(前后都以非字符和数字为依据目的是最大幅度的避免参数值里面存在问号,实际执行过程中这个问题已经被规避,但调试打印参数带入无法规避)
private final static Pattern ARG_PATTERN = Pattern.compile("\\W\\?\\W");
// 通过ThreadLocal 来保存线程数据
private static ThreadLocal<SqlExecuteTrace> threadLocal = new TransmittableThreadLocal<SqlExecuteTrace>();
// sql执行超时处理器
public static OverTimeSqlHandler overTimeSqlHandler;
/**
* sql格式化输出器(用于debug sql输出)
*/
private static SqlFormater sqlFormater;
/**
* @todo 登记开始执行
* @param sqlId
* @param type
* @param debugPrint
*/
public static void start(String sqlId, String type, Boolean debugPrint) {
threadLocal.set(new SqlExecuteTrace(sqlId, type, (debugPrint == null) ? debug : debugPrint.booleanValue()));
}
/**
* @todo 向线程中登记发生了异常,便于在finally里面明确是错误并打印相关sql
* @param exception
*/
public static void error(Exception exception) {
if (threadLocal.get() != null) {
threadLocal.get().setError(exception.getMessage());
}
}
/**
* @todo 在debug模式下,在console端输出sql,便于开发人员查看
* @param topic
* @param sql
* @param paramValues
*/
public static void showSql(String topic, String sql, Object[] paramValues) {
try {
// debug模式直接输出
if (threadLocal.get() != null) {
threadLocal.get().addSqlLog(topic, sql, paramValues);
}
} catch (Exception e) {
}
}
public static void setDialect(String dialect) {
if (threadLocal.get() != null) {
threadLocal.get().setDialect(dialect);
}
}
/**
* @TODO 提供中间日志输出
* @param topic
* @param message
* @param args
*/
public static void debug(String topic, String message, Object... args) {
try {
if (threadLocal.get() != null) {
threadLocal.get().addLog(topic, message, args);
}
} catch (Exception e) {
}
}
/**
* 在执行结尾时记录日志
*/
private static void destroyLog() {
try {
SqlExecuteTrace sqlTrace = threadLocal.get();
if (sqlTrace == null) {
return;
}
long runTime = sqlTrace.getExecuteTime();
long overTime = runTime - printSqlTimeoutMillis;
// sql执行超过阀值记录日志为软件优化提供依据
if (overTime >= 0) {
sqlTrace.setOverTime(true);
sqlTrace.addLog("slowSql执行超时", "耗时(毫秒):{} >={} (阀值)!", runTime, printSqlTimeoutMillis);
} else {
sqlTrace.addLog("执行总时长", "耗时:{} 毫秒 !", runTime);
}
// 日志输出
printLogs(sqlTrace);
} catch (Exception e) {
}
}
/**
* @TODO 输出日志
* @param sqlTrace
*/
private static void printLogs(SqlExecuteTrace sqlTrace) {
boolean errorLog = false;
String reportStatus = "成功!";
if (sqlTrace.isOverTime()) {
errorLog = true;
reportStatus = "执行耗时超阀值!";
}
if (sqlTrace.isError()) {
errorLog = true;
reportStatus = "发生异常错误!";
}
if (!errorLog) {
// sql中已经标记了#not_debug# 表示无需输出日志(一般针对缓存检测、缓存加载等,避免这些影响业务日志)
if (!sqlTrace.isPrint()) {
return;
}
}
String uid = sqlTrace.getUid();
StringBuilder result = new StringBuilder();
String optType = sqlTrace.getType();
String codeTrace = getFirstTrace();
result.append("\n/*|----------------------开始执行报告输出 --------------------------------------------------*/");
result.append("\n/*|任 务 ID: " + uid);
result.append("\n/*|执行结果: " + reportStatus);
result.append("\n/*|数据方言: " + sqlTrace.getDialect());
result.append("\n/*|执行类型: " + optType);
result.append("\n/*|代码定位: " + codeTrace);
if (sqlTrace.getId() != null) {
result.append("\n/*|对应sqlId: " + sqlTrace.getId());
}
List<SqlExecuteLog> executeLogs = sqlTrace.getExecuteLogs();
int step = 0;
int logType;
String topic;
String content;
Object[] args = null;
String sql = null;
for (SqlExecuteLog log : executeLogs) {
step++;
logType = log.getType();
topic = log.getTopic();
content = log.getContent();
args = log.getArgs();
if (logType == 0) {
result.append("\n/*|---- 过程: " + step + "," + topic + "----------------");
// 区别一些批量写和更新操作,参数较多不便于输出
if (optType.startsWith("save") || optType.startsWith("deleteAll")
|| optType.startsWith("batchUpdate")) {
result.append("\n/*| 内部sql: ").append(fitSqlParams(content, args));
result.append("\n/*| save(All)|saveOrUpdate(All)|deleleAll|batchUpdate等不输出sql执行参数");
} else {
sql = fitSqlParams(content, args);
// 对sql格式化输出
if (sqlFormater != null) {
sql = sqlFormater.format(sql, sqlTrace.getDialect());
result.append("\n/*| 模拟入参后sql:\n").append(sql);
} else {
result.append("\n/*| 模拟入参后sql:").append(sql);
}
result.append("\n/*| sql参数: ");
if (args != null && args.length > 0) {
StringBuilder paramStr = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i > 0) {
paramStr.append(",");
}
paramStr.append("p[" + i + "]=" + args[i]);
}
result.append(paramStr);
} else {
result.append("无参数");
}
}
} else {
result.append("\n/*|---- 过程: " + step + "," + (null == topic ? "" : topic)
+ (null == content ? "" : ":" + StringUtil.fillArgs(content, args)));
}
}
result.append("\n/*|----------------------完成执行报告输出 --------------------------------------------------*/");
result.append("\n");
if (sqlTrace.isError()) {
logger.error(result.toString());
} else if (sqlTrace.isOverTime()) {
logger.warn(result.toString());
if (overTimeSqlHandler != null) {
overTimeSqlHandler.log(new OverTimeSql(sqlTrace.getId(), sql, sqlTrace.getExecuteTime(), codeTrace));
}
} else {
if (logger.isDebugEnabled()) {
logger.debug(result.toString());
} else {
out.println(result.toString());
}
}
}
/**
* 清理线程中的数据
*/
public static void destroy() {
// 执行完成时打印日志
destroyLog();
threadLocal.remove();
threadLocal.set(null);
}
/**
* @param debug the debug to set
*/
public static void setDebug(boolean debug) {
SqlExecuteStat.debug = debug;
}
public static void setOverTimeSqlHandler(OverTimeSqlHandler overTimeSqlHandler) {
SqlExecuteStat.overTimeSqlHandler = overTimeSqlHandler;
}
/**
* @param printSqlTimeoutMillis the printSqlTimeoutMillis to set
*/
public static void setPrintSqlTimeoutMillis(int printSqlTimeoutMillis) {
SqlExecuteStat.printSqlTimeoutMillis = printSqlTimeoutMillis;
}
public static void setSqlFormater(SqlFormater sqlFormater) {
SqlExecuteStat.sqlFormater = sqlFormater;
}
/**
* @TODO 将参数值拟合到sql中作为debug输出,便于开发进行调试(2020-06-15)
* @param sql
* @param params
* @return
*/
private static String fitSqlParams(String sql, Object[] params) {
if (sql == null || params == null || params.length == 0) {
return sql;
}
StringBuilder lastSql = new StringBuilder();
// 补空的目的在于迎合匹配规则
Matcher matcher = ARG_PATTERN.matcher(sql.concat(" "));
int start = 0;
int end;
int index = 0;
int paramSize = params.length;
Object paramValue;
// 逐个查找?用实际参数值进行替换
while (matcher.find(start)) {
end = matcher.start() + 1;
lastSql.append(sql.substring(start, end));
if (index < paramSize) {
paramValue = params[index];
// 字符
if (paramValue instanceof CharSequence) {
lastSql.append("'" + paramValue + "'");
} // update 2022-11-3 timestamp显示毫秒级别
else if (paramValue instanceof Timestamp) {
lastSql.append("'" + DateUtil.formatDate(paramValue, "yyyy-MM-dd HH:mm:ss.SSS") + "'");
} else if (paramValue instanceof Date || paramValue instanceof LocalDateTime) {
lastSql.append("'" + DateUtil.formatDate(paramValue, "yyyy-MM-dd HH:mm:ss") + "'");
} else if (paramValue instanceof LocalDate) {
lastSql.append("'" + DateUtil.formatDate(paramValue, "yyyy-MM-dd") + "'");
} else if (paramValue instanceof LocalTime) {
lastSql.append("'" + DateUtil.formatDate(paramValue, "HH:mm:ss") + "'");
} else if (paramValue instanceof Object[]) {
lastSql.append(combineArray((Object[]) paramValue));
} else if (paramValue instanceof Collection) {
lastSql.append(combineArray(((Collection) paramValue).toArray()));
} else {
lastSql.append("" + paramValue);
}
} else {
// 问号数量大于参数值数量,说明sql中存在写死的条件值里面存在问号,因此不再进行条件值拟合
return sql;
}
// 正则匹配最后是\\W,所以要-1
start = matcher.end() - 1;
index++;
}
lastSql.append(sql.substring(start));
return lastSql.toString();
}
/**
* @TODO 组合in参数
* @param array
* @return
*/
private static String combineArray(Object[] array) {
if (array == null || array.length == 0) {
return " null ";
}
StringBuilder result = new StringBuilder();
Object value;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
result.append(",");
}
value = array[i];
if (value instanceof CharSequence) {
result.append("'" + value + "'");
} else if (value instanceof Timestamp) {
result.append("'" + DateUtil.formatDate(value, "yyyy-MM-dd HH:mm:ss.SSS") + "'");
} else if (value instanceof Date || value instanceof LocalDateTime) {
result.append("'" + DateUtil.formatDate(value, "yyyy-MM-dd HH:mm:ss") + "'");
} else if (value instanceof LocalDate) {
result.append("'" + DateUtil.formatDate(value, "yyyy-MM-dd") + "'");
} else if (value instanceof LocalTime) {
result.append("'" + DateUtil.formatDate(value, "HH:mm:ss") + "'");
} else {
result.append("" + value);
}
}
return result.toString();
}
/**
* @TODO 定位第一个调用sqltoy的代码位置
* @return
*/
public static String getFirstTrace() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
String className = null;
int lineNumber = 0;
String method = null;
StackTraceElement traceElement;
int length = stackTraceElements.length;
// 逆序
for (int i = length - 1; i > 0; i--) {
traceElement = stackTraceElements[i];
className = traceElement.getClassName();
// 进入调用sqltoy的代码,此时取上一个
if (className.startsWith("org.sagacity.sqltoy")) {
method = traceElement.getMethodName();
lineNumber = traceElement.getLineNumber();
// 避免异常发生
if (i + 1 < length) {
traceElement = stackTraceElements[i + 1];
className = traceElement.getClassName();
method = traceElement.getMethodName();
lineNumber = traceElement.getLineNumber();
}
break;
}
}
return "" + className + "." + method + "[代码第:" + lineNumber + " 行]";
}
/**
* @TODO 获取执行总时长
* @return
*/
public static Long getExecuteTime() {
SqlExecuteTrace sqlTrace = threadLocal.get();
if (sqlTrace != null) {
return sqlTrace.getExecuteTime();
}
return -1L;
}
public static SqlExecuteTrace get() {
return threadLocal.get();
}
public static void set(SqlExecuteTrace sqlTrace) {
threadLocal.set(sqlTrace);
}
}
package org.sagacity.sqltoy;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sagacity.sqltoy.utils.FileUtil;
import org.sagacity.sqltoy.utils.IdUtil;
import org.sagacity.sqltoy.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @project sagacity-sqltoy
* @description sqlToy的基础常量参数定义
* @author zhongxuchen
* @version v1.0,Date:2014年12月26日
*/
public class SqlToyConstants {
/**
* 定义日志
*/
protected final static Logger logger = LoggerFactory.getLogger(SqlToyConstants.class);
/**
* 符号对,用来提取字符串中对称字符的过滤,如:{ name(){} },第一个{对称的符合}是最后一位
*/
public static HashMap<String, String> filters = new HashMap<String, String>() {
/**
*
*/
private static final long serialVersionUID = 1636155921862321269L;
{
put("(", ")");
put("'", "'");
put("\"", "\"");
put("[", "]");
put("{", "}");
}
};
// 目前还不支持此功能的提醒
public static String UN_SUPPORT_MESSAGE = "This feature is currently not supported!";
public static final String DEFAULT_NULL = "_SQLTOY_NULL_FLAG";
public static String UN_MATCH_DIALECT_MESSAGE = "Failed to correctly match the corresponding database dialect!";
/**
* 判断sql中是否存在union all的表达式
*/
public static String UNION_ALL_REGEX = "\\W+union\\s+\\all\\W+";
/**
* 判断sql中是否存在union的表达式
*/
public static String UNION_REGEX = "\\W+union\\W+";
/**
* 当sql中是参数条件是?时转换后对应的别名模式:sagParamIndexName+index,如sagParamIndexName0、sagParamIndexName1
* 统一成别名模式的好处在于解决诸如分页、取随机记录等封装处理的统一性问题
*/
public static String DEFAULT_PARAM_NAME = "sagParamIndexName";
/**
* 随机记录数量参数名称
*/
public final static String RANDOM_NAMED = "sagRandomSize";
/**
* 分页开始记录参数Named
*/
public final static String PAGE_FIRST_PARAM_NAME = "pageFirstParamName";
/**
* 分页截止记录参数Named
*/
public final static String PAGE_LAST_PARAM_NAME = "pageLastParamName";
/**
* 临时表占位符号
*/
public final static String TEMPLATE_TABLE_HOLDER = "@templateTable";
/**
* 缓存翻译时在缓存中未匹配上key的返回信息
*/
public static String UNCACHED_KEY_RESULT = "[${value}]未定义";
/**
* 存放sqltoy的系统参数
*/
private static Map<String, String> sqlToyProps = new HashMap<String, String>();
/**
* sqltoy 默认的配置文件
*/
private final static String DEFAULT_CONFIG = "org/sagacity/sqltoy/sqltoy-default.properties";
public final static String XML_FETURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
/**
* 服务器节点ID
*/
public static int WORKER_ID = 0;
/**
* 数据中心ID
*/
public static int DATA_CENTER_ID = 0;
/**
* 为22位或26位主键提供的主机Id
*/
public static String SERVER_ID;
public static String keywordSign = "'";
/**
* sql in 里面参数最大值
*/
public static int SQL_IN_MAX = 999;
/**
* 并行默认最大等待时长
*/
public static int PARALLEL_MAXWAIT_SECONDS = 1800;
public static int FETCH_SIZE = -1;
/**
* 默认一页数据条数
*/
public static int DEFAULT_PAGE_SIZE = 10;
/**
* 变更操作型sql空白默认转为null
*/
public static boolean executeSqlBlankToNull = true;
/**
* 分页中间表名称
*/
public static String INTERMEDIATE_TABLE = "SAG_INTERMEDIATE_TABLE";
public static String INTERMEDIATE_TABLE1 = "SAG_INTERMEDIATE_TABLE1";
/**
* 字符串中内嵌参数的匹配模式
*/
public final static Pattern paramPattern = Pattern.compile(
"\\$\\{\\s*[0-9a-zA-Z\u4e00-\u9fa5]+((\\.|\\_)[0-9a-zA-Z\u4e00-\u9fa5]+)*(\\[\\d*(\\,)?\\d*\\])?\\s*\\}");
// update 2020-9-16 将\\W 替换为[^A-Za-z0-9_:] 增加排除: 适应::jsonb 这种模式场景
// update 2021-10-13 增加参数名称为中文场景(应对一些极为不规范的项目场景)
public final static Pattern SQL_NAMED_PATTERN = Pattern.compile(
"[^A-Za-z0-9_:\u4e00-\u9fa5]\\:\\s*[a-zA-Z\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*(\\.[\\w\u4e00-\u9fa5]+)*(\\[\\d+\\])?\\s?");
public final static Pattern NOSQL_NAMED_PATTERN = Pattern.compile(
"(?i)\\@(param|blank|value)?\\(\\s*\\:\\s*[a-zA-Z\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*(\\.[\\w\u4e00-\u9fa5]+)*(\\[\\d+\\])?\\s*\\)");
// mysql8 支持 with recursive cte as
// postgresql12 支持materialized 物化
// with aliasTable as materialized ()
// with aliasTable as not materialized ()
public final static Pattern withPattern = Pattern.compile(
"(?i)\\s*with\\s+([a-z]+\\s+)?[a-z|0-9|\\_]+\\s*(\\([a-z|0-9|\\_|\\s|\\,]+\\))?\\s+as\\s*(\\s+[a-z|\\_]+){0,2}\\s*\\(");
// with 下面多个as
public final static Pattern otherWithPattern = Pattern.compile(
"(?i)\\s*\\,\\s*([a-z]+\\s+)?[a-z|0-9|\\_]+\\s*(\\([a-z|0-9|\\_|\\s|\\,]+\\))?\\s+as\\s*(\\s+[a-z|\\_]+){0,2}\\s*\\(");
// 以空白结尾
public final static Pattern BLANK_END = Pattern.compile("\\s$");
/**
* 不输出sql的表达式
*/
public final static Pattern NOT_PRINT_REGEX = Pattern.compile("(?i)\\#not\\_(print|debug)\\#");
public final static Pattern DO_PRINT_REGEX = Pattern.compile("(?i)\\#(print|debug)\\#");
/**
* 忽视空记录
*/
public final static Pattern IGNORE_EMPTY_REGEX = Pattern.compile("(?i)\\#ignore_all_null_set\\#");
/**
* 判断sql中是否存在@include(sqlId)的表达式
*/
public final static Pattern INCLUDE_PATTERN = Pattern.compile("(?i)\\@include\\([\\w\\W]*\\)");
// 标记分页或取随机记录原始sql的标记,便于sql interceptor加工处理快速定位
public final static String MARK_ORIGINAL_START = " /*-- sqltoy_original_mark_start --*/ ";
public final static String MARK_ORIGINAL_END = " /*-- sqltoy_original_mark_end --*/ ";
public final static String MERGE_ALIAS_ON = ") tv on (";
public final static String MERGE_ALIAS_ON_REGEX = "\\)\\s+tv\\s+on\\s+\\(";
public final static String MERGE_UPDATE = " when matched then update set ";
public final static String MERGE_INSERT = " when not matched then insert ";
/**
* @todo 解析模板中的参数
* @param template
* @return
*/
private static LinkedHashMap<String, String> parseParams(String template) {
LinkedHashMap<String, String> paramsMap = new LinkedHashMap<String, String>();
Matcher m = paramPattern.matcher(template);
String group;
while (m.find()) {
group = m.group();
// key as ${name} value:name
paramsMap.put(group, group.substring(2, group.length() - 1).trim());
}
return paramsMap;
}
/**
* @todo 获取常量值
* @param key
* @return
*/
public static String getKeyValue(String key) {
String result = sqlToyProps.get(key);
if (result == null) {
result = System.getProperty(key);
}
return result;
}
/**
* @todo 获取常量值
* @param key
* @param defaultValue
* @return
*/
public static String getKeyValue(String key, String defaultValue) {
String result = sqlToyProps.get(key);
if (result == null) {
result = System.getProperty(key);
}
if (StringUtil.isNotBlank(result)) {
return result;
}
return defaultValue;
}
/**
* @todo db2 是否为查询语句自动补充with ur进行脏读
* @return
*/
public static boolean db2WithUR() {
return Boolean.parseBoolean(getKeyValue("sqltoy.db2.search.with.ur", "true"));
}
/**
* @todo 获取记录提取的警告阀值
* @return
*/
public static int getWarnThresholds() {
// 默认值为25000
return Integer.parseInt(getKeyValue("sqltoy.fetch.result.warn.thresholds", "25000"));
}
/**
* @todo 获取项目中在代码中编写的sql数量
* @return
*/
public static int getMaxCodeSqlCount() {
// 默认值为2000
return Integer.parseInt(getKeyValue("sqltoy.max.code.sql.count", "2500"));
}
/**
* @todo 获取记录提取的最大阀值
* @return
*/
public static Long getMaxThresholds() {
// 无限大
return Long.parseLong(getKeyValue("sqltoy.fetch.result.max.thresholds", "999999999999"));
}
/**
* @todo oracle分页是否忽视排序导致错乱的问题
* @return
*/
public static boolean oraclePageIgnoreOrder() {
return Boolean.parseBoolean(getKeyValue("sqltoy.oracle.page.ignore.order", "false"));
}
/**
* @todo 是否显示数据库信息
* @return
*/
public static boolean showDatasourceInfo() {
return Boolean.parseBoolean(getKeyValue("sqltoy.show.datasource.info", "false"));
}
/**
* @todo 获取文件中的常量元素
* @param propertiesFile
*/
private static void loadPropertyFile(String propertiesFile) {
// 加载指定的额外参数,或提供开发者修改默认参数
if (StringUtil.isBlank(propertiesFile)) {
return;
}
InputStream fis = null;
try {
Properties props = new Properties();
fis = FileUtil.getFileInputStream(propertiesFile);
if (fis != null) {
props.load(fis);
sqlToyProps.putAll((Map) props);
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @todo 加载数据库方言的参数
* @param keyValues
*/
public static void loadProperties(Map<String, String> keyValues) {
// 加载默认参数
loadPropertyFile(DEFAULT_CONFIG);
if (keyValues != null && !keyValues.isEmpty()) {
sqlToyProps.putAll(keyValues);
}
}
/**
* @todo 用户可以根据实际数据库方言,通过常量参数转换默认值(如db2 当前时间戳,可以是current
* timestamp,而在oracle中必须是current_timestamp)
* @param dbType
* @param defaultValue
* @return
*/
public static String getDefaultValue(Integer dbType, String defaultValue) {
String realDefault = getKeyValue(defaultValue);
if (realDefault == null) {
if ("CURRENT TIMESTAMP".equals(defaultValue.toUpperCase())) {
return "CURRENT_TIMESTAMP";
}
return defaultValue;
}
return realDefault;
}
/**
* @param uncachedKeyResult the uncachedKeyResult to set
*/
public static void setUncachedKeyResult(String uncachedKeyResult) {
UNCACHED_KEY_RESULT = uncachedKeyResult;
}
/**
* @todo 替换变量参数
* @param template
* @return
*/
public static String replaceParams(String template) {
if (StringUtil.isBlank(template)) {
return template;
}
LinkedHashMap<String, String> paramsMap = parseParams(template);
String result = template;
if (paramsMap.size() > 0) {
Map.Entry<String, String> entry;
String value;
for (Iterator<Map.Entry<String, String>> iter = paramsMap.entrySet().iterator(); iter.hasNext();) {
entry = iter.next();
value = getKeyValue(entry.getValue());
if (value != null) {
result = result.replace(entry.getKey(), value);
}
}
}
return result;
}
/**
* @todo 获取loadAll单个批次最大的记录数量,主要是防止sql in ()参数超过1000导致错误
* @return
*/
public static int getLoadAllBatchSize() {
// 默认值为100
return Integer.parseInt(getKeyValue("sqltoy.loadAll.batchsize", "1000"));
}
/**
* @TODO 是否打开sql签名
* @return
*/
public static boolean openSqlSign() {
return Boolean.parseBoolean(getKeyValue("sqltoy.open.sqlsign", "true"));
}
/**
* @TODO 针对主键策略提前设置或计算雪花算法的worker_id,dataCenterId以及22位和26位主键对应的应用id
* @param workerId
* @param dataCenterId
* @param serverId
*/
public static void setWorkerAndDataCenterId(Integer workerId, Integer dataCenterId, Integer serverId) {
try {
String serverIdentity = IdUtil.getLastIp(2);
int id = Integer.parseInt(serverIdentity == null ? "0" : serverIdentity);
String keyValue;
if (workerId == null) {
keyValue = getKeyValue("sqltoy.snowflake.workerId");
if (keyValue != null) {
workerId = Integer.parseInt(keyValue);
}
}
if (dataCenterId == null) {
keyValue = getKeyValue("sqltoy.snowflake.dataCenterId");
if (keyValue != null) {
dataCenterId = Integer.parseInt(keyValue);
}
}
if (workerId != null && (workerId.intValue() > 0 && workerId.intValue() < 32)) {
WORKER_ID = workerId.intValue();
} else {
if (id > 31) {
// 个位作为workerId
WORKER_ID = id % 10;
} else {
WORKER_ID = id;
}
}
if (dataCenterId != null && dataCenterId.intValue() > 0 && dataCenterId.intValue() < 32) {
DATA_CENTER_ID = dataCenterId.intValue();
} else {
if (id > 31) {
// 十位数作为dataCenterId
DATA_CENTER_ID = id / 10;
} else {
DATA_CENTER_ID = id;
}
}
// 22位或26位主键对应的serverId
String serverNode = (serverId == null) ? getKeyValue("sqltoy.server.id") : ("" + serverId);
if (serverNode != null) {
serverNode = StringUtil.addLeftZero2Len(serverNode, 3);
if (serverNode.length() > 3) {
serverNode = serverNode.substring(serverNode.length() - 3);
}
SERVER_ID = serverNode;
}
} catch (Exception e) {
e.printStackTrace();
logger.error("设置workerId和dataCenterId发生错误:{}", e.getMessage());
}
}
}
/**
*
*/
package org.sagacity.sqltoy.callback;
/**
* @project sagacity-sqltoy
* @description 提供缓存名称映射key的过滤反调函数,用于过滤比如租户、状态信息
* @author zhongxuchen
* @version v1.0, Date:2022年12月19日
* @modify 2022年12月19日,修改说明
*/
@FunctionalInterface
public interface CacheFilter {
public boolean doFilter(Object[] cacheRow);
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.sql.CallableStatement;
import java.sql.ResultSet;
/**
* @project sagacity-sqltoy
* @description 数据库CallableStatement针对存储过程处理反调抽象类,用来处理result
* @author zhongxuchen
* @version v1.0,Date:2009-3-20
*/
public abstract class CallableStatementResultHandler {
/**
* 结果集
*/
private Object result;
/**
* @TODO 存储过程执行
* @param rowData 数据集合
* @param pst 数据库pst
* @param rs ResultSet
* @throws Exception 异常抛出
*/
public abstract void execute(Object rowData, CallableStatement pst, ResultSet rs) throws Exception;
public void setResult(Object result) {
this.result = result;
}
public Object getResult() {
return this.result;
}
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.sql.Connection;
/**
* @project sagacity-sqltoy
* @description 数据库连接反调,通过反调传递connection,并通过Result进行数据交互
* @author zhongxuchen
* @version v1.0,Date:2012-6-10
*/
public abstract class DataSourceCallbackHandler {
/**
* 结果集
*/
private Object result = null;
/**
* @todo 基于给定的连接需要实现的方法
* @param conn 数据库连接
* @param dbType 数据库类型
* @param dialect 数据方言
* @throws Exception 异常
*/
public abstract void doConnection(Connection conn, Integer dbType, String dialect) throws Exception;
/**
* @return the result
*/
public Object getResult() {
return result;
}
/**
* @param result the result to set
*/
public void setResult(Object result) {
this.result = result;
}
}
package org.sagacity.sqltoy.callback;
import javax.sql.DataSource;
import org.sagacity.sqltoy.config.model.SqlToyConfig;
/**
* @project sagacity-sqltoy
* @description 提供sql查询语句跨库适配反调函数
* @author zhongxuchen
* @version v1.0,Date:2022-8-13
*/
@FunctionalInterface
public interface DbAdapterHandler {
public void query(SqlToyConfig sqlToyConfig, DataSource dataSource);
}
package org.sagacity.sqltoy.callback;
import org.sagacity.sqltoy.model.IgnoreCaseSet;
import org.sagacity.sqltoy.plugins.secure.FieldsSecureProvider;
/**
* @project sagacity-sqltoy
* @description 查询时字段密文解密处理器
* @author zhongxuchen
* @version v1.0,Date:2021-11-8
*/
public class DecryptHandler {
/**
* 需要解密的字段
*/
private IgnoreCaseSet columns = new IgnoreCaseSet();
/**
* 加解密逻辑实现类
*/
private FieldsSecureProvider fieldsSecureProvider;
public DecryptHandler(FieldsSecureProvider fieldsSecureProvider, IgnoreCaseSet columns) {
this.fieldsSecureProvider = fieldsSecureProvider;
this.columns = columns;
}
/**
* @TODO 实现解密
* @param column
* @param value
* @return
*/
public Object decrypt(String column, Object value) {
if (value == null || fieldsSecureProvider == null || column == null) {
return value;
}
boolean exists = columns.contains(column);
// 去除下划线
if (!exists) {
exists = columns.contains(column.replace("_", ""));
}
if (exists) {
String content = value.toString();
if ("".equals(content.trim())) {
return value;
}
return fieldsSecureProvider.decrypt(content);
}
return value;
}
public IgnoreCaseSet getColumns() {
return columns;
}
public void setColumns(IgnoreCaseSet columns) {
this.columns = columns;
}
public FieldsSecureProvider getFieldsSecureProvider() {
return fieldsSecureProvider;
}
public void setFieldsSecureProvider(FieldsSecureProvider fieldsSecureProvider) {
this.fieldsSecureProvider = fieldsSecureProvider;
}
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import org.sagacity.sqltoy.config.model.EntityMeta;
import org.sagacity.sqltoy.dialect.model.SavePKStrategy;
/**
* @project sagacity-sqltoy
* @description 定义产生主键策略的抽象类,用于反调方式进行自定义灵活实现
* @author zhongxuchen
* @version v1.0,Date:2015年3月19日
*/
public abstract class GenerateSavePKStrategy {
/**
* @todo 提供不同数据库方言insert数据时主键产生策略
* @param entityMeta
* @return
*/
public abstract SavePKStrategy generate(EntityMeta entityMeta);
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import org.sagacity.sqltoy.config.model.EntityMeta;
/**
* @project sagacity-sqltoy
* @description 提供一个参数sql的反调函数,方便抽象统一的方言处理
* @author zhongxuchen
* @version v1.0,Date:2015年3月5日
*/
public abstract class GenerateSqlHandler {
/**
* @todo 根据pojo的meta产生特定的诸如:insert、update等sql
* @param entityMeta
* @param forceUpdateFields 强制修改的字段
* @return
*/
public abstract String generateSql(EntityMeta entityMeta, String[] forceUpdateFields);
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @project sagacity-sqltoy
* @description 批量行数据插入反调抽象类定义(实际极少使用,留给在超极端场景下自定义pst处理)
* @author zhongxuchen
* @version v1.0,Date:2010-1-5
*/
@FunctionalInterface
public interface InsertRowCallbackHandler {
/**
* @todo 批量插入反调
* @param pst
* @param index 第几行
* @param rowData 单行数据
* @throws SQLException
*/
public void process(PreparedStatement pst, int index, Object rowData) throws SQLException;
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import org.sagacity.sqltoy.model.LockMode;
/**
* @project sagacity-sqltoy
* @description 根据数据库类型获得锁表sql
* @author zhongxuchen
* @version v1.0, Date:2021-3-17
* @modify 2021-3-17,修改说明
*/
@FunctionalInterface
public interface LockSqlHandler {
/**
* @TODO 提供不同数据库类型sql加锁语句处理
* @param sql
* @param dbType 数据库类型
* @param lockMode 锁类型
* @return
*/
public String getLockSql(String sql, Integer dbType, LockMode lockMode);
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.util.List;
import org.sagacity.sqltoy.SqlToyContext;
import org.sagacity.sqltoy.config.model.ShardingGroupModel;
/**
* @project sagacity-sqltoy
* @description 并行执行反调定义
* @author zhongxuchen
* @version v1.0,Date:2017年11月3日
*/
@FunctionalInterface
public interface ParallelCallbackHandler {
/**
* @todo 并行执行反调计算
* @param sqlToyContext
* @param shardingGroupModel 集合数据根据分组策略拆分成的单一组数据和对应table或数据源配置
* @return
* @throws Exception
*/
public List<?> execute(SqlToyContext sqlToyContext, ShardingGroupModel shardingGroupModel) throws Exception;
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @project sagacity-sqltoy
* @description 数据库preparedStatement处理反调抽象类,用来处理result
* @author zhongxuchen
* @version v1.0,Date:2009-3-20
*/
public abstract class PreparedStatementResultHandler {
/**
* 结果集
*/
private Object result;
/**
* @TODO 执行pst
* @param rowData
* @param pst
* @param rs
* @throws Exception
*/
public abstract void execute(Object rowData, PreparedStatement pst, ResultSet rs) throws Exception;
public void setResult(Object result) {
this.result = result;
}
public Object getResult() {
return this.result;
}
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.util.HashMap;
import java.util.List;
/**
* @project sagacity-sqltoy
* @description 反射对象提取数据时,提供对数据的判断和修改(已经很少使用)
* @author zhongxuchen
* @version v1.0,Date:2012-7-14
*/
@SuppressWarnings("rawtypes")
public abstract class ReflectPropsHandler {
/**
* 集合数据的行号
*/
private int rowIndex = 0;
private boolean isArray = true;
/**
* 反射处理的对象属性以及属性顺序,reflectBean(entity,String[] properties) hashMap
*/
private HashMap<String, Integer> propertyIndexMap = new HashMap<String, Integer>();
/**
* 单行数据值,集合反射时优先将该数据初始化,实现类通过process()设置和修改该数据,从而形成 数据交互
*/
private Object[] rowData;
/**
* list 行数据模式
*/
private List rowList;
/**
* 抽象方法,需要由实现类实现具体逻辑
*/
public abstract void process();
/**
* @todo 提供process实现中设置具体属性的值
* @param property
* @param value
*/
@SuppressWarnings("unchecked")
public void setValue(String property, Object value) {
String key = property.toLowerCase();
if (propertyIndexMap.containsKey(key)) {
if (isArray) {
rowData[propertyIndexMap.get(key)] = value;
} else {
rowList.set(propertyIndexMap.get(key), value);
}
}
}
/**
* @todo 获取属性的值
* @param property
* @return
*/
public Object getValue(String property) {
String key = property.toLowerCase();
if (propertyIndexMap.containsKey(key)) {
if (isArray) {
return rowData[propertyIndexMap.get(key)];
}
return rowList.get(propertyIndexMap.get(key));
}
return null;
}
public int getRowIndex() {
return rowIndex;
}
public void setRowIndex(int rowIndex) {
this.rowIndex = rowIndex;
}
public void setPropertyIndexMap(HashMap<String, Integer> propertyIndexMap) {
this.propertyIndexMap = propertyIndexMap;
}
/**
* @return the propertyIndexMap
*/
public HashMap<String, Integer> getPropertyIndexMap() {
return propertyIndexMap;
}
/**
* 判断propertyIndexMap是否已经完成初始化
*
* @return
*/
public boolean initPropsIndexMap() {
if (propertyIndexMap != null && propertyIndexMap.size() > 0) {
return true;
}
return false;
}
/**
* @TODO 取回结果
* @return
*/
public Object[] getRowData() {
return rowData;
}
/**
* @TODO 反射过程中调用,提供交互数据
* @param rowData
*/
public void setRowData(Object[] rowData) {
this.rowData = rowData;
this.isArray = true;
}
/**
* @return the rowList
*/
public List getRowList() {
return rowList;
}
/**
* @param rowList the rowList to set
*/
public void setRowList(List rowList) {
this.isArray = false;
this.rowList = rowList;
}
/**
* @todo 当特定属性的值为一个给定值时,将反射的属性值设置为null
* @param value
* @param properties
*/
@Deprecated
public void setEqualNull(Object value, String... properties) {
if (properties == null || properties.length == 0) {
return;
}
for (String property : properties) {
if (this.getValue(property) != null && this.getValue(property).equals(value)) {
this.setValue(property, null);
}
}
}
}
/**
*
*/
package org.sagacity.sqltoy.callback;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @project sagacity-sqltoy
* @description 行结果集反调处理接口,提供给开发者自行对行进行处理(已经极少使用)
* @author zhongxuchen
* @version v1.0,Date:2008-12-9
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class RowCallbackHandler {
/**
* 结果集
*/
private List result = new ArrayList();
/**
* @todo 行处理抽象方法接口定义
* @param rs
* @param index
* @throws SQLException
*/
public abstract void processRow(ResultSet rs, int index) throws SQLException;
/**
* @todo 返回结果
* @return
*/
public List getResult() {
return result;
}
/**
* @todo 加入行数据
* @param rowData
*/
public void addRow(Object rowData) {
result.add(rowData);
}
}
package org.sagacity.sqltoy.callback;
/**
* @project sagacity-sqltoy
* @description 用于EntityQuery 查询字段使用
* @author zhongxuchen
* @version v1.0, Date:2020-10-15
* @modify 2020-10-15,修改说明
*/
public abstract class SelectFields {
/**
* @TODO 获取最终查询字段
* @return
*/
public abstract String[] getSelectFields();
}
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册