7

This question is so simple, you can probably just read the code

This is a very simple performance question. In the code example below, I wish to set the Owner on my Cat object. I have the ownerId, but the cats method for requires an Owner object, not a Long. Eg: setOwner(Owner owner)

@Autowired OwnerRepository ownerRepository;
@Autowired CatRepository catRepository;

Long ownerId = 21;
Cat cat = new Cat("Jake");
cat.setOwner(ownerRepository.findById(ownerId)); // What a waste of time
catRepository.save(cat)

I'm using the ownerId to load an Owner object, so I can call the setter on the Cat which is simply going to pull out the id, and save the Cat record with an owner_id. So essentially I'm loading an owner for nothing.

What is the correct pattern for this?

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
sparkyspider
  • 13,195
  • 10
  • 89
  • 133
  • there is no performance cost after the first time an Owner is loaded by ID. From the second onwards you will hit the L2 cache instead of hitting the DB – Alboz Nov 05 '15 at 22:38

5 Answers5

9

First of all, you should pay attention to your method to load an Owner entity.

If you're using an Hibernate Session :

// will return the persistent instance and never returns an uninitialized instance
session.get(Owner.class, id);

// might return a proxied instance that is initialized on-demand
session.load(Owner.class, id);

If you're using EntityManager :

// will return the persistent instance and never returns an uninitialized instance
em.find(Owner.class, id);

// might return a proxied instance that is initialized on-demand
em.getReference(Owner.class, id);

So, you should lazy load the Owner entity to avoid some hits to the cache nor the database.

By the way, I would suggest to inverse your relation between Owner and Cat.

For example :

Owner owner = ownerRepository.load(Owner.class, id);
owner.addCat(myCat);
victor gallet
  • 1,819
  • 17
  • 25
4

Victor's answer is correct (+1 from me), but requires going through the EntityManager or Hibernate session. Assuming the repositories you have autowired are JPA repositories from Spring Data and you would prefer to go through them, use the JpaRepository#getOne method. It calls EntityManager#getReference, so it does the same thing, returning a proxy to the entity.

I do not think the relationship necessarily needs to be reversed here, which mapping to use depends on the situation. In many cases many-to-one is preferred.

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
2

Probably not what you were looking for, but nothing in your question implies that you have to solve this with JPA. Some things are just much much simpler with plain old SQL:

INSERT INTO cat (name, owner_id) VALUES ('Jake', 21)
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
2

If you are using Hibernate you can do this:

Long ownerId = 21;
Cat cat = new Cat("Jake");
Owner owner = new Owner();
owner.setId(ownerId);
cat.setOwner(owner);
catRepository.save(cat)

It's not standard JPA, but, if you are not willing to migrate to other JPA provider, it's the best from a performance perspective.

Update

As Nathan pointed out, you need to make sure the Owner is not already associated (in which case you can get a NonUniqueObjectException since the Persistence Context can have at most one entity associated in the 1st level cache).

Using EntityManager.contains(entity) doesn't help in this case, since Hibernate stores the entities in an IdentiyHashMap, where the key is the Object reference itself.

So you should use this method when, for example, you have a use case where you must insert these entities for the first time, or when you need to update them and the Owner hadn't been loaded in the current running Persistence Context (either directly or through JPQL or a Criteria API).

Otherwise, use EntityManager.getReferemce(Class entityClass, Object primaryKey).

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • @Vlad Mihalcea If he uses the entity manager to retrieve a record by ID, after the first time it will hit the L2 cache so there is no performance implication. – Alboz Nov 05 '15 at 22:35
  • 1
    The 2nd level cache is disabled by default in Hibernate and the OP doesn't mention it at all. – Vlad Mihalcea Nov 06 '15 at 02:55
1

One more way (can come handy sometimes in legacy code or db schema):

@Entity
public class Cat {
  @Column(name = "OWNER_ID")
  private Long ownerId;

  @ManyToOne
  @JoinColumn(name = "OWNER_ID", insertable = false, updatable = false)
  private Owner owner;
}
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110