AOP数据唯一性校验
数据唯一性校验
介绍
在新增或修改操作的时候,我们经常需要校验数据是否已经存在,每次都是同样的逻辑,无非就是表名和查询条件的字段名不相同,代码显得很冗余。
AOP校验数据在新增或修改是否已经存在:
- 注解参数:表名,字段列表,新增还是修改,响应信息
- 新增:select count(*) from 表名 where 遍历字段列表 = 参数列表
- 修改:select count(*) from 表面 where 遍历字段列表 = 参数列表 and id != 修改数据id
通过切面拼接表名和查询条件,执行查询,如果返回结果大于0,则直接响应信息。
注解
检查数据唯一性注解:
1 2 3 4 5 6 7 8 9 10 11
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CheckUnique { String table() default "";
CheckType type() default CheckType.ADD;
String message() default "数据已存在";
KeyValue[] keyValues(); }
|
子注解:用于数据库字段和对象属性名映射
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface KeyValue {
String value();
String key() default "";
boolean isPK() default false; }
|
枚举类:用于区分当前为新增还是修改操作
1 2 3 4 5 6 7 8 9 10 11 12
| public enum CheckType {
ADD,
UPDATE, }
|
切面
如果项目使用了Mybatis-Plus框架,则可以结合@TableName
和@TableId
注解使用,注解参数可以省略表名和主键。
该切面使用了工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
@Aspect @Component public class CheckUniqueAspect {
@Autowired private GeneralMapper generalMapper; @Before("@annotation(checkUnique)") public void checkDatabaseForExistence(JoinPoint joinPoint, CheckUnique checkUnique) {
KeyValue[] keyValues = checkUnique.keyValues(); if (keyValues.length == 0) { throw new IllegalArgumentException("至少包含一个查询条件!"); }
Object[] args = joinPoint.getArgs(); if (args.length == 0) { throw new IllegalArgumentException("方法应该至少有一个参数!"); } Object entity = args[0]; Class<?> entityClass = entity.getClass(); HashMap<String, Object> map = new HashMap<>(); if (checkUnique.type() == CheckType.UPDATE) { boolean hasPrimaryKey = Arrays.stream(keyValues).anyMatch(KeyValue::isPK); if (!hasPrimaryKey) { Field idField = findFieldWithAnnotation(entityClass, TableId.class); String idFieldName = idField.getName(); String underFieldName = StringUtils.toUnderScoreCase(idFieldName); idField.setAccessible(true); try { Object idValue = idField.get(entity); SqlUtil.filterKeyword(idValue.toString());
map.put(underFieldName, idValue); } catch (IllegalAccessException e) { e.printStackTrace(); throw new IllegalArgumentException("修改类型注解数组需要包含主键!"); } } }
String table = checkUnique.table(); if (StringUtils.isEmpty(table)) { if (entityClass.isAnnotationPresent(TableName.class)) { TableName annotation = entityClass.getAnnotation(TableName.class); table = annotation.value(); } else { throw new IllegalArgumentException("需要给定表名!"); } }
StringBuilder queryBuilder = new StringBuilder(); for (KeyValue keyValue : keyValues) {
String valueExpr = keyValue.value(); if (StringUtils.isEmpty(valueExpr)) { throw new IllegalArgumentException("@KeyValue注解参数不能为空!"); }
String[] parts = valueExpr.split("\\."); String fieldName = parts.length == 2 ? parts[1] : parts[0];
String key = StringUtils.isEmpty(keyValue.key()) ? fieldName : keyValue.key();
key = StringUtils.toUnderScoreCase(key);
Object value = getValue(entity, fieldName);
SqlUtil.filterKeyword(value.toString());
if (ObjectUtil.isEmpty(value)) { throw new IllegalArgumentException("属性值不能为空!"); }
if (keyValue.isPK()) { map.put(key, value); } else { queryBuilder.append(key).append(" = ").append("'").append(value).append("'"); } if (keyValues.length > 1 && !keyValues[keyValues.length - 1].equals(keyValue)) { queryBuilder.append(" AND "); } }
if (!map.isEmpty()) { for (Map.Entry<String, Object> entry : map.entrySet()) { queryBuilder.append(" AND "); queryBuilder.append(entry.getKey()).append(" != ").append("'").append(entry.getValue()).append("'"); } }
String query = queryBuilder.toString(); System.out.println("==================" + query + "===================="); int count = generalMapper.getCount(table, query); if (count > 0) { throw new RuntimeException(checkUnique.message()); }
}
private Object getValue(Object entity, String fieldName) {
if (StringUtils.isEmpty(fieldName)) { throw new IllegalArgumentException("无效的表达格式: " + fieldName); } Field field = findField(entity.getClass(), StringUtils.toCamelCase(fieldName));
if (!field.isAccessible()) { field.setAccessible(true); }
try { Object value = field.get(entity); if (field.getType() == Date.class) { value = DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", (Date) value); } return value; } catch (IllegalAccessException e) { throw new RuntimeException("从访问字段失败: " + fieldName); } }
private Field findField(Class<?> clazz, String fieldName) { if (clazz == null) { throw new IllegalArgumentException("找不到字段: " + fieldName); } try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { return findField(clazz.getSuperclass(), fieldName); } }
private Field findFieldWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) { Field foundField = null; for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(annotationClass)) { foundField = field; break; } } if (foundField == null) { Class<?> superclass = clazz.getSuperclass(); if (superclass != null) { foundField = findFieldWithAnnotation(superclass, annotationClass); } else { throw new IllegalArgumentException("修改类型注解数组需要包含主键!"); } } return foundField; } }
|
执行Mapper:
1 2 3
| public interface GeneralMapper { int getCount(@Param("tableName") String tableName, @Param("queryCondition") String queryCondition); }
|
Mapper.xml:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.tsy.general.mapper.GeneralMapper">
<select id="getCount" parameterType="String" resultType="Integer"> select count(*) from ${tableName} where ${queryCondition} </select>
</mapper>
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Override @CheckUnique(table = "tsy_clinometer_data", keyValues = { @KeyValue(key = "time", value = "tsyClinometerData.time"), @KeyValue(key = "clinometer_node_id", value = "tsyClinometerData.clinometerNodeId")}, message = "新增失败,该测斜数据已存在!") public int insertTsyClinometerData(TsyClinometerData tsyClinometerData) { return tsyClinometerDataMapper.insertTsyClinometerData(tsyClinometerData); }
@Override @CheckUnique(table = "tsy_clinometer_data", keyValues = { @KeyValue(key = "time", value = "tsyClinometerData.time"), @KeyValue(key = "clinometer_node_id", value = "tsyClinometerData.clinometerNodeId"), @KeyValue(key = "id", value = "tsyClinometerData.id", isPK = true)}, type = CheckType.UPDATE, message = "修改失败,该测斜数据已存在!") public int updateTsyClinometerData(TsyClinometerData tsyClinometerData) { return tsyClinometerDataMapper.updateTsyClinometerData(tsyClinometerData); }
@PutMapping @CheckUnique(keyValues = {@KeyValue("name")}, type = CheckType.UPDATE, message = "修改失败,名称已存在") public AjaxResult update(@RequestBody TsyTest tsyTest) { boolean update = tsyTestService.updateById(tsyTest); return toAjax(update); }
@PostMapping @CheckUnique(keyValues = {@KeyValue("name")}, message = "新增失败,名称已存在") public AjaxResult add(@RequestBody TsyTest tsyTest) { boolean save = tsyTestService.save(tsyTest); return toAjax(save); }
|