Dynamic DTO Validation Config Retrieved from the Database – 从数据库获取的动态DTO验证配置

最后修改: 2017年 4月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to take a look at how we can create a custom validation annotation that uses a regular expression retrieved from a database to match against the field value.

在本教程中,我们将看看如何创建一个自定义验证注解,该注解使用从数据库中检索的正则表达式来与字段值相匹配

We will use Hibernate Validator as a base implementation.

我们将使用Hibernate Validator作为基础实现。

2. Maven Dependencies

2.Maven的依赖性

For development, we will need the following dependencies:

对于开发,我们将需要以下的依赖性。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.4.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.4.0</version>
</dependency>

The latest versions of spring-boot-starter-thymeleaf, spring-boot-starter-data-jpa can be downloaded from Maven Central.

spring-boot-starter-thymeleafspring-boot-starter-data-jpa的最新版本可以从Maven中心下载。

3. Custom Validation Annotation

3.自定义验证注释

For our example, we will create a custom annotation called @ContactInfo that will validate a value against a regular expression retrieved from a database. We will then apply this validation on the contactInfo field of a POJO class called Customer.

在我们的例子中,我们将创建一个名为@ContactInfo的自定义注解,它将根据从数据库中检索的正则表达式来验证一个值。然后我们将在一个名为Customer的POJO类的contactInfo字段上应用这个验证。

To retrieve regular expressions from a database, we will model these as a ContactInfoExpression entity class.

为了从数据库中检索正则表达式,我们将把这些建模为ContactInfoExpression实体类。

3.1. Data Models and Repository

3.1.数据模型和存储库

Let’s create the Customer class with id and contactInfo fields:

让我们创建带有idcontactInfo字段的Customer类。

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String contactInfo;

    // standard constructor, getters, setters
}

Next, let’s take a look at the ContactInfoExpression class – which will hold the regular expression values in a property called pattern:

接下来,让我们看看ContactInfoExpression类–它将在一个名为pattern的属性中保存正则表达式值。

@Entity
public class ContactInfoExpression {

    @Id
    @Column(name="expression_type")
    private String type;
 
    private String pattern;

    //standard constructor, getters, setters
}

Next, let’s add a repository interface based on Spring Data to manipulate the ContactInfoExpression entities:

接下来,让我们添加一个基于Spring Data的存储库接口来操作ContactInfoExpression实体。

public interface ContactInfoExpressionRepository 
  extends Repository<ContactInfoExpression, String> {
 
    Optional<ContactInfoExpression> findById(String id);
}

3.2. Database Setup

3.2.数据库设置

For storing regular expressions, we will use an H2 in-memory database with the following persistence configuration:

为了存储正则表达式,我们将使用一个H2内存数据库,其持久性配置如下。

@EnableJpaRepositories("com.baeldung.dynamicvalidation.dao")
@EntityScan("com.baeldung.dynamicvalidation.model")
@Configuration
public class PersistenceConfig {

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2)
          .addScript("schema-expressions.sql")
          .addScript("data-expressions.sql")
          .build();
        return db;
    }
}

The two scripts mentioned are used for creating the schema and inserting the data into the contact_info_expression table:

所提到的两个脚本用于创建模式和将数据插入到contact_info_expression表中。

CREATE TABLE contact_info_expression(
  expression_type varchar(50) not null,
  pattern varchar(500) not null,
  PRIMARY KEY ( expression_type )
);

The data-expressions.sql script will add three records to represent the types email, phone, and website. These represent regular expressions for validating that value is a valid email address, a valid US phone number, or a valid URL:

data-expressions.sql脚本将添加三条记录,代表emailphonewebsite类型。这些代表正则表达式,用于验证该值是一个有效的电子邮件地址,一个有效的美国电话号码,或一个有效的URL。

insert into contact_info_expression values ('email',
  '[a-z0-9!#$%&*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?')
insert into contact_info_expression values ('phone',
  '^([0-9]( |-)?)?(\(?[0-9]{3}\)?|[0-9]{3})( |-)?([0-9]{3}( |-)?[0-9]{4}|[a-zA-Z0-9]{7})$')
insert into contact_info_expression values ('website',
  '^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$')

3.3. Creating the Custom Validator

3.3.创建自定义验证器

Let’s create the ContactInfoValidator class that contains the actual validation logic. Following Java Validation specification guidelines, the class implements the ConstraintValidator interface and overrides the isValid() method.

让我们创建包含实际验证逻辑的ContactInfoValidator类。根据Java验证规范指南,该类实现了ConstraintValidator接口并重写了isValid()方法。

This class will obtain the value of the currently used type of contact info — email, phone, or website — which is set in a property called contactInfoType, then use it to retrieve the regular expression’s value from the database:

这个类将获得当前使用的联系信息类型的值–email, phone, or website–它被设置在一个叫做contactInfoType的属性中,然后用它从数据库中检索正则表达式的值。

public class ContactInfoValidator implements ConstraintValidator<ContactInfo, String> {
    
    private static final Logger LOG = Logger.getLogger(ContactInfoValidator.class);

    @Value("${contactInfoType}")
    private String expressionType;

    private String pattern;
 
    @Autowired
    private ContactInfoExpressionRepository expressionRepository;

    @Override
    public void initialize(ContactInfo contactInfo) {
        if (StringUtils.isEmptyOrWhitespace(expressionType)) {
            LOG.error("Contact info type missing!");
        } else {
            pattern = expressionRepository.findById(expressionType)
              .map(ContactInfoExpression::getPattern).get();
        }
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (!StringUtils.isEmptyOrWhitespace(pattern)) {
            return Pattern.matches(pattern, value);
        }
        LOG.error("Contact info pattern missing!");
        return false;
    }
}

The contactInfoType property can be set in the application.properties file to one of the values email, phone or website:

contactInfoType属性可在application.properties文件中设置为emailphonewebsite其中一个值。

contactInfoType=email

3.4. Creating the Custom Constraint Annotation

3.4.创建自定义约束注释

And now, let’s create the annotation interface for our custom constraint:

现在,让我们为我们的自定义约束创建注释接口。

@Constraint(validatedBy = { ContactInfoValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ContactInfo {
    String message() default "Invalid value";

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

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

3.5. Applying the Custom Constraint

3.5.应用自定义约束

Finally, let’s add validation annotations to the contactInfo field of our Customer class:

最后,让我们为Customer类的contactInfo字段添加验证注释。

public class Customer {
    
    // ...
    @ContactInfo
    @NotNull
    private String contactInfo;
    
    // ...
}

4. Spring Controller and HTML Form

4.Spring控制器和HTML表单

To test our validation annotation, we will create a Spring MVC request mapping that uses the @Valid annotation to trigger the validation of a Customer object:

为了测试我们的验证注解,我们将创建一个Spring MVC请求映射,使用@Valid注解来触发Customer对象的验证。

@PostMapping("/customer")
public String validateCustomer(@Valid Customer customer, BindingResult result, Model model) {
    if (result.hasErrors()) {
        model.addAttribute("message", "The information is invalid!");
    } else {
        model.addAttribute("message", "The information is valid!");
    }
    return "customer";
}

The Customer object is sent to the controller from an HTML form:

Customer对象从一个HTML表单被发送到控制器。

<form action="customer" method="POST">
Contact Info: <input type="text" name="contactInfo" /> <br />
<input type="submit" value="Submit" />
</form>
<span th:text="${message}"></span>

To wrap it all up, we can run our application as a Spring Boot application:

为了总结这一切,我们可以将我们的应用程序作为Spring Boot应用程序运行。

@SpringBootApplication
public class DynamicValidationApp {
    public static void main(String[] args) {
        SpringApplication.run(DynamicValidationApp.class, args);
    }
}

5. Conclusion

5.结论

In this example, we have shown how we can create a custom validation annotation that retrieves a regular expression dynamically from a database and uses it to validate the annotated field.

在这个例子中,我们展示了如何创建一个自定义验证注解,从数据库中动态检索正则表达式并使用它来验证注解字段。

The full source code of the example can be found over on GitHub.

该示例的完整源代码可以在GitHub上找到over