Java Custom Annotation Verification Specified Fields Correspond to Database Content Repetition

I. Preface

In some scenarios, we need to verify whether the code is duplicated, whether the account number is duplicated, whether the ID number is duplicated, etc. Codes like validation are as follows: So is there any way to solve this similar amount of duplicate code?

We can achieve this by customizing annotation validation, as follows by adding a custom annotation @Field RepeatValidator (field = resources), message = menu code duplication on the entity class. "" Let's start with the code.~

Two, implementation

Basic environment:
  1. javax.validation.validation-api
  2. org.hibernate.hibernate-validator

Spring Boot environment has been automatically included in spring-boot-starter-web, if not because of the version, you can go to the maven warehouse search manual introduction to the project use.

The edition of spring boot is: 2.1.7

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
[Note: The minor edition is based on MyBatis-Plus architecture. Other architectures are slightly different. The implementation of this paper can be referred to]

1. Custom Annotation @Field RepeatValidator

// Meta-annotations: Explain other common labels [Retention, @Documented, @Target, @Inherited, @Repeatable]
@Documented
/**
 * Specify the life cycle:
 *      RetentionPolicy.SOURCE Annotations are reserved only at the source stage and will be discarded and ignored when compilers compile.
 *      RetentionPolicy.CLASS Annotations are reserved for compilation only, and they are not loaded into the JVM.
 *      RetentionPolicy.RUNTIME Annotations can be retained until the program runs, and they are loaded into the JVM, so they can be retrieved when the program runs.
 */
@Retention(RetentionPolicy.RUNTIME)
/**
 * Specify where annotations are used:
 *      ElementType.ANNOTATION_TYPE You can annotate a note.
 *      ElementType.CONSTRUCTOR The construction method can be annotated.
 *      ElementType.FIELD Attributes can be annotated
 *      ElementType.LOCAL_VARIABLE Local variables can be annotated
 *      ElementType.METHOD Method can be annotated
 *      ElementType.PACKAGE You can annotate a package
 *      ElementType.PARAMETER The parameters in a method can be annotated.
 *      ElementType.TYPE You can annotate a type, such as classes, interfaces, enumerations
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
@Constraint(validatedBy = FieldRepeatValidatorClass.class)
//@ Repeatable (LinkVals. class) (repeatable annotation of the same field, or class, supported after Java 1.8)
public @interface FieldRepeatValidator {

    /**
     * Entity class id field - default to id (this value is not available)
     * @return
     */
    String id() default "id";;

    /**
     * Annotation Properties - Corresponding Check Fields
     * @return
     */
    String field();

    /**
     * Default error message
     * @return
     */
    String message() default "Duplicate field content!";

    Class<?>[] groups() default {};
    Class<? extends Payload>[]  payload() default {};

}

2. @Field RepeatValidator Annotation Interface Implementation Class

/**
 *  <p> FieldRepeatValidator Annotation interface implementation class</p>
 *
 * @description :
 *        Tip 01: ConstraintValidator interface must be implemented
 *     Tip 02: After implementing the ConstraintValidator interface, spring manages beans of this kind even without configuring beans
 *     Tip 03: Dependency injection of other beans into classes that implement the ConstraintValidator interface
 *     Tip 04: After implementing the ConstraintValidator interface, the initialize and isValid methods must be rewritten.
 *              initialize The main method is initialization, which is usually used to obtain the attribute values of custom annotations.
 *              isValid The method mainly carries on the check logic, returns true to indicate that the check passes, returns false to indicate that the check fails, and usually carries on the check judgment according to the annotation attribute value and the entity class attribute value [Object: the attribute value of the check field]
 * @author : zhengqing
 * @date : 2019/9/10 9:22
 */
public class FieldRepeatValidatorClass implements ConstraintValidator<FieldRepeatValidator, Object> {

    private String id;
    private String field;
    private String message;

    @Override
    public void initialize(FieldRepeatValidator fieldRepeatValidator) {
        this.id = fieldRepeatValidator.id();
        this.field = fieldRepeatValidator.field();
        this.message = fieldRepeatValidator.message();
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        return FieldRepeatValidatorUtils.fieldRepeat(id, field, o, message);
    }

}

3. Tool Class for Repeated Judgment of Database Field Content

public class FieldRepeatValidatorUtils {

    /**
     * Entity class id field
     */
    private static String id;
    /**
     * Entity class id field value
     */
    private static Integer idValue;
    /**
     * Check field
     */
    private static String field;
    /**
     * Check field values - strings, numbers, objects...
     */
    private static Object fieldValue;
    /**
     * Check field - corresponding database field
     */
    private static String db_field;
    /**
     * Object value of entity class
     */
    private static Object object;

    /**
     * In the later stage of TODO, if it is necessary to check whether the same field is duplicated or not, do `field', or `split', if the id is not the only consideration to pass the value to judge or take the second field value of field to get the id.
     *
     * @param field: Check field
     * @param object: Object data
     * @param message: Callback to the front-end prompt message
     * @return: boolean
     */
    public static boolean fieldRepeat(String id, String field, Object object, String message) {
        // Use the middle static forName() method of the Class class to obtain the Class object corresponding to the string; className: must be the name of the interface or Class
        // The static method forName() calls the startup class loader - > loads a class xx - > instantiates - > to achieve decoupling flexibility
//        Object object = Class.forName(className).newInstance();

        FieldRepeatValidatorUtils.id = id;
        FieldRepeatValidatorUtils.field = field;
        FieldRepeatValidatorUtils.object = object;
        getFieldValue();

        // _Check whether the content of the field is repetitive
        // Factory pattern + ar dynamic grammar
        BaseEntity entity = (BaseEntity) object;
//        List list = entity.selectPage( new Page<>( 1,1 ), new EntityWrapper().eq( field, fieldValue ) ).getRecords();
        List list = entity.selectList( new EntityWrapper().eq( db_field, fieldValue ) );
        // If the data repeatedly returns false - > then returns the custom error message to the front end
        if ( idValue == null ){
            if ( !CollectionUtils.isEmpty( list ) ){
                throw new MyException( message );
            }
        } else {
            if ( !CollectionUtils.isEmpty( list ) ){
                // fieldValueNew: Front-end input field values
                Object fieldValueNew = fieldValue;
                FieldRepeatValidatorUtils.object = entity.selectById( idValue );
                // Gets the check field value of the object in which the id resides - old data
                getFieldValue();
                if ( !fieldValueNew.equals( fieldValue ) || list.size() > 1 ){
                    throw new MyException( message );
                }
            }
        }
        return true;
    }

    /**
     * Get id, check field values
     */
    public static void getFieldValue(){
        // Get all the fields
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field f : fields) {
            // (2) Setting the member attribute private in the object to be readable
            f.setAccessible(true);
            // (3) Judging whether field annotations exist or not
            if ( f.isAnnotationPresent(ApiModelProperty.class) ) {
                // (4) If it exists, get the field corresponding to the annotation and determine whether it is consistent with the field we want to verify.
                if ( f.getName().equals( field ) ){
                    try {
                        // Get its attribute value if it is consistent
                        fieldValue = f.get(object);
                        // Obtain the database field attribute corresponding to the check field. Purpose: Make ar query for mybatis-plus.
                        TableField annotation = f.getAnnotation(TableField.class);
                        db_field = annotation.value();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                // Getting id value - > function: judging whether insert or update operation
                if ( id.equals( f.getName() ) ){
                    try {
                        idValue = (Integer) f.get(object);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

4. Global exception handling

Function: Let the validation take effect, that is, if the parameter validation is not valid, the exception will be thrown. We can capture the intercept in the global exception, and then proceed with logical processing before returning to the front end.
@Slf4j
@RestControllerAdvice
public class MyGlobalExceptionHandler {

    private static final Logger LOG = LoggerFactory.getLogger(MyGlobalExceptionHandler.class);

    /**
     * Custom exception handling
     */
    @ExceptionHandler(value = MyException.class)
    public ApiResult myException(MyException be) {
        log.error("Custom exceptions:", be);
        if(be.getCode() != null){
            return ApiResult.fail(be.getCode(), be.getMessage());
        }
        return ApiResult.fail( be.getMessage() );
    }

    // Abnormal Processing of Parameter Check===========================================================================
    // MethodArgumentNotValidException is an exception in spring Boot when checking binding parameters. It needs to be handled in spring Boot and other exceptions need to be handled in ConstraintViolationException.

    /**
     * Method parameter checking
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult handleMethodArgumentNotValidException( MethodArgumentNotValidException e ) {
        log.error( "Method parameter checking:" + e.getMessage(), e );
        return ApiResult.fail( e.getBindingResult().getFieldError().getDefaultMessage() );
    }

    /**
     * ValidationException
     */
    @ExceptionHandler(ValidationException.class)
    public ApiResult handleValidationException(ValidationException e) {
        log.error( "ValidationException:", e );
        return ApiResult.fail( e.getCause().getMessage() );
    }

    /**
     * ConstraintViolationException
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ApiResult handleConstraintViolationException(ConstraintViolationException e) {
        log.error( "ValidationException:" + e.getMessage(), e );
        return ApiResult.fail( e.getMessage() );
    }
    
}

The custom exception handling code is as follows:

public class MyException extends RuntimeException {

    /**
     * Abnormal status code
     */
    private Integer code;

    public MyException(Throwable cause) {
        super(cause);
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public MyException(String message, Throwable cause) {
        super(message, cause);
    }

    public Integer getCode() {
        return code;
    }

}

3. Examples of @Field RepeatValidator annotations

1. Add the following code to the entity class

@FieldRepeatValidator(field = "resources", message = "Menu Coding Repeat!")
public class Menu extends BaseEntity { ... }

2. Add @Validated annotation to the method of controller layer.

@PostMapping(value = "/save", produces = "application/json;charset=utf-8")
    @ApiOperation(value = "Save menu ", httpMethod = "POST", response = ApiResult.class)
    public ApiResult save(@RequestBody @Validated Menu input) {
        Integer id = menuService.save(input);
        // Update authority
        shiroService.updatePermission(shiroFilterFactoryBean, null, false);
        return ApiResult.ok("Save menu successfully", id);
}

4. Some Native Annotations for Direct Use

The following original annotations Baidu once, you will find that there are many, very simple not to say much.

@ Null must be null
 @ NotNull must not be null
 @ AssertTrue must be true, supporting boolean, Boolean
 @ AssertFalse must be false, supporting boolean, Boolean
 @ Min(value) must be less than value, supporting BigDecimal, BigInteger, byte, shot, int, long and their wrapper classes
 @ Max(value) must be greater than value, supporting BigDecimal, BigInteger, byte, shot, int, long and their wrapper classes
 @ DecimalMin(value) must be less than value, supporting BigDecimal, BigInteger, CharSequence, byte, shot, int, long and their wrapper classes
 @ DecimalMax(value) must be greater than value, supporting BigDecimal, BigInteger, CharSequence, byte, shot, int, long and their wrapper classes
 @ Size(max=, min=) supports CharSequence, Collection, Map, Array
 @ Digits (integer, fraction) must be a number
 @ Negative must be a negative number
 @ Negative OrZero must be a negative number or 0
 @ Positive must be a positive number
 @ PositiveOrZero must be a positive number or 0
 @ Past must be a past date
 @ PastOrPresent must be a past or current date
 @ Future must be a future date
 @ FutureOrPresent must be a future or current date
 @ Pattern(regex=,flag=) must conform to the specified regular expression
 @ NotBlank(message =) must be a non-empty string
 @ Email must be an e-mail address
 @ NotEmpty The commented string must be non-empty
... ... ... 

Five, summary

Here, let's briefly talk about the realization of Xiaobian. Firstly, we customize a comment and put it on a field or class. Aim: To get its value by reflection, and then get the value, we can do a series of our own business operations, such as querying the corresponding database data with more field attributes and attribute values, and then verifying it, if it does not conform to our own logic. We throw an exception to the globally unified exception class to process the error message, and then return it to the front end for processing. The general idea is this. It's easy to implement. There should be some comments in the code. I believe it won't be too difficult to understand.

Finally, the source code of the small edition is given for your reference.

Case Demo

Keywords: Programming Attribute Spring Database Hibernate

Added by tidalwave on Mon, 23 Sep 2019 09:58:49 +0300