Asked  6 Months ago    Answers:  5   Viewed   586 times

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.

Here's a snippet of the code:

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    private Account fromAccount;
....

@Entity
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
    private Set<Transaction> transactions;

I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)

So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:

if (account.getId()!=null) {
    account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
 // the below fails with a "detached entity" message. why?
entityManager.persist(transaction);

How can I correctly save a Transaction, associated with an already persisted Account object?

 Answers

22

This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.

As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.

An example setter for the Many side is in this link.

After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.

Tuesday, June 1, 2021
 
muaaz
answered 6 Months ago
16

Persist is intended for brand new transient objects and it fails if the id is already assigned. You should probably call saveOrUpdate instead of persist.

Alternatively, you can check if the object is already contained in your entity manager, and if so do a

entityManager.merge(yourObject);

,else

entityManager.persist(yourObject);
Tuesday, August 3, 2021
 
CoderGuy123
answered 4 Months ago
59

Maybe you miss the Provider class or one of its dependencies in your pom.xml dependencies?

The link you give to the hibernate docs says that you should also add

<project ...>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>${hibernate-core-version}</version>
    </dependency>
  </dependencies>
</project>

to your pom.xml

Monday, August 9, 2021
 
Lance
answered 4 Months ago
97

Entity graphs are meant to control which relationships (e.g. one-to-one, one-to-many, etc.) are loaded lazily or eagerly. They may not work for loading individual columns (it depends on the provider).

Hibernate has some support for this, but it is fairly difficult to get working, described here. However, they mention the following reticence towards this approach (which I whole-heartedly agree with):

Please note that this is mostly a marketing feature; optimizing row reads is much more important than optimization of column reads.

So I would not recommend going too far down this road until you've confirmed that this is indeed a bottleneck in your application (e.g. this kind of fetch tuning can be a symptom of premature optimization).

UPDATE:

As pointed out, JPA does leave it up to the provider as to whether or not simple columns (non-associations) are lazily fetched.

The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified. In particular, lazy fetching might only be available for Basic mappings for which property-based access is used.

Starting with Hibernate 5, official support for bytecode enhancement was added and this may allow for lazy attribute fetching.

From the latest Hibernate docs we have:

2.3.2

fetch - FetchType (defaults to EAGER)

Defines whether this attribute should be fetched eagerly or lazily. JPA says that EAGER is a requirement to the provider (Hibernate) that the value should be fetched when the owner is fetched, while LAZY is merely a hint that the value be fetched when the attribute is accessed. Hibernate ignores this setting for basic types unless you are using bytecode enhancement.

And this next snippet that describes the advantages of bytecode enhancement.

Lazy attribute loading

Think of this as partial loading support. Essentially you can tell Hibernate that only part(s) of an entity should be loaded upon fetching from the database and when the other part(s) should be loaded as well. Note that this is very much different from proxy-based idea of lazy loading which is entity-centric where the entity’s state is loaded at once as needed. With bytecode enhancement, individual attributes or groups of attributes are loaded as needed.

Sunday, August 22, 2021
 
wim
answered 4 Months ago
wim
55

I think the issue is here:

 @Test
    public void signup(){

    User user = new User();
    Company company = new Company();
    company.setName("Test");
    company = usService.saveCompany(company); //object is saved and transaction is closed, so company is detached here. 
    user.setFirstName("Test");
    user.setLastName("User");
    user.setEmail("test@test.com");
    user.setPassword("verySecret");
    user.setCompany(company); //u are setting this detached object to user, NOTE user object's company attr is cascade.all which means this company will be saved as well when you save user. 
    user = usService.saveUser(user); // gives exception, because you are saving new user object with detached company object. 

   }

So how do we solve it? You can let user save company object, so you do not save company explicitly, since cascade.all was on for company object in user, company should be saved as well when user is being saved:

@Test
    public void signup(){

    User user = new User();
    Company company = new Company();
    company.setName("Test");
    user.setFirstName("Test");
    user.setLastName("User");
    user.setEmail("test@test.com");
    user.setPassword("verySecret");
    user.setCompany(company);
    user = usService.saveUser(user); // gives exception

   }
Tuesday, August 24, 2021
 
HamidR
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share