Search code examples
jpaexceptioneclipselinkpersist

Why doesn't JPA repeat persist method throw an exception?


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.

As per persistence API, if an entity alredy exists, persist(called from pm.create) throw's EntityExistsException, but its not happening as per code.

  1. Pesistence provider(PP) - EclipseLink.
  2. Why is PP ignoring repeat persist?
  3. In what circumstances does a PP choose to throw an exception?

EDIT:

Product.java

NOTE:

  1. Excluded getters and setters(for all fields) and toString() for brevity.
  2. I tried my best to format code as per guidelines, but its not happening, please bear.

@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" ) })

public class Product implements Serializable {

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;
}


 }

Solution

  • As per the JPA Spec:

    1. If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation.

    2. If X is a preexisting managed entity, it is ignored by the persist operation (...)

    3. If X is a detached object, the EntityExistsException may be thrown when the persist operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time

    When you invoke EntityManager.persist(product), product becomes a managed entity (#1). Any subsequent calls to EntityManager.persist(product) are ignored, as described in #2. The final point applies only when you try to invoke persist() on a detached entity.