为啥 JPA 重复持久方法不抛出异常?

Posted

技术标签:

【中文标题】为啥 JPA 重复持久方法不抛出异常?【英文标题】:Why doesn't JPA repeat persist method throw an exception?为什么 JPA 重复持久方法不抛出异常? 【发布时间】:2019-11-15 18:57:54 【问题描述】:
Product product = new Product();
product.setName( "foo" );
product.setPrice(BigDecimal.valueOf( 4.5 ) );
pm.create( product ); // pm delegates calls to an entity manager object using persist method and tx is immediately commited after the call

List<Product> products = pm.findAllProducts();
products.stream().forEach( System.out::println ); // New product is listed too.

pm.create( product ); // Causes no exception! But, as per API, it should.

products = pm.findAllProducts(); // Fetch successful
products.stream().forEach( System.out::println ); // No difference from first print.

根据persistence API,如果实体已经存在,则坚持(从pm.create 调用)抛出EntityExistsException,但它不会按照代码发生。

    持久性提供程序 (PP) - EclipseLink。 为什么 PP 忽略重复仍然存在? PP 在什么情况下选择抛出异常?

编辑:

Product.java

注意:

    为简洁起见,排除了 getter 和 setter(适用于所有字段)和 toString()。 我已尽力按照指南格式化代码,但它没有发生,请多多包涵。

@Entity @Table(name = "PRODUCTS") @XmlRootElement @NamedQueries( @NamedQuery(name = "Product.findAll", query = "SELECT p FROM Product p") , @NamedQuery(name = "Product.findById", query = "SELECT p FROM Product p WHERE p.id = :id") , @NamedQuery(name = "Product.findByName", query = "SELECT p FROM Product p WHERE p.name like :name") , @NamedQuery(name = "Product.findByPrice", query = "SELECT p FROM Product p WHERE p.price = :price") , @NamedQuery(name = "Product.findByBestBefore", query = "SELECT p FROM Product p WHERE p.bestBefore = :bestBefore") , @NamedQuery(name = "Product.findByVersion", query = "SELECT p FROM Product p WHERE p.version = :version") , @NamedQuery(name = "Product.findTotal", query = "SELECT count(p.id), sum(p.price) FROM Product p WHERE p.id in :ids" )

公共类产品实现可序列化

private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@SequenceGenerator( name="pidGen", sequenceName="PID_SEQ", allocationSize=1 )
@GeneratedValue( strategy=SEQUENCE, generator="pidGen" )
private Integer id;
@Basic(optional = false)
@NotNull
@Size(min = 3, max = 40, message="prod.name")
private String name;
// @Max(value=?)  @Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
@Basic(optional = false)
@NotNull
@Max( value=1000, message="prod.price.max")
@Min( value=1, message="prod.price.min")
private BigDecimal price;
@Column(name = "BEST_BEFORE")
@Temporal(TemporalType.DATE)
//private Date bestBefore;
private LocalDate bestBefore;
@Version
private Integer version;

public Product() 


public Product(Integer id) 
    this.id = id;


public Product(Integer id, String name, BigDecimal price) 
    this.id = id;
    this.name = name;
    this.price = price;




@Override
public int hashCode() 
    int hash = 0;
    hash += (id != null ? id.hashCode() : 0);
    return hash;


@Override
public boolean equals(Object object) 
    // TODO: Warning - this method won't work in the case the id fields are not set
    if (!(object instanceof Product)) 
        return false;
    
    Product other = (Product) object;
    if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) 
        return false;
    
    return true;



 

【问题讨论】:

pm 是什么的一个实例? 显示产品实体 @AlanHay pm 是 POJO - 助手/实用程序/代表。 create 方法:public void create( @Valid Product product ) em.getTransaction().begin(); em.persist( product ); em.getTransaction().commit(); @MaciejKowalski 更新了 OP。 【参考方案1】:

根据JPA Spec:

    如果 X 是一个新实体,它将成为托管实体。实体 X 将在或 在事务提交之前或作为刷新操作的结果。

    如果 X 是预先存在的托管实体,则持久操作将忽略它 (...)

    如果 X 是一个分离的对象,在持久化时可能会抛出 EntityExistsException 操作被调用,或者可能在刷新或提交时抛出 EntityExistsException 或另一个 PersistenceException

当您调用 EntityManager.persist(product) 时,product 成为托管实体 (#1)。任何后续对EntityManager.persist(product) 的调用都将被忽略,如#2 中所述。最后一点仅在您尝试在 分离的 实体上调用 persist() 时适用。

【讨论】:

@MaciejKowalski 啊!现在我看到 OP 正在启动并在 pm.create() 内提交事务。是的,你完全正确 @MaciejKowalski 再三考虑,这是一个应用程序管理的 EM,因此根据定义,它具有扩展范围。这是否意味着product 会保留在持久性上下文中,直到 EM 被清除或关闭? 我们不知道。也许你是对的。但这隐藏在 create 方法中 #2 正在根据情况采取行动。感兴趣的用户: - 如果一个新实体被持久化和更新,那么如果在其上调用persist 方法,持久化提供者将更新它。代码(与OP比较):Product product = new Product(); product.setName( "foo1" ); product.setPrice(BigDecimal.valueOf( 4.5 ) ); pm.create( product ); List&lt;Product&gt; products = pm.findAllProducts(); products.stream().forEach( System.out::println ); product.setName( "foo2" ); pm.create( product ); products = pm.findAllProducts(); products.stream().forEach( System.out::println ); 规范的“3.2.2Persisting an Entity Instance”部分(参见答案中的链接)有解释。

以上是关于为啥 JPA 重复持久方法不抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

C++ STL栈问题:为啥栈为空时pop()不抛出异常?

为啥设置 DataSource 时 ComboBox 不抛出异常?

Spring事务异常回滚,捕获异常不抛出就不会回滚

Spring事务异常回滚,捕获异常不抛出就不会回滚

Spring事务异常回滚,捕获异常不抛出就不会回滚(转载) 解决了我一年前的问题

如何从子方法中退出方法而不抛出异常