在许多业务场景中,我们需要对数据进行排序,尤其是在列表展示和排序管理中。传统的排序方式通常是在数据库查询时通过 SQL 语句实现,但这对于复杂的排序逻辑(如修改一个排序值时,其他排序值也需要相应调整)显得不够灵活。本文介绍如何使用 AOP
切面与 MyBatis-Plus
结合,实现自定义排序注解,以简化排序逻辑的管理。
也可以不用 mybatis-plus
用其他技术去实现也可以例如 Jdbc Template
,AnyLine MDM
等 .
注意
一定要使用 @Transactional
开启事务管理
一. 使用示例
二. 实现代码
1. 枚举
/**
* 重新排序操作类型
*
* @author 鲁子狄
**/
@Getter
@AllArgsConstructor
public enum ReorderOrderOperaEnum {
/**
* 新增操作
*/
INSERT("insert"),
/**
* 更新操作
*/
UPDATE("update"),
/**
* 删除操作
*/
DELETE("delete"),
/**
* 默认操作
*/
DEFAULT("default");
private final String value;
}
2. 自定义注解
/**
* 重新排序注解
*
* @author 鲁子狄
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReorderOrder {
/**
* 操作类型
*/
ReorderOrderOperaEnum opera() ;
/**
* mapper class
*/
Class<? extends BaseMapperPlus> baseMapper() ;
/**
* 重新排序字段
*/
String orderField() default "order_num";
/**
* 父级字段
*/
String parentField() default "parent_id";
/**
* 主键字段
*/
String idField() default "id";
/**
* 逻辑删除字段
*/
String deletedField() default "del_flag";
/**
* 逻辑删除字段值
*/
int deletedValue() default 2;
}
3. 切面逻辑
/**
* 修改排序字段切面
*
* @author 鲁子狄
**/
@Slf4j
@Aspect
@Component
@SuppressWarnings("unchecked")
public class ReorderOrderAspect {
// 非静态变量,用于在切面中存储和管理状态信息
private ReorderOrderOperaEnum operationType;
private BaseMapperPlus<?, ?> baseMapper;
private Class<?> entityType;
private String orderField;
private String parentField;
private String idField;
private String deletedField;
private Integer deletedValue;
private String entityParentId;
private String entityOrderField;
/**
* 向前移动策略
*/
private final OrderChangeStrategy MOVE_FORWARD = (updateWrapper, newOrder, currentOrder) ->
updateWrapper.ge(orderField, newOrder).lt(orderField, currentOrder);
/**
* 向后移动策略
*/
private final OrderChangeStrategy MOVE_BACKWARD = (updateWrapper, newOrder, currentOrder) ->
updateWrapper.gt(orderField, currentOrder).le(orderField, newOrder);
/**
* 策略接口
*/
@FunctionalInterface
interface OrderChangeStrategy {
void apply(UpdateWrapper<?> updateWrapper, int newOrder, int currentOrder);
}
/**
* 环绕拦截
*
* @param joinPoint 切点
* @param reorderOrder 注解
* @return Object
* @throws Throwable 错误
*/
@Around("@annotation(reorderOrder)")
public Object doAround(ProceedingJoinPoint joinPoint, ReorderOrder reorderOrder) throws Throwable {
// 设置变量值
setVariableValue(reorderOrder);
// 根据业务类型处理不同的操作
return switch (operationType) {
case INSERT ->
// 插入操作
handleInsert(joinPoint);
case UPDATE ->
// 更新操作
handleUpdate(joinPoint);
case DELETE ->
// 删除操作
handleDelete(joinPoint);
default ->
// 默认情况下执行原方法
joinPoint.proceed();
};
}
/**
* 设置变量值
*
* @param reorderOrder 注解
*/
private void setVariableValue(ReorderOrder reorderOrder) {
operationType = reorderOrder.opera();
baseMapper = SpringUtil.getBean(reorderOrder.baseMapper());
entityType = baseMapper.currentModelClass();
orderField = reorderOrder.orderField();
parentField = reorderOrder.parentField();
idField = reorderOrder.idField();
deletedField = reorderOrder.deletedField();
deletedValue = reorderOrder.deletedValue();
entityParentId = StringUtils.toCamelCase(reorderOrder.parentField());
entityOrderField = StringUtils.toCamelCase(reorderOrder.orderField());
}
/**
* handleInsert 处理插入操作
*
* @param joinPoint 切点
*/
private Object handleInsert(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行原方法
Object result = joinPoint.proceed();
if (result == null) {
return null;
}
// 获取方法参数
Object arg = joinPoint.getArgs()[0];
Object parentId = getField(arg, entityParentId);
// 获取新的排序值
Integer newOrder = getField(arg, entityOrderField);
// 获取最大排序值
Integer maxOrderNum = getMaxOrCurrentOrderNum(parentId, null);
if (newOrder != null && newOrder <= maxOrderNum) {
// 确定排序变化方向
int orderChange = determineOrderChange(newOrder, null);
// 构建更新包装器
UpdateWrapper updateWrapper = buildUpdateWrapper(null, newOrder, orderChange, parentId, (Long) result);
// 执行更新
baseMapper.update(updateWrapper);
}
return result;
}
/**
* handleUpdate 处理更新操作
*
* @param joinPoint 切点
*/
private Object handleUpdate(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法参数
Object arg = joinPoint.getArgs()[0];
// 获取父ID
Object parentId = getField(arg, entityParentId);
// 获取新的排序值
Integer newOrder = getField(arg, entityOrderField);
// 获取当前数据 id
Object id = getField(arg, idField);
// 获取当前排序值
Integer currentOrder = getMaxOrCurrentOrderNum(parentId, id);
// 判断新旧排序值是否相等
if (newOrder != null && !newOrder.equals(currentOrder)) {
// 确定排序变化方向
int orderChange = determineOrderChange(newOrder, currentOrder);
// 构建更新包装器
UpdateWrapper updateWrapper = buildUpdateWrapper(currentOrder, newOrder, orderChange, parentId, id);
// 执行更新
baseMapper.update(updateWrapper);
}
return joinPoint.proceed();
}
/**
* getMaxOrCurrentOrderNum 获取排序最大值或当前排序值
*
* @param parentId 父ID
* @param id id
* @return {@link java.lang.Integer}
*/
private Integer getMaxOrCurrentOrderNum(Object parentId, Object id) {
QueryWrapper queryWrapper = new QueryWrapper<>(entityType);
if (parentId != null) {
queryWrapper.eq(parentField, parentId);
}
if (id != null) {
// 添加主键ID条件
queryWrapper.eq(idField, id);
// 选择排序字段
queryWrapper.select(orderField);
} else {
// 选择最大排序值
queryWrapper.select(String.format("MAX(%s)", orderField));
}
List<?> list = baseMapper.selectObjs(queryWrapper);
return list.isEmpty() ? 0 : (Integer) list.get(0);
}
/**
* buildUpdateWrapper 构建更新包装器
*
* @param currentOrder 当前的排序值(如果是插入操作,则为 null )
* @param newOrder 新的排序值
* @param orderChange 排序变化的方向
* @param parentId 父级id
* @param id id
* @return {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<T>}
*/
private <T> UpdateWrapper<T> buildUpdateWrapper(Integer currentOrder, int newOrder, int orderChange, Object parentId, Object id) {
UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
if (parentId != null) {
// 添加父ID条件
updateWrapper.eq(parentField, parentId);
}
if (currentOrder != null) {
// 修改操作
OrderChangeStrategy strategy = orderChange == 1 ? MOVE_FORWARD : MOVE_BACKWARD;
strategy.apply(updateWrapper, newOrder, currentOrder);
} else {
// 新增操作
updateWrapper.ge(orderField, newOrder);
}
updateWrapper.ne(idField, id);
// 更新SQL片段
updateWrapper.setSql(true, String.format("%s = %s + %s", orderField, orderField, orderChange));
return updateWrapper;
}
/**
* determineOrderChange 确定排序变化的方向
*
* @param newOrder 新的排序值
* @param currentOrder 当前的排序值
* @return {@link int}
*/
private int determineOrderChange(int newOrder, Integer currentOrder) {
return switch (operationType) {
// 插入操作,仅存在+1或不变
case INSERT -> 1;
// 删除操作,仅存在-1或不变
case DELETE -> -1;
// 更新操作,取决于新的排序位置是变大还是变小
case UPDATE -> {
boolean isOrderDecreasing = newOrder < currentOrder;
// 返回变化方向
yield isOrderDecreasing ? 1 : -1;
}
default -> throw new IllegalArgumentException("请检查传入操作类型参数.");
};
}
/**
* handleDelete 处理删除操作
*
* @param joinPoint 切点
*/
private Object handleDelete(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行原方法
Object result = joinPoint.proceed();
if (result == null) {
return null;
}
// 获取方法参数
Collection<Long> ids = (Collection<Long>) joinPoint.getArgs()[0];
// 转换为列表
List<Long> idList = ids.stream().toList();
// 获取要删除的数据
List<?> deleteList = getDeleteList(idList);
if (CollUtil.isEmpty(deleteList)) {
return null;
}
List<Integer> deletedOrders = deleteList.stream()
// 获取排序值
.map(o -> (Integer) getField(o, entityOrderField))
.filter(Objects::nonNull)
.toList();
// 获取最小排序值
int minDeletedOrder = Collections.min(deletedOrders);
// 获取父ID
Object parentId = getField(deleteList.get(0), entityParentId);
// 更新排序值
deleteUpdateOrderNum(minDeletedOrder, parentId);
return result;
}
/**
* getDeleteList 获取删除的数据
*
* @param ids id 集合
* @return {@link java.util.List<?>}
*/
private List<?> getDeleteList(List<Long> ids) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.in(idField, ids);
String sql = String.format(
"OR (%s IN (%s) AND %s = %d)",
idField,
StringUtils.join(ids, ", "),
deletedField,
deletedValue
);
queryWrapper.last(sql);
return baseMapper.selectList(queryWrapper);
}
/**
* deleteUpdateOrderNum 删除操作时更新排序字段
*
* @param minDeletedOrder 最小删除排序值
* @param parentId 父ID
*/
private void deleteUpdateOrderNum(int minDeletedOrder, Object parentId) {
// 1. 查询需要更新的记录
QueryWrapper queryWrapper = new QueryWrapper<>();
int rankNum = minDeletedOrder - 1;
queryWrapper.select(idField, orderField,
rankNum + " + RANK() OVER (ORDER BY " + orderField + ") AS rank_before_delete");
queryWrapper.gt(orderField, minDeletedOrder);
if (parentId != null) {
queryWrapper.eq(parentField, parentId);
}
// 获取结果集
List<Map<String, Object>> resultMaps = baseMapper.selectMaps(queryWrapper);
if (CollectionUtils.isEmpty(resultMaps)) {
return;
}
// 2. 提取结果集中每条记录的id和rank_before_delete
Map<Long, Integer> newOrderNumMap = resultMaps.stream()
.collect(Collectors.toMap(
map -> ((Number) map.get(idField)).longValue(),
map -> ((Number) map.get("rank_before_delete")).intValue()
));
// 3. 构建动态CASE语句
StringBuilder sqlCase = buildCaseStatement(newOrderNumMap);
// 4. 创建UpdateWrapper对象来更新order_num字段
UpdateWrapper updateWrapper = new UpdateWrapper<>();
updateWrapper.ge(orderField, minDeletedOrder);
updateWrapper.setSql(orderField + " = " + sqlCase);
if (parentId != null) {
updateWrapper.eq(parentField, parentId);
}
// 5. 执行更新操作
baseMapper.update(updateWrapper);
}
/**
* 构建动态CASE语句
*
* @param newOrderNumMap 新的排序值映射
* @return CASE语句字符串
*/
private StringBuilder buildCaseStatement(Map<Long, Integer> newOrderNumMap) {
StringBuilder sqlCase = new StringBuilder("CASE ");
boolean isFirst = true;
for (Map.Entry<Long, Integer> entry : newOrderNumMap.entrySet()) {
if (!isFirst) {
sqlCase.append(" ");
} else {
isFirst = false;
}
sqlCase.append("WHEN ").append(idField).append(" = ").append(entry.getKey()).append(" THEN ").append(entry.getValue());
}
sqlCase.append(" ELSE ").append(orderField).append(" END");
return sqlCase;
}
/**
* getField 获取指定对象的指定字段的值
*
* @param obj 对象
* @param fieldName 字段名
* @return {@link T}
*/
private <T> T getField(Object obj, String fieldName) {
// 特殊处理的字段集合
Set<String> specialFields = Set.of(entityParentId);
// 获取指定对象的指定字段的值
return FieldUtils.getField(obj, fieldName, entityType, specialFields);
}
}
4. 帮助类
/**
* 字段工具类
*
* @author 鲁子狄
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("unchecked")
public class FieldUtils {
/**
* 获取指定对象的指定字段的值。
*
* @param obj 对象
* @param fieldName 字段名
* @param entityType 实体类型
* @param specialFields 特殊处理的字段集合
* @param <T> 返回类型的泛型
* @return 字段值
*/
public static <T> T getField(Object obj, String fieldName, Class<?> entityType, Set<String> specialFields) {
Class<?> clazz = obj.getClass();
// 如果对象类型不匹配,进行类型转换
if (!clazz.equals(entityType)) {
obj = MapstructUtils.convert(obj, entityType);
clazz = entityType;
}
while (clazz != null) {
try {
// 获取字段
Field field = clazz.getDeclaredField(fieldName);
// 设置访问权限
field.setAccessible(true);
// 获取字段值
return (T) field.get(obj);
} catch (NoSuchFieldException e) {
// 如果字段在特殊处理字段集合中,则返回 null
if (specialFields != null && specialFields.contains(fieldName)) {
return null;
}
// 继续向上级类中查找
clazz = clazz.getSuperclass();
} catch (IllegalAccessException e) {
throw new RuntimeException("无法访问字段 " + fieldName + ":" + e);
}
}
// 如果到达这里,说明没有找到字段
throw new NoSuchElementException("无法找到字段 " + fieldName);
}
}