Jakarta ee 为什么GlassFish 4不验证我的@Valid@ElementCollection字段的成员?

Jakarta ee 为什么GlassFish 4不验证我的@Valid@ElementCollection字段的成员?,jakarta-ee,bean-validation,glassfish-4,hibernate-validator,Jakarta Ee,Bean Validation,Glassfish 4,Hibernate Validator,我创建了以下实体并可嵌入: @Entity public class Person implements Serializable { @NotNull @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*") private String firstName; @Valid @ElementCollection p

我创建了以下实体并可嵌入:

@Entity
public class Person implements Serializable {

    @NotNull
    @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*")
    private String firstName;

    @Valid
    @ElementCollection
    private List<Email> email;
...
}

@Embeddable
public class Email implements Serializable {

    @NotNull (message = "Email Address may not be null")
    @Pattern(regexp = "[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}", message = "Email Address must match pattern [-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}")
    private String address;
...
}
然后我进行了以下测试

  • 创造人
  • 将地址为“adsj alkjdfa”的电子邮件添加到个人电子邮件列表中
  • 保存该对象
  • 正如预期的那样,我收到了一个约束冲突,声明电子邮件与模式不匹配,并且该人没有被持久保存在数据库中

    作为最后一个测试,我将以下代码添加到JSF CDIBean的addEmail方法中,以便手动检查验证

        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
        Set<ConstraintViolation<Email>> emailCV = validator.validate(newEmail);
        logger.debug("Email constraint violations: " + emailCV);
    
        Set<ConstraintViolation<Person>> personCV = validator.validate(person);
        logger.debug("Person constraint violations: " + personCV);
    
        Set<ConstraintViolation<Person>> personEmailCV = validator.validateProperty(person, "email");
        logger.debug("Person Email constraint violations: " + personEmailCV);
    
    以下是我的Person类的更完整视图:

    @Entity
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    @NamedQueries({
            @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p ORDER BY p.lastName, p.firstName"),
            @NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE p.lastName = :lastName and p.firstName = :firstName and p.middleName = :middleName"),
            @NamedQuery(name = "Person.findByShortName", query = "SELECT p FROM Person p WHERE p.shortName = :shortName") })
    public class Person implements Serializable {
    
        final static Logger logger = LoggerFactory.getLogger(Person.class.getName());
    
        static final long serialVersionUID = 1L;
    
        @TableGenerator(name = "Person_Generator", table = "ID_Gen", pkColumnName = "GEN_NAME", valueColumnName = "GEN_VAL", initialValue = 0, allocationSize = 1)
        @Id
        @GeneratedValue(generator = "Person_Generator")
        @XmlAttribute
        private Long id;
        @Version
        @XmlAttribute
        private Integer version;
        private String prefixName;
        @NotNull
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*")
        private String firstName;
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Middle Name must match pattern ([A-Z][a-z]*)*")
        private String middleName;
        @NotNull 
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Last Name must match pattern ([A-Z][a-z]*)*")
        private String lastName;
        private String suffixName;
        @NotNull
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Familiar Name must match pattern ([A-Z][a-z]*)*")
        private String familiarName;
        @NotNull
        @Column(unique = true)
        @Pattern(regexp = "[a-z0-9]*", message = "Short Name must match pattern [a-z0-9]*")
        private String shortName;
        private String description;
    
        @Valid
        @ElementCollection
        private List<Email> email;
        @ElementCollection
        private List<Voice> voice;
        @ElementCollection
        private List<Address> addresses;
    
        private ELISFile picture;
        @XmlElementWrapper(name = "notes")
        @XmlElement(name = "note")
        @ElementCollection
        private List<EntityNote> notes;
        @XmlTransient
        // @XmlElementWrapper(name="history")
        // @XmlElement(name="event")
        @NotNull
        @ElementCollection
        private List<EntityEvent> history;
    
        public Person() {
    
            logger.debug("Person created.");
    
            email = new ArrayList<Email>();
            voice = new ArrayList<Voice>();
            addresses = new ArrayList<Address>();
            notes = new ArrayList<EntityNote>();
            history = new ArrayList<EntityEvent>();
    
        }
    ...
    

    问题不在于Glassfish,而在于您的模型以及JSF如何与Bean验证集成。您可能已经发现,JSF并没有使用
    Validator#validte
    ,而是使用
    Validator#validateProperty
    来验证单个属性上的约束。前者验证完整对象图,而后者仅验证给定属性上指定的约束。级联验证不由
    验证程序#validateProperty
    执行


    不过,JPA级别的验证(如果您启用它)应该可以工作。在不同的JPA生命周期回调上会发生完整的实体验证。

    这个问题的答案有以下两部分,其中第1部分解释了JSF没有捕获验证错误的原因,第2部分解释了JPA没有捕获验证错误的原因

    第一部分

    正如Hardy在回答中所说,JSF使用Hibernate Validator的validateProperty方法部分验证Person实体,validateProperty不支持Person的email属性上的@Valid注释

    第二部分

    GlassFish 4.0和4.1中的JPA/EclipseLink在使用@Valid@ElementCollection注释list属性时不会级联到该属性的元素中。如果我从我的属性中删除@ElementCollection,我可以看到级联验证按预期工作,但这破坏了JPA,因此不是一个好的解决方法。我在GlassFish bug跟踪系统中记录了这个问题,名为GlassFish-21184

    我通过在调用persist之前手动执行验证来解决该错误,如下所示:

    Set<ConstraintViolation<Person>> personCV = validator.validate(person);
    
    if (personCV.size() > 0)
        throw new ConstraintViolationException(personCV);
    
    em.persist(person);
    
    Set personCV=validator.validate(个人);
    如果(personCV.size()>0)
    抛出新的ConstraintViolationException(personCV);
    em.person;
    
    很有趣。JSF使用validateProperty部分验证Person实体,validateProperty不支持Person的email属性上的@Valid注释。这解释了为什么我的JSF客户端不报告约束冲突。但是,我仍然不理解为什么JPA级别在我持久化实体时不验证电子邮件属性。我读到的所有内容都表明默认情况下启用了bean验证。我是否必须向persistence.xml文件添加一些属性才能启用JPA级别的bean验证?谢谢你的帮助!默认情况下启用JPA生命周期事件验证,但也可以由某些属性控制。看见很难说为什么它对您不起作用,特别是因为在您的示例中,您没有显示JPA注释/配置。我建议打开调试级日志记录以查看是否发生验证。如果你想要更多反馈,你需要用更多信息更新你的问题。我在GlassFish 4服务器配置中添加了一个org.hibernate.validator记录器,并观察到验证器正在验证firstName属性是否为null并与模式匹配。如果我在email属性中添加@Size(min=1)注释,我会发现验证器会在email属性上验证列表的大小,但不会验证组成列表的email对象的address属性。我用一些配置信息更新了我的原始问题。
    @Entity
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    @NamedQueries({
            @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p ORDER BY p.lastName, p.firstName"),
            @NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE p.lastName = :lastName and p.firstName = :firstName and p.middleName = :middleName"),
            @NamedQuery(name = "Person.findByShortName", query = "SELECT p FROM Person p WHERE p.shortName = :shortName") })
    public class Person implements Serializable {
    
        final static Logger logger = LoggerFactory.getLogger(Person.class.getName());
    
        static final long serialVersionUID = 1L;
    
        @TableGenerator(name = "Person_Generator", table = "ID_Gen", pkColumnName = "GEN_NAME", valueColumnName = "GEN_VAL", initialValue = 0, allocationSize = 1)
        @Id
        @GeneratedValue(generator = "Person_Generator")
        @XmlAttribute
        private Long id;
        @Version
        @XmlAttribute
        private Integer version;
        private String prefixName;
        @NotNull
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "First Name must match pattern ([A-Z][a-z]*)*")
        private String firstName;
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Middle Name must match pattern ([A-Z][a-z]*)*")
        private String middleName;
        @NotNull 
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Last Name must match pattern ([A-Z][a-z]*)*")
        private String lastName;
        private String suffixName;
        @NotNull
        @Pattern(regexp = "([A-Z][a-z]*)*", message = "Familiar Name must match pattern ([A-Z][a-z]*)*")
        private String familiarName;
        @NotNull
        @Column(unique = true)
        @Pattern(regexp = "[a-z0-9]*", message = "Short Name must match pattern [a-z0-9]*")
        private String shortName;
        private String description;
    
        @Valid
        @ElementCollection
        private List<Email> email;
        @ElementCollection
        private List<Voice> voice;
        @ElementCollection
        private List<Address> addresses;
    
        private ELISFile picture;
        @XmlElementWrapper(name = "notes")
        @XmlElement(name = "note")
        @ElementCollection
        private List<EntityNote> notes;
        @XmlTransient
        // @XmlElementWrapper(name="history")
        // @XmlElement(name="event")
        @NotNull
        @ElementCollection
        private List<EntityEvent> history;
    
        public Person() {
    
            logger.debug("Person created.");
    
            email = new ArrayList<Email>();
            voice = new ArrayList<Voice>();
            addresses = new ArrayList<Address>();
            notes = new ArrayList<EntityNote>();
            history = new ArrayList<EntityEvent>();
    
        }
    ...
    
    @Embeddable
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Email implements Serializable {
    
        final static Logger logger = LoggerFactory.getLogger(Email.class.getName());
    
        static final long serialVersionUID = 1L;
    
        @NotNull (message = "Email Type may not be null")
        @Enumerated(EnumType.STRING)
        private EmailType type;
        @NotNull (message = "Email Address may not be null")
        @Pattern(regexp = "[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}", message = "Email Address must match pattern [-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}")
        private String address;
        private String description;
        ...
    
    Set<ConstraintViolation<Person>> personCV = validator.validate(person);
    
    if (personCV.size() > 0)
        throw new ConstraintViolationException(personCV);
    
    em.persist(person);