春天的靴子 代理篇第一话

| 分类 MICROSERVICE  | 标签 hibernate  validator 

Data validation is integral part of any application. You will find data validation at presentation layer with the use of Javascript. Then at the server side code before processing the client data. Also data validation occurs before persisting it, to make sure it follows the correct format.

Hibernate Validator

Validation is a cross cutting task, so we should try to keep it apart from our business logic. That’s why JSR303 and JSR349 provides specification for validating a bean by using annotations. Hibernate Validator provides the reference implementation of both these bean validation specs.

It’s very easy to use Hibernate Validator and best part is that we can easily extend it and create our own custom validation annotations.

Hibernate Validator Maven Dependencies

We need to add JSR303 and Hibernate Validator dependencies to use them.

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.1.Final</version>
</dependency>

JSR-303 Annotations and Addtional’s

Annotations in JSR-303 spec,

// 空检查
@Null				// 被注释的元素必须为 null  
@NotNull    		// 被注释的元素必须不为 null 
// Boolean 检查
@AssertTrue     	// 被注释的元素必须为 true  
@AssertFalse    	// 被注释的元素必须为 false  
// 数值检查
@Min(value)     	// 被注释的元素必须是一个数字,其值必须大于等于value
@Max(value)     	// 被注释的元素必须是一个数字,其值必须小于等于value
@DecimalMin(value)  // 被注释的元素必须是一个数字,其值必须大于等于value 
@DecimalMax(value)  // 被注释的元素必须是一个数字,其值必须小于等于value
@Digits(integer, fraction)     // 被注释的元素必须是一个数字,其值必须在可接受的范围内,interger指定整数精度,fraction指定小数精度。
// 长度检查
@Size(max=, min=)   // 被注释的元素的大小必须在指定的范围内  
// 日期检查,日期检查还不支持java.time包里的类
@Past   			// 被注释的元素必须是一个过去的日期  
@Future     		// 被注释的元素必须是一个将来的日期  
@Pattern(regex=,flag=)  	// 被注释的元素必须符合指定的正则表达式
// 校验
@Valid 				// 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验;如果是一个map,则对其中的值部分进行校验。

Hibernate additinal annotions

@NotBlank(message =)	// 验证字符串非null,且长度必须大于0  
@Email  				// 被注释的元素必须是电子邮箱地址  
@Length(min=,max=)		// 被注释的字符串的大小必须在指定的范围内  
@NotEmpty				// 被注释的字符串的必须非空  
@Range(min=,max=,message=)		// 被注释的元素必须在合适的范围内  

Hibernate Validation Example

Let’s create a simple class and add some validations to it.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

	@Min(value=1, groups=EmpIdCheck.class)
	private int id;
	
	@NotNull(message="Name cannot be null")
	@Size(min=5, max=30)
	private String name;
	
	@Email
	private String email;
	
	@CreditCardNumber
	private String creditCardNumber;
}

We should avoid using implementation specific annotations for loose coupling. But hibernate validator provides some very good validation annotations such as @Email and @CreditCardNumber.

We can also provide custom error message to use with any validation. If there are no message defined then hibernate built-in error message will be used.

We can assign groups to any validation, this can be useful to validate a set of fields only. For example, if I just need to validate the Employee id field, I can use EmpIdCheck group. For this we need to define a class/interface.

interface EmpIdCheck {}

We will look it’s usage in the test program later on.

Hibernate Validator Custom Error Messages

We can define our custom error messages too, all we need is to have ValidationMessages.propertiesfile in the classpath.

#Hibernate Bug for @CreditCardNumber Workaround - https://hibernate.atlassian.net/browse/HV-881
org.hibernate.validator.constraints.LuhnCheck.message=The check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed.

org.hibernate.validator.constraints.Email.message=Invalid email address

These property files also support localization, in that case we need to keep file names like ValidationMessages_tr_TR.properties

The property name for message is fully classified annotation name with message at the end, you can easily figure out from above examples.

XML Based Constraints Validation

Sometimes we want validation on third party classes, then we can’t use annotations with them. In this situation, xml configuration based validation comes handy.

<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.1.xsd"
	xmlns="http://jboss.org/xml/ns/javax/validation/mapping" version="1.1">

	<default-package>com.fuqssi.model</default-package>

	<bean class="EmployeeXMLValidation" ignore-annotations="true">
		<field name="id">
			<constraint annotation="javax.validation.constraints.Min">
				<element name="value">1</element>
			</constraint>
		</field>
		<field name="name">
			<constraint annotation="javax.validation.constraints.NotNull" />
			<constraint annotation="javax.validation.constraints.Size">
				<element name="min">5</element>
				<element name="max">30</element>
			</constraint>
		</field>
		<field name="email">
			<constraint annotation="org.hibernate.validator.constraints.Email" />
		</field>
		<field name="creditCardNumber">
			<constraint annotation="org.hibernate.validator.constraints.CreditCardNumber" />
		</field>
	</bean>

default-package is used to define the base package, so that we can skip long package names.

ignore-annotations is used to tell Hibernate validator to ignore any annotations present in the class for validation purpose, only perform validations as configured in the xml file.

We can have multiple bean validations in a single file, we need to load this file to validator factory configuration.

Property level constraints

We can apply constraints on the getter methods too, we should not apply it on setter method. Also we should avoid applying constraints on both fields and getter method because then it will be validated twice.

import javax.validation.constraints.NotNull;
public class MyBean {
	private String name;
	@NotNull
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

Hibernate Validation with Inheritance

Bean validations are inherited, so if we are validating object of child class then any validations in parent class will also be executed.

import javax.validation.constraints.NotNull;
public class MyChildBean extends MyBean {
	private String data;
	@NotNull
	public String getData() {
		return data;
	}
	public void setData(String data) {
		this.data = data;
	}
}

If we will validate instance of MyChildBean, MyBean name field will also be validated.

Validation with Composition using @Valid annotation

We can use @Valid annotation incase of composition, so that it’s validations are executed.

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class AnotherBean {
	@NotNull
	@Valid
	private MyChildBean childBean;
	public MyChildBean getChildBean() {
		return childBean;
	}
	public void setChildBean(MyChildBean childBean) {
		this.childBean = childBean;
	}
}

Now if we validate AnotherBean instance, MyChildBean object will also be validated.

Hibernate Validator Example Test Program

We have seen a lot of validation scenarios, here is the test program to show the process to validate them.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Method;
import java.util.Set;

import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;

import org.hibernate.validator.HibernateValidator;

import com.journaldev.validator.hibernate.model.EmployeeXMLValidation;
import com.journaldev.validator.hibernate.model.ParamValidationBean;

public class ValidatorTest {

	public static void main(String[] args) throws FileNotFoundException, NoSuchMethodException, SecurityException {
		
		//Getting Validator instance with Annotations
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
		Validator validator = validatorFactory.getValidator();
		
		//If there are multiple JSR303 implementations in classpath
		//we can get HibernateValidator specifically too
		ValidatorFactory hibernateVF = Validation.byProvider(HibernateValidator.class)
									.configure().buildValidatorFactory();
		
		System.out.println("\nSimple field level validation example");
		Employee emp = new Employee(-1, "Name","email","123");
		Set<ConstraintViolation<Employee>> validationErrors = validator.validate(emp);
		
		if(!validationErrors.isEmpty()){
			for(ConstraintViolation<Employee> error : validationErrors){
				System.out.println(error.getMessageTemplate()+"::"+error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}
		
		System.out.println("\nXML Based validation example");
		
		//XML Based validation
		Configuration<?> config = Validation.byDefaultProvider().configure();
		config.addMapping(new FileInputStream("employeeXMLValidation.xml"));
		ValidatorFactory validatorFactory1 = config.buildValidatorFactory();
		Validator validator1 = validatorFactory1.getValidator();
		
		EmployeeXMLValidation emp1 = new EmployeeXMLValidation(10, "Name","email","123");
		
		Set<ConstraintViolation<EmployeeXMLValidation>> validationErrors1 = validator1.validate(emp1);
		
		if(!validationErrors1.isEmpty()){
			for(ConstraintViolation<EmployeeXMLValidation> error : validationErrors1){
				System.out.println(error.getMessageTemplate()+"::"+error.getInvalidValue()+"::"+ error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}
		
		System.out.println("\nValidation Group example");
		validationErrors = validator.validate(emp, EmpIdCheck.class);
		
		if(!validationErrors.isEmpty()){
			for(ConstraintViolation<Employee> error : validationErrors){
				System.out.println(error.getMessageTemplate()+"::"+error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}
		
		System.out.println("\nValidation with inheritance example");

		//Validation inheritance and Property-level constraints example
		MyChildBean childBean = new MyChildBean();
		Set<ConstraintViolation<MyChildBean>> validationInheritanceErrors = validator.validate(childBean);
		
		if(!validationInheritanceErrors.isEmpty()){
			for(ConstraintViolation<MyChildBean> error : validationInheritanceErrors){
				System.out.println(error.getMessageTemplate()+"::"+error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}
		
		System.out.println("\nValidation in composition using @Valid annotation");

		//@Valid annotation - validation in composition example
		AnotherBean compositionBean = new AnotherBean();
		compositionBean.setChildBean(new MyChildBean());
		Set<ConstraintViolation<AnotherBean>> validationCompositionErrors = validator.validate(compositionBean);
		
		if(!validationCompositionErrors.isEmpty()){
			for(ConstraintViolation<AnotherBean> error : validationCompositionErrors){
				System.out.println(error.getMessageTemplate()+"::"+error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}
		
		System.out.println("\nParameter validation example");
		ParamValidationBean paramValidationBean = new ParamValidationBean("Pankaj");
		Method method = ParamValidationBean.class.getMethod("printData", String.class);
		Object[] params = {"1234"};
		ExecutableValidator executableValidator = validator.forExecutables();
		Set<ConstraintViolation<ParamValidationBean>> violations = 
				executableValidator.validateParameters(paramValidationBean, method, params);
		if(!violations.isEmpty()){
			for(ConstraintViolation<ParamValidationBean> error : violations){
				System.out.println(error.getMessageTemplate()+"::"+error.getPropertyPath()+"::"+error.getMessage());
				
			}
		}	
	}
}

Important points from above hibernate validator test program are:

  1. Validator instance is thread safe, so we can cache it and reuse it.
  2. If there are multiple JSR303 implementation present, then we can get the Hibernate Validator instance using Validation.byProvider() method.
  3. Notice the use of validation of a group, it’s validating only the fields that are part of the group.
  4. ExecutableValidator is used to validate the parameters of a method.
  5. ExecutableValidator provide methods to validate constructor parameters and return values too, these methods are validateReturnValue(), validateConstructorParameters() and validateConstructorReturnValue()

上一篇     下一篇