3

I have 2 entities in a JPA project:

A category and a question. so each Category will have a list of questions and each question will be part of a category (OnetoMany relation). I manage the bi-directional relationship through the set/add methodes in both entities :

Question :

    @ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "Qcategory")
private Category category;

public void setCategory(Category category) {
 this.category = category;

 if (category != null && !category.getQuestions().contains(this)) {
 category.addQuestion(this);
 }
 }

Category :

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "category")
private List<Question> questions= new ArrayList<Question>();


 public void addQuestion(Question question) {
 this.questions.add(question);

 if (question.getCategory() != this) {
 question.setCategory(this);
 }

 }

I first create a category.

Category category1 = new Category();
category1.setName = "exampleCategory";

I add this to the db through my repository (added in a similar way as the question addOrUpdate as below)

After that I create an question

Question question1 = new Question();

I set the category of the question to category1

question.setCategory = category1;

After this I also try to persist the question to the db by calling the addOrUpdate method below. I then get an error :

....:javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: jpa.entities.Category

I use a repository method like :

@Override
public boolean addOrUpdate(Question question) {
    EntityManagerFactory emf = JPARepositoryFactory
            .getEntityManagerFactory();
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();

    Question tempQuestion = null;
    try {
        if (question.getId() != null) {
            tempQuestion = em.find(Question.class,
                    question.getId());
        }

        if (tempQuestion == null) {
            em.persist(question);
        } else {

            tempQuestion .setCategory(question.getCategory());
            ... (other setters)
            tempQuestion = em.merge(question);
        }
    } catch (Exception e) {
        ....logging...      }
    tx.commit();
    em.close();
    emf.close();
    return true;
}

Any suggestion would be more then welcome.

Darth Blue Ray
  • 9,445
  • 10
  • 35
  • 48

3 Answers3

1

So you're only allowed to call persist once on an entity. That error means that you've already called persist on that Question object that is being passed in, but you did so in another transaction. If you want to reattach the Question object to the persistence context you need to call merge or reload it from the database.

Pace
  • 41,875
  • 13
  • 113
  • 156
  • I first create an category and save it to the db (persist). After that I create a question and I set the question.setCategory to the category I created. Then I try to save this new question to the db (with a reference to teh allready excisting category) I keep both ends of the relationship at the right state. I believe this is a "normal" way to work with these realtionships. You create one object that is needed in the other and select that before I create the other. – Darth Blue Ray Mar 11 '13 at 11:55
  • 1
    Ah. But you have a persist cascade on your question. This means it goes and calls persist on the category which has already been persisted, thus the message. Remove the persist cascade from the Question object's category field. – Pace Mar 11 '13 at 18:42
  • This seems to do the trick. what Cascade I need to put in? I changed it to Cascade.MERGE but I need to evaluate if that's ok – Darth Blue Ray Mar 11 '13 at 22:25
  • 1
    You only want cascade persist if you create all of the objects within the same transaction. A typical example is something like an order which has several line items. If you want to create the order and all the line items at the same time then you could put Cascade.PERSIST on the order. Typically you don't have any cascades on the many side of a relationship. – Pace Mar 11 '13 at 23:28
  • Thanks! I will try to put off all cascade on the many sides of the relationships (also no need for MERGE??). Any suggestion what type of cascade to put on the other side (one)? – Darth Blue Ray Mar 12 '13 at 09:04
0

What you have to do before persist or merge is to set the Category reference in each Question .

PSR
  • 39,804
  • 41
  • 111
  • 151
0

[Note: This may be not the direct answer, just few observations]

  1. It's definitely isn't a good practice to initialize EntityManagerFactory with each method invocation. Instead, it should be created once probably during application startup.

  2. You are passing Category, which is part of another persistence context to addOrUpdate in which it isn't in managed state.

  3. What do you have cascade=MERGE/cascade=PERSIST or cascade=ALL on your relationship.

  4. Probably, you can fetch Category by id again in the current thransaction & set it in question before persisting.

From Documentation :

Bidirectional relationships must follow these rules.

  • The inverse side of a bidirectional relationship must refer to its owning side by using the mappedBy element of the @OneToOne, @OneToMany, or @ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
  • The many side of many-to-one bidirectional relationships must not define the mappedBy element. The many side is always the owning side of the relationship.
  • For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
  • For many-to-many bidirectional relationships, either side may be the owning side.
Nayan Wadekar
  • 11,444
  • 4
  • 50
  • 73
  • I have Cascade= CascadeType.ALL in my relationship (see relationship at the top of my question). I believe I followed the rules for this relatyionship. How do I fetch the id again, if you see at my addOrUpdate method? – Darth Blue Ray Mar 11 '13 at 10:07
  • @DarthBlueRay It's similar to what you have done to fetch `tempQuestion` through EntityManager, something like `em.find(Question.class, category.getId())` & then set it in `question`. Can try your current code with single transaction propagating. Creating category & then persisteing it, then setting it in question & persist. Can change persist/merge operations accordingly. – Nayan Wadekar Mar 11 '13 at 10:20
  • I changed the code to : tempCategory = em.find(Category.class, question.getCategory().getId()); tempFeedbackVraag.setCategory(tempCategory); If this what you mean. I got the same error ... . – Darth Blue Ray Mar 11 '13 at 10:35
  • I'm wrong doing so. first I create a new category what I persist. Then I create a new question. then I set this category to the question (set) and then try to persist this. In this case the question it self is new (must be persisted) but the property category has a link to an all ready persisted category (hence the detached object). When adding the new question I perist, I don't merge ... . I will fetch it and try to set it just before persisting. – Darth Blue Ray Mar 11 '13 at 10:50
  • I tried to fetch the category like : tempCategory = em.find(Category.class, question.getCategory().getId()); question.setCategory(tempCategory); em.persist(question); I still get the same error. – Darth Blue Ray Mar 11 '13 at 10:56
  • @DarthBlueRay Have you tried `em.merge(question)` instead of persisting. – Nayan Wadekar Mar 11 '13 at 11:38
  • When I try this (em.merge(question) I get an javax.persistence.OptimisticLockException error?! – Darth Blue Ray Mar 11 '13 at 11:43