
app.myoss.cloud.mybatis.repository.service.impl.BaseCrudServiceImpl Maven / Gradle / Ivy
/*
* Copyright 2018-2018 https://github.com/myoss
*
* 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
*
* http://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 app.myoss.cloud.mybatis.repository.service.impl;
import static app.myoss.cloud.mybatis.repository.utils.DbUtils.checkDBResult;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import app.myoss.cloud.core.constants.MyossConstants;
import app.myoss.cloud.core.lang.bean.BeanUtil;
import app.myoss.cloud.core.lang.concurrent.CallableFunc;
import app.myoss.cloud.core.lang.dto.Order;
import app.myoss.cloud.core.lang.dto.Page;
import app.myoss.cloud.core.lang.dto.Result;
import app.myoss.cloud.core.lang.dto.Sort;
import app.myoss.cloud.mybatis.constants.MybatisConstants;
import app.myoss.cloud.mybatis.mapper.template.CrudMapper;
import app.myoss.cloud.mybatis.repository.entity.LogicDeleteEntity;
import app.myoss.cloud.mybatis.repository.entity.PrimaryKeyEntity;
import app.myoss.cloud.mybatis.repository.service.CrudService;
import app.myoss.cloud.mybatis.table.TableColumnInfo;
import app.myoss.cloud.mybatis.table.TableInfo;
import app.myoss.cloud.mybatis.table.TableMetaObject;
import lombok.extern.slf4j.Slf4j;
/**
* 实现数据库表增、删、改、查常用操作的基类
*
* @param "实体类"的 Mapper Interface 接口
* @param 实体类
* @author Jerry.Chen
* @since 2018年5月9日 下午2:09:18
*/
@Slf4j
public class BaseCrudServiceImpl, T> implements CrudService {
protected Class> mapperClass;
protected Class> entityClass;
protected TableInfo tableInfo;
/**
* 表中的所有字段,用于校验字段名,防止sql注入
*/
protected Map fieldColumns;
protected M crudMapper;
/**
* 初始化实现数据库表增、删、改、查常用操作的基类
*/
public BaseCrudServiceImpl() {
Class extends BaseCrudServiceImpl> clazz = this.getClass();
Type genType = clazz.getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
this.mapperClass = (Class) params[0];
this.entityClass = (Class) params[1];
}
/**
* 使用 Spring 自动注入"实体类"的 Mapper Interface 接口代理对象
*
* @param crudMapper "实体类"的 Mapper Interface 接口代理对象
*/
@Autowired
public void setCrudMapper(M crudMapper) {
this.crudMapper = crudMapper;
this.tableInfo = TableMetaObject.getTableInfo(this.entityClass);
if (this.tableInfo != null) {
this.fieldColumns = Collections.unmodifiableMap(this.tableInfo.getColumns().stream().collect(
Collectors.toMap(TableColumnInfo::getProperty, TableColumnInfo::getActualColumn)));
} else {
log.error("[{}] getTableInfo failed in [{}]", this.entityClass, this.getClass());
}
}
/**
* 检查待保存的记录的字段是否有null值
*
* @param result 执行结果
* @param record 实体对象
* @param optionParam 可选参数,默认为 {@code null }
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkNull4Create(Result> result, T record, Object optionParam) {
if (!result.isSuccess()) {
return false;
}
if (record == null) {
result.setSuccess(false).setErrorCode(MybatisConstants.VALUE_IS_BLANK).setErrorMsg("实体对象不能为空");
}
return result.isSuccess();
}
/**
* 检查主键字段是否为空
*
* @param sqlCommandType 执行的 SQL 命令类型
* @param result 执行结果
* @param id 主键值
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkPrimaryKeyIsNull(SqlCommandType sqlCommandType, Result> result, Serializable id) {
if (!result.isSuccess()) {
return false;
}
if (id == null) {
result.setSuccess(false).setErrorCode(MybatisConstants.VALUE_IS_BLANK).setErrorMsg("主键字段不能为空");
}
return result.isSuccess();
}
/**
* 检查实体和主键字段是否为空
*
* @param sqlCommandType 执行的 SQL 命令类型
* @param record 实体对象
* @param checkAll 检查所有的主键字段值是否为空(false: 只要有一个主键字段为空,则校验失败)
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkPrimaryKeyIsNull(SqlCommandType sqlCommandType, Object record, boolean checkAll) {
boolean isNull = record == null;
if (!isNull) {
int nullCount = 0;
Set primaryKeyColumns = tableInfo.getPrimaryKeyColumns();
for (TableColumnInfo columnInfo : primaryKeyColumns) {
Object value = BeanUtil.methodInvoke(columnInfo.getPropertyDescriptor().getReadMethod(), record);
if (value == null) {
nullCount++;
} else if (value instanceof CharSequence && StringUtils.isBlank((CharSequence) value)) {
nullCount++;
}
if (nullCount > 0 && !checkAll) {
isNull = true;
break;
}
}
if (checkAll && nullCount > 0 && nullCount == primaryKeyColumns.size()) {
isNull = true;
}
}
return isNull;
}
/**
* 检查实体和主键字段是否为空
*
* @param sqlCommandType 执行的 SQL 命令类型
* @param result 执行结果
* @param record 实体对象
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkPrimaryKeyIsNull(SqlCommandType sqlCommandType, Result> result, Object record) {
if (!result.isSuccess()) {
return false;
}
boolean isNull = checkPrimaryKeyIsNull(sqlCommandType, record, true);
if (isNull) {
result.setSuccess(false).setErrorCode(MybatisConstants.VALUE_IS_BLANK).setErrorMsg("主键字段不能为空");
}
return result.isSuccess();
}
/**
* 检查通用查询条件字段是否为空,这里只检查主键id是否为空,防止全表扫描
*
* @param sqlCommandType 执行的 SQL 命令类型
* @param result 执行结果
* @param condition 查询条件
* @param extraCondition 扩展查询条件,需要自定义
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkCommonQueryConditionIsAllNull(SqlCommandType sqlCommandType, Result> result, T condition,
Map extraCondition) {
if (!result.isSuccess()) {
return false;
}
return checkPrimaryKeyIsNull(sqlCommandType, result, condition);
}
/**
* 校验分页查询条件字段是否有空值,默认不做任何校验,子类去重写
*
* @param condition 分页查询条件
* @param result 分页查询返回结果
* @return true: 校验成功; false: 校验失败
*/
protected boolean checkPageConditionIsAllNull(Page condition, Page result) {
// 分页查询,默认不做任何校验
return result.isSuccess();
}
/**
* 检查待保存的记录的字段是否符合预期的格式
*
* @param result 执行结果
* @param record 实体对象
* @param optionParam 可选参数,默认为 {@code null }
* @return true: 校验成功; false: 校验失败
*/
protected boolean validFieldValue(Result> result, T record, Object optionParam) {
if (!result.isSuccess()) {
return false;
}
if (record == null) {
result.setSuccess(false).setErrorCode(MybatisConstants.VALUE_IS_BLANK).setErrorMsg("实体对象不能为空");
}
return result.isSuccess();
}
/**
* 检查待保存的记录的字段是否符合预期的格式
*
* @param result 执行结果
* @param record 实体对象
* @param optionParam 可选参数,默认为 {@code null }
* @return true: 校验成功; false: 校验失败
*/
protected boolean validFieldValue(Result> result, Map record, Object optionParam) {
if (!result.isSuccess()) {
return false;
}
if (record == null) {
result.setSuccess(false).setErrorCode(MybatisConstants.VALUE_IS_BLANK).setErrorMsg("实体对象不能为空");
}
return result.isSuccess();
}
/**
* 检查待保存的记录的字段是否符合预期的格式
*
* @param result 执行结果
* @param record 实体对象
* @param optionParam 可选参数,默认为 {@code null }
* @return true: 校验成功; false: 校验失败
*/
protected boolean createValidate(Result> result, T record, Object optionParam) {
if (!checkNull4Create(result, record, optionParam)) {
return false;
}
return validFieldValue(result, record, optionParam);
}
/**
* 将 {@code Map} 中的 {@code key} 转换成数据库字段名,会校验数据库字段名,防止SQL注入
*
* @param record 待更新的实体对象,key:是数据库列名,value:是数据库列的值
* @return 数据库字段列表
*/
protected Map convertToUpdateUseMap(Map record) {
Map updateMap = new HashMap<>(record.size());
for (Entry entry : record.entrySet()) {
String key = entry.getKey();
String columnName = fieldColumns.get(key);
if (columnName != null) {
// 校验字段名,防止SQL注入
updateMap.put(columnName, entry.getValue());
} else {
log.error("[{}] ignored invalid filed: {}", this.getClass(), key);
}
}
return updateMap;
}
/**
* 将实体类排序字段转换成数据库字段排序,会校验数据库字段名,防止SQL注入
*
* @param sort 实体类排序字段
* @return 数据库字段排序
*/
protected List convertToOrders(Sort sort) {
if (sort == null || CollectionUtils.isEmpty(sort.getOrders())) {
return null;
}
List orders = new ArrayList<>(sort.getOrders().size());
for (Order item : sort.getOrders()) {
String columnName = fieldColumns.get(item.getProperty());
if (columnName != null) {
// 校验字段名,防止SQL注入
Order order = new Order(item.getDirection(), columnName);
orders.add(order);
} else {
log.error("[{}] ignored invalid filed: {}", this.getClass(), item.getProperty());
}
}
return orders;
}
/**
* 查询存在的记录,用于"检查待保存的实体对象是否已经有存在相同的记录(幂等校验)"
*
* @param result 执行结果
* @param record 待保存的实体对象
* @return 存在的记录
* @see #checkRecordIfExist4Create(Result, Object)
* @see #checkRecordIfExist4Update(Result, Object)
*/
protected List findExistRecord4CheckRecord(Result> result, T record) {
return null;
}
/**
* 检查待保存的实体对象是否已经有存在相同的记录(幂等校验)
*
* @param result 执行结果
* @param record 实体对象
* @return true: 存在相同记录, false: 不存在相同记录
* @see #findExistRecord4CheckRecord(Result, Object)
*/
protected boolean checkRecordIfExist4Create(Result> result, T record) {
List exists = findExistRecord4CheckRecord(result, record);
if (CollectionUtils.isEmpty(exists)) {
return false;
}
result.setSuccess(false).setErrorCode(MybatisConstants.MORE_RECORDS);
T item = exists.get(0);
StringBuilder errorMsg = new StringBuilder();
Iterator iterator = tableInfo.getPrimaryKeyColumns().iterator();
boolean hasNext = iterator.hasNext();
while (hasNext) {
TableColumnInfo columnInfo = iterator.next();
Object value = BeanUtil.methodInvoke(columnInfo.getPropertyDescriptor().getReadMethod(), item);
errorMsg.append(columnInfo.getProperty()).append("=").append(value);
hasNext = iterator.hasNext();
if (hasNext) {
errorMsg.append(", ");
}
}
if (errorMsg.length() > 0) {
errorMsg.insert(0, "已经存在相同的记录,主键值[").append("]");
result.setErrorMsg(errorMsg.toString());
} else {
result.setErrorMsg("已经存在相同的记录");
}
return true;
}
/**
* 检查待更新的实体对象是否已经有存在相同的记录(幂等校验)。如果实体的类型是 {@link LogicDeleteEntity}
* ,如果数据是逻辑删除,则校验通过。
*
* @param result 执行结果
* @param record 实体对象
* @return true: 存在相同记录, false: 不存在相同记录
* @see #findExistRecord4CheckRecord(Result, Object)
*/
protected boolean checkRecordIfExist4Update(Result> result, T record) {
if (record instanceof LogicDeleteEntity
&& StringUtils.equals(((LogicDeleteEntity) record).getIsDeleted(), MyossConstants.Y)) {
return false;
}
List exists = findExistRecord4CheckRecord(result, record);
if (CollectionUtils.isEmpty(exists)) {
return false;
}
if (record instanceof PrimaryKeyEntity) {
// 使用主键实体类的字段比较
Serializable id = ((PrimaryKeyEntity) record).getPrimaryKey();
for (T item : exists) {
Serializable id1 = ((PrimaryKeyEntity) item).getPrimaryKey();
if (!id1.equals(id)) {
result.setSuccess(false).setErrorCode(MybatisConstants.MORE_RECORDS).setErrorMsg(
"已经存在相同的记录,主键id=" + id1);
return true;
}
}
return false;
}
Set primaryKeyColumns = tableInfo.getPrimaryKeyColumns();
if (!CollectionUtils.isEmpty(primaryKeyColumns)) {
// 使用主键字段比较
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy