与 Bean Validation API 结合使用时,Hibernate 不遵循 JPA 规范?

Posted

技术标签:

【中文标题】与 Bean Validation API 结合使用时,Hibernate 不遵循 JPA 规范?【英文标题】:Hibernate not following JPA specifications when combined with Bean Validation API? 【发布时间】:2013-02-05 08:41:32 【问题描述】:

这个问题是对这个问题的跟进:JPA ConstraintViolation vs Rollback

我对 JPA 和验证 API (JSR-303) 的组合进行了一些测试。

我在JPA specifications(第 101-102 页)中找到了以下内容:

默认情况下,默认的 Bean Validation 组(组 Default)将在 pre-persist 和 pre-update 生命周期验证事件时进行验证

...

如果 validate 方法返回的 ConstraintViolation 对象集不为空,则持久性提供程序必须抛出 javax.validation.ConstraintViolationException,其中包含对返回的 ConstraintViolation 对象集的引用,并且必须将事务标记为回滚。

我设置了以下测试:

HibernateValidator 作为 JSR-303 实现 2 PersistenceProvider Hibernate 和 EclipseLink 一个实体 NameNotNullWithDefaultGeneratedStrategy,其 ID 使用默认策略 (@Generated) 和 @NotNull String name 列生成 另一个实体 NameNotNullWithTableGeneratedStrategy 的 id 由表策略 (@TableGenerated) 和 @NotNull String name 列生成 测试尝试persist 每个实体的一个实例,name 为空。 预期结果是persist 方法抛出javax.validation.ConstraintViolationException,事务标记为rollback only(即这些假设基于本文引用的JPA 规范)。

结果是:

使用 eclipse 链接作为提供者: persist 方法为两个实体抛出一个 javax.validation.ConstraintViolationException。 交易在两种情况下都标记为rollback only 使用休眠作为提供程序: persist 为实体NameNotNullWithDefaultGeneratedStrategy 抛出javax.validation.ConstraintViolationException + 标记为rollback only 的事务 persist 不为实体NameNotNullWithTableGeneratedStrategy + 事务未标记 抛出任何异常rollback only commitNameNotNullWithTableGeneratedStrategy 失败并出现 RollbackException

问题是:

真的违反了 JPA 规范吗?或者我缺少表格生成策略的特殊情况? 如果是违规:是否存在任何与之相关的错误报告?

这是我的测试代码:

package com.example.jpa.validator;
import org.junit.Assert;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.RollbackException;

public class ConstraintViolationExceptionTest 

    @Test
    public void testHibernateDefaultStrategy()   // Success
        testPersistWithNullName("pu-hibernate",new NameNotNullWithDefaultGeneratedStrategy());
    

    @Test
    public void testHibernateTableStrategy() 
        testPersistWithNullName("pu-hibernate",new NameNotNullWithTableGeneratedStrategy());
        //this test fail with :
        //java.lang.AssertionError: Expecting a javax.validation.ConstraintViolationException, but persist() succeed !
    

    @Test
    public void testEclipseLinkDefaultStrategy()   // Success
        testPersistWithNullName("pu-eclipselink",new NameNotNullWithDefaultGeneratedStrategy());
    

    @Test
    public void testEclipseLinkTableStrategy()   // Success
        testPersistWithNullName("pu-eclipselink",new NameNotNullWithTableGeneratedStrategy());
    

    private void testPersistWithNullName(String persistenceUnitName, Object entity)
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
        EntityManager entityManager = emf.createEntityManager();
        try 
            final EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            try 
                try 
                    entityManager.persist(entity);
                    Assert.fail("Expecting a javax.validation.ConstraintViolationException, but persist() succeed !");
                 catch (javax.validation.ConstraintViolationException cve) 
                    //That's expected
                    Assert.assertTrue("According JPA specs transaction must be flagged as rollback only",transaction.getRollbackOnly());
                 catch (Exception e) 
                    Assert.assertTrue("According JPA specs transaction must be flagged as rollback only",transaction.getRollbackOnly());
                    e.printStackTrace();
                    Assert.fail("Expecting a javax.validation.ConstraintViolationException, but got " + e.getClass());
                
                transaction.commit();
                Assert.fail("persisted with null name !!!");
             catch (RollbackException e) 
                //That's expected
              catch (Exception e) 
                e.printStackTrace();
                Assert.fail("Unexpected exception :"+e.getMessage());
            
         finally 
            entityManager.close();
        
    

实体

默认策略

package com.example.jpa.validator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
public class NameNotNullWithDefaultGeneratedStrategy 

    @Id @GeneratedValue private Long id;
    @NotNull public String name;
    public NameNotNullWithDefaultGeneratedStrategy() 

表策略:

package com.example.jpa.validator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
import javax.validation.constraints.NotNull;

@Entity
public class NameNotNullWithTableGeneratedStrategy 

    @GeneratedValue(strategy = GenerationType.TABLE,
        generator = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
    @TableGenerator(name = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
    @Id @NotNull private Long id;
    @NotNull public String name;
    public NameNotNullWithTableGeneratedStrategy() 

persistence.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
        <persistence-unit name="pu-hibernate" transaction-type="RESOURCE_LOCAL">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <class>com.example.jpa.validator.NameNotNullWithTableGeneratedStrategy</class>
            <class>com.example.jpa.validator.NameNotNullWithDefaultGeneratedStrategy</class>
            <properties>
                <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test_mem_hibernate"/>
                <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
                <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            </properties>
        </persistence-unit>
        <persistence-unit name="pu-eclipselink" transaction-type="RESOURCE_LOCAL">
            <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
            <class>com.example.jpa.validator.NameNotNullWithTableGeneratedStrategy</class>
            <class>com.example.jpa.validator.NameNotNullWithDefaultGeneratedStrategy</class>
            <properties>
                <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test_mem"/>
                <property name="eclipselink.ddl-generation" value="create-tables"/>
                <property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform"/>
            </properties>
        </persistence-unit>
    </persistence>

pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>com.example</groupId>
        <artifactId>com.example.jpa.validator</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <hibernate.version>4.2.0.CR1</hibernate.version>
            <hibernate-validator.version>4.3.1.Final</hibernate-validator.version>
            <junit.version>4.11</junit.version>
            <h2.version>1.3.170</h2.version>
        </properties>

        <dependencies>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>$hibernate-validator.version</version>
            </dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <version>$h2.version</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
                <version>$junit.version</version>
            </dependency>

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>$hibernate.version</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-entitymanager</artifactId>
                <version>$hibernate.version</version>
            </dependency>

            <dependency>
                <groupId>org.eclipse.persistence</groupId>
                <artifactId>org.eclipse.persistence.jpa</artifactId>
                <version>2.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.persistence</groupId>
                <artifactId>javax.persistence</artifactId>
                <version>2.0.0</version>
            </dependency>
        </dependencies>

        <repositories>
            <repository>
                <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
                <id>eclipselink</id>
                <layout>default</layout>
                <name>Repository for library EclipseLink (JPA 2.0)</name>
            </repository>
        </repositories>
    </project>

【问题讨论】:

@Column(nullable=false) 和 @NotNull 会发生什么? 我试过@Column(nullable=false):结果相同 我真的不明白为什么在这种情况下生成策略会影响验证,但是我肯定会打开一个错误报告,因为您已经针对休眠设置了一个可重现的测试用例(好吧,在剥离 eclipselink 部分之后) -验证器。 刚刚回答了类似主题的***.com/questions/16172843/…,另一个答案只是使用.flush() 调用,因为(他们的回答状态)“JPA 仅在与数据库同步时调用 Bean 验证”(这是真的吗?)。我对此进行了测试,它适用于非简单的@GeneratedValue.saveAndFlush() 也是如此)。只是想让您知道一个潜在的“解决方法”。 【参考方案1】:

我为此提交了错误报告:https://hibernate.onjira.com/browse/HHH-8028

【讨论】:

以上是关于与 Bean Validation API 结合使用时,Hibernate 不遵循 JPA 规范?的主要内容,如果未能解决你的问题,请参考以下文章

由于 Bean Validation API 无法启动 Hibernate Validator

“Bean Validation API 在类路径上,但找不到实现”阻止启动

5. Bean Validation声明式验证四大级别:字段属性容器元素类

javax.validation.ValidationException: Unable to create a Configuration, because no Bean Validation p

如何使用java validation api进行参数校验----Hibernate-Validation

JSR 303 - Bean Validation 介绍及最佳实践