一: 使用場景
在開發(fā)過程中,前端給后端傳遞集合,并且需要保證集合的實體類中的某些字段必須是惟一的,不能重復。
傳遞的集合:
private List<User> userInfoList;
集合對應的實體類:
@Data
public class User {
private int id;
private String name;
private String card;
}
如果我們要保證傳遞的name或者card必須是唯一的,不能重復,應該如何實現(xiàn)呢,此時可以通過自定義注解的方式實現(xiàn)。
二: 定義FieldUniqueValid注解
2.1 @FieldUniqueValid
/**
* 該注解用于校驗List集合實體類當中的某些字段的唯一性
* <p>
* 條件表達式支持使用"$parent."獲取父節(jié)點屬性(實體內不能使用除List集合外的其他集合類型,例如Set等)
* <ul>
* <li>標記在字段上:用于指定單個或多個字段,fields需填寫</li>
* </ul>
* @author ikun
* @date 2023.07.27
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldUniqueValidator.class)
public @interface FieldUniqueValid {
//需要進行唯一校驗的字段
String[] fieldsCode() default{};
String[] fieldsName() default{};
String message() default "[fieldName]列[index]行數(shù)據(jù)重復";
//不能有默認值,報錯:contains Constraint annotation, but the groups parameter default value is not the empty array
Class<?>[] groups() default {};//在那種組中使用
Class<? extends Payload>[] payload() default {};
}
2.2 注解說明
@Documented
@Document 是 java 在生成文檔,是否顯示注解的開關。
@Target(ElementType.FIELD)
ElementType.FIELD:該注解只能聲明在一個類的字段前。
2.3 @Constraint 注解介紹
@Constraint注解是Java Bean Validation框架中的一個注解,用于自定義約束注解,即自定義校驗規(guī)則。
通過在自定義注解上添加@Constraint注解,可以將該注解標記為一個自定義約束注解。同時,需要指定一個實現(xiàn)了ConstraintValidator接口的驗證器類,用于驗證該注解所標記的字段或參數(shù)是否符合自定義的校驗規(guī)則。
@Constraint注解有以下屬性:
-
validatedBy:用于指定實現(xiàn)了ConstraintValidator接口的驗證器類。該屬性的值是一個Class對象數(shù)組,可以指定多個驗證器類。
-
message:用于指定當校驗失敗時,所返回的錯誤信息。可以使用占位符{},在校驗器中使用具體的參數(shù)替換。
-
groups:用于指定分組,即根據(jù)不同的分組應用不同的校驗規(guī)則。
-
payload:用于指定元數(shù)據(jù),即可以通過該屬性傳遞一些額外的驗證信息。
使用@Constraint注解,可以通過自定義注解的方式,為字段或參數(shù)添加自定義的校驗規(guī)則,并實現(xiàn)校驗邏輯。這樣,在進行參數(shù)校驗時,可以方便地通過注解的方式來調用自定義的校驗規(guī)則。
2.4 @FieldUniqueValid注解使用
@FieldUniqueValid(fieldsCode = {"name,card"}, fieldsName = {"姓名,身份證號"})
private List<User> userInfoList;
- fieldsCode :需要校驗的字段
- fieldsName :校驗字段對應的名稱
三:自定義FieldUniqueValidator校驗類
@Slf4j
public class FieldUniqueValidator implements ConstraintValidator<FieldUniqueValid, Iterable<?>> {
private String[] fieldsCode;
private String[] fieldsName;
/**
* 數(shù)據(jù)有重復的字段名稱
*/
private static final String FIELD_NAME= "[fieldName]";
/**
* 相同元素下標集合
*/
private static final String INDEX = "[index]";
/**
* 初始化參數(shù)
* @param constraintAnnotation 注解的值
*/
@Override
public void initialize(FieldUniqueValid constraintAnnotation) {
fieldsCode = constraintAnnotation.fieldsCode();
fieldsName = constraintAnnotation.fieldsName();
}
@Override
public boolean isValid(Iterable<?> value, ConstraintValidatorContext context) {
//如果沒有配置校驗字段信息,則直接通過
if(fieldsCode.length == 0 && fieldsName.length == 0){
return true;
}
if(fieldsCode.length != fieldsName.length){
throw new RuntimeException("@FieldUniqueValid注解所對應的fieldsCode和fieldsName無法相互映射");
}
if(value == null){
throw new RuntimeException("@FieldUniqueValid注解所在的集合為空,無法判斷");
}
for (int i = 0; i < fieldsCode.length; i++) {
List<Object> list = new ArrayList<>();
Iterator<?> iterator = value.iterator();
long index;
for (index = 0; iterator.hasNext(); index++) {
Object fieldValue = null;
try {
//forceAccess = true,屬性是私有的,需要設置為true打開權限
fieldValue = FieldUtils.readField(iterator.next(),fieldsCode[i],true);
} catch (IllegalAccessException e) {
log.error(fieldsName[i] + "轉化失敗,無法進行校驗", e);
}
list.add(fieldValue);
}
//去重后的總數(shù)
long count = list.stream().distinct().count();
//去重之前和去重以后進行比較
if(count < index){
//返回重復元素下標集合
String sameIndex = getListSameIndex(list);
String defaultConstraintViolation = context.getDefaultConstraintMessageTemplate();
context.disableDefaultConstraintViolation();
String convertedConstraintViolation = defaultConstraintViolation.replace(FIELD_NAME, fieldsName[i]).replace(INDEX, sameIndex);
context.buildConstraintViolationWithTemplate(convertedConstraintViolation).addConstraintViolation();
return false;
}
}
return true;
}
}
3.1 實現(xiàn)ConstraintValidator
ConstraintValidator<FieldUniqueValid, Iterable<?>>:
- FieldUniqueValid:需要校驗的注解,就是我們自定義的
- Iterable<?>:前端傳遞list的類型,此時用Iterable是因為數(shù)據(jù)支持list和set集合
3.2 重寫initialize方法
可以從onstraintAnnotation參數(shù)中獲取fieldsCode、fieldsName里面的參數(shù)。主要作用就是將注解的參數(shù)進行初始化
3.3 重寫isValid方法
Iterable<?> value, ConstraintValidatorContext context
- value:可以獲取到傳遞的集合數(shù)據(jù)
- context:獲取注解上的message信息
3.4 獲取list集合重復數(shù)據(jù)的下標
/**
* 集合【List】找出list中重復元素的下標(顯示下標所在位置)
* @param list
*/
public static String getListSameIndex(List<?> list){
List<Object> same = new ArrayList<>();
List<?> collect = list.stream().distinct().collect(Collectors.toList());
if(collect.size() == list.size()){
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i <collect.size(); i++) {
for (int j = 0; j < list.size(); j++) {
if (list.get(j).equals(collect.get(i))){
same.add(j+1);
}
}
if (same.size() > 1){
sb.append(same).append("、");
}
same.clear();
}
return sb.substring(0, sb.toString().lastIndexOf("、"));
}
3.5 思路
首先獲取到集合的數(shù)據(jù),然后通過反射,用循環(huán)遍歷獲取到name字段的list數(shù)據(jù),然后去重。將去重前后的list進行比較。如果長度變化了則說明有重復數(shù)據(jù)。此時返回false。然后我們我們通過getListSameIndex方法獲取到list重復數(shù)據(jù)的下標然后替換[index]。
3.6 測試
3.6.1 前端傳遞參數(shù),需要進行唯一性校驗的字段
文章來源:http://www.zghlxwxcb.cn/news/detail-627575.html
3.6.2 message提示
文章來源地址http://www.zghlxwxcb.cn/news/detail-627575.html
到了這里,關于Java自定義校驗注解實現(xiàn)List、set集合字段唯一性校驗的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!