0

I have the bidirectional OneToOne association Account <-> Budget. According to this I tried to lazily load the association:

For the Account.java:

@Audited
@Entity
public class Account {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE)
  protected long id;

  @OneToOne(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.LAZY)
  private  Budget mainBudget;
  } 
...
}

For the Budget.java:

@Audited
@Entity
public class Budget {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE)
  protected long id;

  @OneToOne(fetch=FetchType.LAZY)
  @MapsId
  private Account account;

  @OneToMany(targetEntity = Budget.class, cascade = CascadeType.ALL, 
    mappedBy = "parentBudget", orphanRemoval=true)
  @Fetch(value = FetchMode.SUBSELECT)
  private  List<Budget> subBudget; 
...
} 

Now I am trying to make a new account with a new budget with the create-method in the DAOClass looking as follows:

@Resource(name = "DBRouter", type = DatabaseRouter.class)
protected DatabaseRouter router;

@PersistenceContext
protected EntityManager em;

@Transactional
public D create(D d, boolean flush, String resource) {
  router.setDataSource(resource);
  if (flush) {
    em.flush();
  }
  D obj = em.merge(d);
  if (flush) {
    em.flush();
  }
  return obj;
}

This gives me the following exception:

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [[PACKACKENAMES].Account#50](31 internal lines)
    at org.apache.openejb.persistence.JtaEntityManager.merge(JtaEntityManager.java:203)
    at [PACKACKENAMES].DAOClass.create(DaoClass.java:75)
    at [PACKACKENAMES].DaoClass.create(DaoClass.java:66)
...

If I remove the @MapsId annotation, then I don't have this problem, although then the lazy loading does not work properly. What is the reason for this exception and how to solve it?

lasbr
  • 79
  • 1
  • 8
  • MapsId annotation is wrong here. It would be needed if you had embedded id or parent entity. What is a problem with lazy loading if you removed the MapsId? Please add details to the question. – Mar-Z Apr 06 '23 at 19:45
  • @Mar-Z In [this article](https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/) it is said that the best way to achieve lazy loaded OneToOne associations is with `@MapsId`. It also says this about "doing it without `@MapsId": _While the unidirectional @OneToOne association can be fetched lazily, the parent-side of a bidirectional @OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad._ – lasbr Apr 06 '23 at 19:54
  • Got it. In your case Account is a parent entity for Budget. Usage of MapsId annotation is correct. However (as stated in the article) you should remove the GeneratedValue annotation from the Budget. Because the id is populated with the identifier of the account association. Hope it will help to get rid of the exception. – Mar-Z Apr 07 '23 at 08:20
  • The MapsId annotation - why do you have it? What does your Budget table look like, as I think you are trying something that is just won't work, like having a parent budget with an account, but all the Child budgets either with a null account or using the same account as the parent. If you are using the foriegn key to account as the ID, it must be unique! So an account cannot have multiple budgets associated to it, and every budget must have an account. PS the article you linked is very misleading. MapsId has nothing to do with lazy, and should ONLY be used when needed. – Chris Apr 10 '23 at 15:26
  • @Chris I dropped the idea of using `@MapsId` already, since I don't want an embedded id at all. My Budget objects have to be standalone, because I have other entities with a `@OneToOne`-association as well. I posted [a new question](https://stackoverflow.com/questions/75976850/hibernate-lazily-fetch-onetoone-association-with-same-parent-referred-by-mult) with another problem and more detailed insight into what I actually want to do. When eagerly loaded (without `mappedBy` and `@MapsId` of course) the associations worked as shown above. Now my goal is to just load them lazily. – lasbr Apr 10 '23 at 15:38

2 Answers2

1

In your case Account is a parent entity for Budget. @MapsId annotation says JPA to use the id of the parent entity for the mapping of annotated one-to-one relationship. Both ids will be identical for associated entities (rows). There is no need to generate a separate id for Budget. As we can see from the exception it would even not work.

Solution - remove the @GeneratedValue annotation from the Budget entity.

public class Budget {
    @Id
    private long id;

    @OneToOne(fetch=FetchType.LAZY)
    @MapsId
    private Account account;
...
}
Mar-Z
  • 2,660
  • 2
  • 4
  • 16
  • Unfortunately this did not solve the issue. I guess it may has to do with Budget not only being a child of Account but also a parent of itsself. I added this information to the question (Budget.java now has a subBudget-association) – lasbr Apr 07 '23 at 12:23
0

You have:

public class Budget {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE)
  protected long id;

  @OneToOne(fetch=FetchType.LAZY)
  @MapsId
  private Account account;

But this mapping doesn't make sense. The id can't be a @GeneratedValue if it's mapped by the one-to-one association to Account.

So:

  1. remove the @GeneratedValue annotation, and also
  2. make sure you explicitly set the id field to the value of the id field of the associated Account.

Or, alternatively:

  • just don't use @MapsId, if what you want is a foreign key column that is separate from the primary key.

See also:

Hibernate error (EntityExistsException) when persisting entity with children multiple times

Which is a similar problem.

Gavin King
  • 3,182
  • 1
  • 13
  • 11