4

I am learning JPA, and I am trying to use it in a Spring MVC web app. I need to implement a method that deletes an object/record. Currently, I have the following implementation of the method:

@Transactional
public void deleteProduct(int productId, int productVersion) {

    Product product = entityManager.find(Product.class, productId);
    product.setVersion(productVersion);
    entityManager.remove(product);
}

productVersion is used for optimistic locking. It is the old version of the object/record that comes from the web GUI.

This method deletes the record in the DB, but it does not throw any exception when the version of the record in the DB does not match productVersion. (I only have a problem with deleting objects: when I update a record with entityManager.merge(product), I get an exception with the message: Row was updated or deleted by another transaction.)

Hibernate generates the following SQL query: delete from Product where id=? and version=?, i.e. it tries to check the version field.

What am I doing wrong?

Also, is it a correct approach to removing an object by its id? I am concerned that my method generated two SQL queries: a SELECT for entityManager.find(), and a DELETE for entityManager.remove(). Is there a more optimal way to delete an object/record?

Product class

@Entity
public class Product {

    @Id
    @GeneratedValue
    protected int id;

    protected String name;

    protected BigDecimal price;

    @Version
    protected int version;

    // getters and setters
}
Alex
  • 815
  • 3
  • 11
  • 21
  • Your Question Tags state that you are using Hibernate and Spring. Is that correct? If so, why aren't you using session factory and things like that which make your ORM life easier? – Niklas S. Aug 21 '14 at 11:42
  • What behavior are you trying to achieve? Do you want the delete to always succeed or do you want delete to succeed only if the version matches? – Varun Phadnis Aug 21 '14 at 11:44
  • @MrPixelDream Yes, I use Hibernate and Spring. I use JPA as it's a part of Java EE. I cannot switch to pure Hibernate. – Alex Aug 21 '14 at 11:54
  • @VarunPhadnis The current method always deletes the record. I want it to throw an exception when the versions do not match. – Alex Aug 21 '14 at 11:58

1 Answers1

8

One way is to do it manually in one query as follows:

@Transactional
public void deleteProduct(int productId, int productVersion) {
   int isSuccessful = entityManager.createQuery("delete from Product p where p.productVersion=:productVersion and p.id=:id")
            .setParameter("id", productId)
            .setParameter("productVersion", productVersion)
            .executeUpdate();
    if (isSuccessful == 0) {
        throw new OptimisticLockException(" product modified concurrently");
    }
}
Varun Phadnis
  • 591
  • 3
  • 6
  • +1 Thank you! Your code works, and it generates one SQL query only. But my example is (mostly) just a learning exercise. I used SQL queries many times before, and now I want to understand the ORM way to do things. I'll keep your code as the last resort, but will wait for other answers. – Alex Aug 21 '14 at 12:41
  • I also had a look at the `entityManager.getReference()` method that you mentioned in the previous version of your answer. Judging by [this](http://stackoverflow.com/a/1608621) post, it really is a way to avoid generating the second SQL query (`SELECT`). But in my application `SELECT` is generated anyway for some reason. – Alex Aug 21 '14 at 13:03
  • Using `entityManager.getReference()` will still generate two queries in your case since you will try to set the `productVersion` in the Entity (`product.setVersion(productVersion)`) thus forcing your JPA provider to load the whole Entity instead of working with the proxy. I had posted it earlier assuming you want to delete always (ignoring the productVersion). Hope this helps. – Varun Phadnis Aug 21 '14 at 13:18
  • 1
    Another issue with this answer is that it does not perform the cascading. In a lot of situations this solution won't suffice. I wanted to implement a generic version of this method (in Spring Data JPA) and I ended up with: find, manual check of the version and delete. Alternatively, merge + delete might work but I didn't check. – Didier L Oct 22 '14 at 11:55