Vojtěch
Vojtěch

Reputation: 12416

Hibernate: Accessing created entity from different transaction

I am having quite complex methods which create different entities during its execution and use them. For instance, I create some images and then I add them to an article:

@Transactional
public void createArticle() {
  List<Image> images = ...
  for (int i = 0; i < 10; i++) {
      // creating some new images, method annotated @Transactional
      images.add(repository.createImage(...));
  }

  Article article = getArticle();
  article.addImages(images);
  em.merge(article);
}

This correctly works – images have their IDs and then they are added to the article. The problem is that during this execution the database is locked and nothing can be modified. This is very unconvinient because images might be processed by some graphic processor and it might take some time.

So we might try to remove the @Transactional from the main method. This could be good. What happens is that images are correctly created and have their ID. But once I try to add them to article and call merge, I get javax.persistence.EntityNotFoundException for Image with ID XXXX. The entity manager can't see that the image was created and have its ID. So the database is not locked, but we can't do anything either.

So what can I do? I don't want to have the database locked during the whole execution and I want to be able to access the created entities!

I am using current version of Spring and Hibernate, everything defined by Annotations. I don't use session factory, I am accessing everything via javax.persistence.EntityManager.

Upvotes: 1

Views: 1601

Answers (3)

Yuri
Yuri

Reputation: 1765

My best guess is that your transaction isolation level is SERIALIZABLE. That's why the DB locks affected tables for the whole duration of a transaction.
If that's the case change the level to READ_COMMITTED. Hibernate (or any JPA provider) works nicely with this one. It won't lock anything unless you explicitly call entityManager.lock(someEntity, LockModeType.SomeLockType))
Also when you choose transaction boundaries firstly think in terms of atomicity. If createArticle() is an atomic unit of work it just has to be made transactional, breaking it into smaller transactions for the sake of 'optimization' is wrong.

Upvotes: 0

Angular University
Angular University

Reputation: 43087

Consider leveraging the Hibernate cascading functionality for persisting object trees in one go with minimal database locking:

@Entity
public class Article {
    @OneToMany(cascade=CascadeType.MERGE)
    private List<Images> images;
}

@Transactional
public void createArticle() {
    //images created as Java objects in memory, no DAOs called yet
    List<Image> images = ...  
    Article article = getArticle();
    article.addImages(images);
    // cascading will save the article AND the images
    em.merge(article);
}

Like this the article AND it's images will get persisted at the end of the transaction in a single transaction with a minimal lifetime. Up until then no locking occurred on the database.

Alternativelly split the createArticle in two @Transactional business methods, one createImages and the other addImagesToArticle and call them one after the other in a third method in another bean:

@Service
public class OtherBean {
    @Autowired
    private YourService yourService;    

    // note that no transactional annotation is used, this is intentional
    public otherMethod() {
        yourService.createImages(); // first transaction - images are committed

        yourService.addImagesToArticle(); // second transaction - images are added to article
    }
}

Upvotes: 2

Petter
Petter

Reputation: 4165

You could try setting the transaction isolation on your datasource to READ_UNCOMMITTED, though that can lead to inconsistencies so it is generally not a recommended thing to do.

Upvotes: 0

Related Questions