1、R 返回值去掉 dataMap
2、basecontroller 插入和 批量插入都使用 id返回值
3、优化postgres支持 修改 com.mybatisflex.core.handler 为 com.cool.core.mybatis.handler
This commit is contained in:
ruying408
2025-05-11 21:04:45 +08:00
parent fc2a06d4b6
commit 509e2f1f4d
18 changed files with 552 additions and 40 deletions

View File

@@ -18,14 +18,14 @@
<properties>
<java.version>17</java.version>
<lombok.version>1.18.34</lombok.version>
<mybatis-flex.version>1.10.8</mybatis-flex.version>
<mybatis-flex.ext.version>1.10.8.122</mybatis-flex.ext.version>
<mybatis-flex.version>1.10.9</mybatis-flex.version>
<mybatis-flex.ext.version>1.10.9.125</mybatis-flex.ext.version>
<hutool.version>5.8.26</hutool.version>
<ognl.version>3.3.2</ognl.version>
<fastjson2.version>2.0.51</fastjson2.version>
<springdoc-openapi.version>2.5.0</springdoc-openapi.version>
<perf4j.version>0.9.16</perf4j.version>
<weixin-java.version>4.6.3.B</weixin-java.version>
<weixin-java.version>4.7.0</weixin-java.version>
</properties>
<dependencies>
@@ -84,7 +84,6 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>

View File

@@ -119,7 +119,7 @@ public abstract class BaseController<S extends BaseService<T>, T extends BaseEnt
if (JSONUtil.isTypeJSONArray(body)) {
JSONArray array = JSONUtil.parseArray(body);
return R.ok(Dict.create()
.set("ids", service.addBatch(requestParams, array.toList(currentEntityClass()))));
.set("id", service.addBatch(requestParams, array.toList(currentEntityClass()))));
} else {
return R.ok(Dict.create().set("id",
service.add(requestParams, requestParams.toBean(currentEntityClass()))));

View File

@@ -9,6 +9,8 @@ import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cool.core.base.service.MapperProviderService;
import com.cool.core.mybatis.pg.PostgresSequenceSyncService;
import com.cool.core.util.DatabaseDialectUtils;
import com.cool.core.util.EntityUtils;
import com.cool.modules.base.entity.sys.BaseSysConfEntity;
import com.cool.modules.base.entity.sys.BaseSysMenuEntity;
@@ -49,6 +51,8 @@ public class DBFromJsonInit {
final private ApplicationEventPublisher eventPublisher;
final private PostgresSequenceSyncService postgresSequenceSyncService;
@Value("${cool.initData}")
private boolean initData;
@@ -58,14 +62,24 @@ public class DBFromJsonInit {
return;
}
// 初始化自定义的数据
extractedDb();
boolean initFlag = extractedDb();
// 初始化菜单数据
extractedMenu();
initFlag = extractedMenu() || initFlag;
// 发送数据库初始化完成事件
eventPublisher.publishEvent(new DbInitCompleteEvent(this));
if (initFlag) {
// 如果是postgresql同步序列
syncIdentitySequences();
}
log.info("数据初始化完成!");
}
private void syncIdentitySequences() {
if (DatabaseDialectUtils.isPostgresql()) {
postgresSequenceSyncService.syncIdentitySequences();
}
}
@Getter
public static class DbInitCompleteEvent {
private final Object source;
@@ -79,21 +93,23 @@ public class DBFromJsonInit {
/**
* 解析插入业务数据
*/
private void extractedDb() {
private boolean extractedDb() {
try {
// 加载 JSON 文件
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:cool/data/db/*.json");
// 遍历所有.json文件
analysisResources(resources);
return analysisResources(resources);
} catch (Exception e) {
log.error("Failed to initialize data", e);
}
return false;
}
private void analysisResources(Resource[] resources)
private boolean analysisResources(Resource[] resources)
throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
String prefix = "db_";
boolean isInit = false;
for (Resource resource : resources) {
File resourceFile = new File(resource.getURL().getFile());
String fileName = prefix + resourceFile.getName();
@@ -112,8 +128,10 @@ public class DBFromJsonInit {
baseSysUserEntity.setCValue("success");
// 当前文件已加载
baseSysConfService.add(baseSysUserEntity);
isInit = true;
log.info("{} 业务数据初始化成功...", fileName);
}
return isInit;
}
private void analysisJson(JSONObject jsonObject)
@@ -158,7 +176,8 @@ public class DBFromJsonInit {
/**
* 解析插入菜单数据
*/
public void extractedMenu() {
public boolean extractedMenu() {
boolean initFlag = false;
try {
String prefix = "menu_";
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@@ -173,10 +192,12 @@ public class DBFromJsonInit {
continue;
}
analysisResources(resource, fileName);
initFlag = true;
}
} catch (Exception e) {
log.error("Failed to initialize data", e);
}
return initFlag;
}
private void analysisResources(Resource resource, String fileName) throws IOException {

View File

@@ -0,0 +1,48 @@
package com.cool.core.mybatis.handler;
import com.cool.core.util.DatabaseDialectUtils;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgresql.util.PGobject;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public abstract class BaseJsonTypeHandler<T> extends BaseTypeHandler<T> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (DatabaseDialectUtils.isPostgresql()) {
PGobject jsonObject = new PGobject();
jsonObject.setType("json");
jsonObject.setValue(toJson(parameter));
ps.setObject(i, jsonObject);
} else {
ps.setString(i, toJson(parameter));
}
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
final String json = rs.getString(columnName);
return StringUtil.noText(json) ? null : parseJson(json);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
final String json = rs.getString(columnIndex);
return StringUtil.noText(json) ? null : parseJson(json);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
final String json = cs.getString(columnIndex);
return StringUtil.noText(json) ? null : parseJson(json);
}
protected abstract T parseJson(String json);
protected abstract String toJson(T object);
}

View File

@@ -0,0 +1,73 @@
package com.cool.core.mybatis.handler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.TypeReference;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
public class Fastjson2TypeHandler extends BaseJsonTypeHandler<Object> {
private final Class<?> propertyType;
private Class<?> genericType;
private Type type;
private boolean supportAutoType = false;
public Fastjson2TypeHandler(Class<?> propertyType) {
this.propertyType = propertyType;
this.supportAutoType = propertyType.isInterface() || Modifier.isAbstract(propertyType.getModifiers());
}
public Fastjson2TypeHandler(Class<?> propertyType, Class<?> genericType) {
this.propertyType = propertyType;
this.genericType = genericType;
this.type = TypeReference.collectionType((Class<? extends Collection>) propertyType, genericType);
Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
if (actualTypeArgument instanceof Class) {
this.supportAutoType = ((Class<?>) actualTypeArgument).isInterface()
|| Modifier.isAbstract(((Class<?>) actualTypeArgument).getModifiers());
}
}
@Override
protected Object parseJson(String json) {
if (genericType != null && Collection.class.isAssignableFrom(propertyType)) {
if (supportAutoType) {
return JSON.parseArray(json, Object.class, JSONReader.Feature.SupportAutoType);
} else {
return JSON.parseObject(json, type);
}
} else {
if (supportAutoType) {
return JSON.parseObject(json, Object.class, JSONReader.Feature.SupportAutoType);
} else {
return JSON.parseObject(json, propertyType);
}
}
}
@Override
protected String toJson(Object object) {
if (supportAutoType) {
return JSON.toJSONString(object
, JSONWriter.Feature.WriteMapNullValue
, JSONWriter.Feature.WriteNullListAsEmpty
, JSONWriter.Feature.WriteNullStringAsEmpty, JSONWriter.Feature.WriteClassName
);
} else {
return JSON.toJSONString(object
, JSONWriter.Feature.WriteMapNullValue
, JSONWriter.Feature.WriteNullListAsEmpty
, JSONWriter.Feature.WriteNullStringAsEmpty
);
}
}
}

View File

@@ -0,0 +1,68 @@
package com.cool.core.mybatis.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.exception.FlexExceptions;
import java.io.IOException;
import java.util.Collection;
public class JacksonTypeHandler extends BaseJsonTypeHandler<Object> {
private static ObjectMapper objectMapper;
private final Class<?> propertyType;
private Class<?> genericType;
private JavaType javaType;
public JacksonTypeHandler(Class<?> propertyType) {
this.propertyType = propertyType;
}
public JacksonTypeHandler(Class<?> propertyType, Class<?> genericType) {
this.propertyType = propertyType;
this.genericType = genericType;
}
@Override
protected Object parseJson(String json) {
try {
if (genericType != null && Collection.class.isAssignableFrom(propertyType)) {
return getObjectMapper().readValue(json, getJavaType());
} else {
return getObjectMapper().readValue(json, propertyType);
}
} catch (IOException e) {
throw FlexExceptions.wrap(e, "Can not parseJson by JacksonTypeHandler: " + json);
}
}
@Override
protected String toJson(Object object) {
try {
return getObjectMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
throw FlexExceptions.wrap(e, "Can not convert object to Json by JacksonTypeHandler: " + object);
}
}
public JavaType getJavaType() {
if (javaType == null){
javaType = getObjectMapper().getTypeFactory().constructCollectionType((Class<? extends Collection>) propertyType, genericType);
}
return javaType;
}
public static ObjectMapper getObjectMapper() {
if (null == objectMapper) {
objectMapper = new ObjectMapper();
}
return objectMapper;
}
public static void setObjectMapper(ObjectMapper objectMapper) {
JacksonTypeHandler.objectMapper = objectMapper;
}
}

View File

@@ -0,0 +1,66 @@
package com.cool.core.mybatis.pg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import java.util.Map;
/**
* PostgreSQL Identity 序列同步服务
* 解决PostgreSQL 默认的序列机制序列会自动递增当手动插入指定id时需调用同步接口否则id会重复。
*/
@Slf4j
@Service
public class PostgresSequenceSyncService {
private final JdbcTemplate jdbcTemplate;
public PostgresSequenceSyncService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void syncIdentitySequences() {
log.info("⏳ 开始同步 PostgreSQL Identity 序列...");
// 查询所有 identity 字段
String identityColumnQuery = """
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE is_identity = 'YES'
AND table_schema = 'public'
""";
List<Map<String, Object>> identityColumns = jdbcTemplate.queryForList(identityColumnQuery);
for (Map<String, Object> col : identityColumns) {
String schema = (String) col.get("table_schema");
String table = (String) col.get("table_name");
String column = (String) col.get("column_name");
String fullTable = schema + "." + table;
// 获取对应的序列名
String seqNameSql = "SELECT pg_get_serial_sequence(?, ?)";
String seqName = jdbcTemplate.queryForObject(seqNameSql, String.class, fullTable, column);
if (seqName == null) {
log.warn("⚠️ 无法获取序列:{}.{}", table, column);
continue;
}
// 获取当前最大 ID
Long maxId = jdbcTemplate.queryForObject(
String.format("SELECT COALESCE(MAX(%s), 0) FROM %s", column, fullTable),
Long.class
);
if (maxId != null && maxId > 0) { // 正确的setval 有返回值,必须用 queryForObject
String setvalSql = "SELECT setval(?, ?)";
Long newVal = jdbcTemplate.queryForObject(setvalSql, Long.class, seqName, maxId);
log.info("✅ 同步序列 [{}] -> 当前最大 ID: {}", seqName, newVal);
}
}
log.info("✅ PostgreSQL Identity 序列同步完成。");
}
}

View File

@@ -4,8 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 返回信息
@@ -24,11 +22,7 @@ public class R<T> implements Serializable {
@Schema(title = "响应数据")
private T data;
@Schema(title = "响应数据")
private Map<String, Object> dataMap = new HashMap<String, Object>();
public R() {
}
@@ -70,14 +64,10 @@ public class R<T> implements Serializable {
public R<T> put(String key, Object value) {
if ( key.equals( "code") ) {
this.code = (int)value;
} else if ( key.equals( "message") ) {
this.message = (String)value;
} else if ( key.equals( "data") ) {
this.data = (T) value;
} else {
dataMap.put(key, value);
switch (key) {
case "code" -> this.code = (int) value;
case "message" -> this.message = (String) value;
case "data" -> this.data = (T) value;
}
return this;
}

View File

@@ -0,0 +1,27 @@
package com.cool.core.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import java.io.Serializable;
public class AutoTypeConverter {
/**
* 将字符串自动转换为数字或保留为字符串
*
* @param input 输入字符串
* @return Integer / Long / String
*/
public static Serializable autoConvert(Object input) {
if (input == null) {
return null;
}
if (NumberUtil.isInteger(input.toString())) {
return Convert.convert(Integer.class, input);
} else if (NumberUtil.isLong(input.toString())) {
return Convert.convert(Long.class, input);
} else {
return (Serializable) input;
}
}
}

View File

@@ -12,16 +12,25 @@ import javax.sql.DataSource;
public class DatabaseDialectUtils {
private static String dialect;
public static String getDatabaseDialect() {
public static String getDatabaseDialect(DataSource dataSource) {
if (dialect == null) {
dialect = determineDatabaseType();
dialect = determineDatabaseType(dataSource);
}
return dialect;
}
private static String determineDatabaseType() {
// 从 DataSource 获取连接
public static boolean isPostgresql() {
DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource));
}
public static boolean isPostgresql(DataSource dataSource) {
return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource));
}
private static String determineDatabaseType(DataSource dataSource) {
// 从 DataSource 获取连接
try (Connection connection = dataSource.getConnection()) {
// 获取元数据
DatabaseMetaData metaData = connection.getMetaData();

View File

@@ -3,7 +3,7 @@ package com.cool.modules.base.entity.sys;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.Fastjson2TypeHandler;
import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
import lombok.Getter;
import lombok.Setter;

View File

@@ -3,7 +3,7 @@ package com.cool.modules.base.entity.sys;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.Fastjson2TypeHandler;
import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
import java.util.List;
import lombok.Getter;

View File

@@ -27,7 +27,6 @@ import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.update.UpdateChain;
import lombok.RequiredArgsConstructor;
import org.dromara.autotable.core.constants.DatabaseDialect;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -55,8 +54,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
// 用户的部门权限
Long[] permsDepartmentArr = coolCache.get("admin:department:" + tokenInfo.get("userId"),
Long[].class);
String databaseDialect = DatabaseDialectUtils.getDatabaseDialect();
if (databaseDialect.equals(DatabaseDialect.PostgreSQL)) {
if (DatabaseDialectUtils.isPostgresql()) {
// 兼容postgresql
qw.select("base_sys_user.id","base_sys_user.create_time","base_sys_user.department_id",
"base_sys_user.email","base_sys_user.head_img","base_sys_user.name","base_sys_user.nick_name",
@@ -96,7 +94,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl<BaseSysUserMapper, B
permsDepartmentArr == null || permsDepartmentArr.length == 0 ? new Long[]{null}
: permsDepartmentArr,
!CoolSecurityUtil.getAdminUsername().equals("admin")));
if (databaseDialect.equals(DatabaseDialect.PostgreSQL)) {
if (DatabaseDialectUtils.isPostgresql()) {
// 兼容postgresql
qw.groupBy("base_sys_user.id","base_sys_user.create_time","base_sys_user.department_id",
"base_sys_user.email","base_sys_user.head_img","base_sys_user.name","base_sys_user.nick_name",

View File

@@ -4,8 +4,8 @@ import com.cool.core.base.BaseEntity;
import com.cool.core.config.PluginJson;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.Fastjson2TypeHandler;
import com.mybatisflex.core.handler.JacksonTypeHandler;
import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
import com.cool.core.mybatis.handler.JacksonTypeHandler;
import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
import com.tangzc.mybatisflex.autotable.annotation.UniIndex;
import lombok.Getter;

View File

@@ -3,7 +3,7 @@ package com.cool.modules.recycle.entity;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.handler.Fastjson2TypeHandler;
import com.cool.core.mybatis.handler.Fastjson2TypeHandler;
import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
import java.util.List;
import java.util.Map;

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.cool.core.util.AutoTypeConverter;
import com.cool.modules.task.entity.TaskInfoEntity;
import com.cool.modules.task.entity.TaskLogEntity;
import com.cool.modules.task.service.TaskInfoLogService;
@@ -36,7 +37,7 @@ public class ScheduleJob extends QuartzJobBean {
Scheduler scheduler = SpringUtil.getBean(Scheduler.class);
TaskInfoEntity taskInfoEntity = taskInfoService
.getById(context.getJobDetail().getKey().getName().split("_")[1]);
.getById(AutoTypeConverter.autoConvert(context.getJobDetail().getKey().getName().split("_")[1]));
if (ObjUtil.isEmpty(taskInfoEntity)) {
log.warn("taskInfoEntity is null");
return;

View File

@@ -0,0 +1,53 @@
package org.dromara.autotable.core.strategy.pgsql.builder;
import org.dromara.autotable.annotation.enums.DefaultValueEnum;
import org.dromara.autotable.core.strategy.ColumnMetadata;
import org.dromara.autotable.core.utils.StringConnectHelper;
import org.dromara.autotable.core.utils.StringUtils;
/**
* 列相关的SQL生成器
* 覆盖原始的ColumnSqlBuilder GENERATED ALWAYS AS IDENTITY 改为 GENERATED BY DEFAULT AS IDENTITY
* 已提issues https://gitee.com/dromara/auto-table/issues/IC6TR5
*/
public class ColumnSqlBuilder {
/**
* 生成字段相关的SQL片段
*
* @param columnMetadata 列元数据
* @return 列相关的sql
*/
public static String buildSql(ColumnMetadata columnMetadata) {
// 例子:"name" varchar(100) NULL DEFAULT '张三' COMMENT '名称'
// 例子:"id" int4(32) NOT NULL AUTO_INCREMENT COMMENT '主键'
StringConnectHelper sql = StringConnectHelper.newInstance("{columnName} {typeAndLength} {null} {default}")
.replace("{columnName}", columnMetadata.getName())
.replace("{typeAndLength}", () -> columnMetadata.getType().getDefaultFullType());
// 如果是自增列则使用GENERATED ALWAYS AS IDENTITY, 忽略not null和默认值的配置
if (columnMetadata.isAutoIncrement()) {
return sql.replace("{null} {default}", "GENERATED BY DEFAULT AS IDENTITY").toString();
}
return sql.replace("{null}", columnMetadata.isNotNull() ? "NOT NULL" : "")
.replace("{default}", () -> {
// 指定NULL
DefaultValueEnum defaultValueType = columnMetadata.getDefaultValueType();
if (defaultValueType == DefaultValueEnum.NULL) {
return "DEFAULT NULL";
}
// 指定空字符串
if (defaultValueType == DefaultValueEnum.EMPTY_STRING) {
return "DEFAULT ''";
}
// 自定义
String defaultValue = columnMetadata.getDefaultValue();
if (DefaultValueEnum.isCustom(defaultValueType) && StringUtils.hasText(defaultValue)) {
return "DEFAULT " + defaultValue;
}
return "";
})
.toString();
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.quartz;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import com.cool.core.util.DatabaseDialectUtils;
import org.quartz.Calendar;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.transaction.PlatformTransactionManager;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler.
* 覆盖原始的QuartzAutoConfiguration兼容postgres数据库
* properties.getProperties().put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
* @author Vedran Pavic
* @author Stephane Nicoll
* @since 2.0.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class })
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class)
public class QuartzAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SchedulerFactoryBean quartzScheduler(QuartzProperties properties,
ObjectProvider<SchedulerFactoryBeanCustomizer> customizers, ObjectProvider<JobDetail> jobDetails,
Map<String, Calendar> calendars, ObjectProvider<Trigger> triggers, ApplicationContext applicationContext) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
schedulerFactoryBean.setJobFactory(jobFactory);
if (properties.getSchedulerName() != null) {
schedulerFactoryBean.setSchedulerName(properties.getSchedulerName());
}
schedulerFactoryBean.setAutoStartup(properties.isAutoStartup());
schedulerFactoryBean.setStartupDelay((int) properties.getStartupDelay().getSeconds());
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(properties.isWaitForJobsToCompleteOnShutdown());
schedulerFactoryBean.setOverwriteExistingJobs(properties.isOverwriteExistingJobs());
if (!properties.getProperties().isEmpty()) {
schedulerFactoryBean.setQuartzProperties(asProperties(properties.getProperties()));
}
schedulerFactoryBean.setJobDetails(jobDetails.orderedStream().toArray(JobDetail[]::new));
schedulerFactoryBean.setCalendars(calendars);
schedulerFactoryBean.setTriggers(triggers.orderedStream().toArray(Trigger[]::new));
customizers.orderedStream().forEach((customizer) -> customizer.customize(schedulerFactoryBean));
return schedulerFactoryBean;
}
private Properties asProperties(Map<String, String> source) {
Properties properties = new Properties();
properties.putAll(source);
return properties;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc")
@Import(DatabaseInitializationDependencyConfigurer.class)
protected static class JdbcStoreTypeConfiguration {
@Bean
@Order(0)
public SchedulerFactoryBeanCustomizer dataSourceCustomizer(QuartzProperties properties, DataSource dataSource,
@QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
ObjectProvider<PlatformTransactionManager> transactionManager,
@QuartzTransactionManager ObjectProvider<PlatformTransactionManager> quartzTransactionManager) {
return (schedulerFactoryBean) -> {
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
schedulerFactoryBean.setDataSource(dataSourceToUse);
PlatformTransactionManager txManager = getTransactionManager(transactionManager,
quartzTransactionManager);
if (txManager != null) {
schedulerFactoryBean.setTransactionManager(txManager);
}
};
}
private DataSource getDataSource(DataSource dataSource, ObjectProvider<DataSource> quartzDataSource) {
DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable();
return (dataSourceIfAvailable != null) ? dataSourceIfAvailable : dataSource;
}
private PlatformTransactionManager getTransactionManager(
ObjectProvider<PlatformTransactionManager> transactionManager,
ObjectProvider<PlatformTransactionManager> quartzTransactionManager) {
PlatformTransactionManager transactionManagerIfAvailable = quartzTransactionManager.getIfAvailable();
return (transactionManagerIfAvailable != null) ? transactionManagerIfAvailable
: transactionManager.getIfUnique();
}
@Bean
@ConditionalOnMissingBean(QuartzDataSourceScriptDatabaseInitializer.class)
@Conditional(OnQuartzDatasourceInitializationCondition.class)
public QuartzDataSourceScriptDatabaseInitializer quartzDataSourceScriptDatabaseInitializer(
DataSource dataSource, @QuartzDataSource ObjectProvider<DataSource> quartzDataSource,
QuartzProperties properties) {
DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource);
if (DatabaseDialectUtils.isPostgresql(dataSource)) {
properties.getProperties().put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
}
return new QuartzDataSourceScriptDatabaseInitializer(dataSourceToUse, properties);
}
static class OnQuartzDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnQuartzDatasourceInitializationCondition() {
super("Quartz", "spring.quartz.jdbc.initialize-schema");
}
}
}
}