5

I'm having problems when saving entities in my DB.

I have something like (very simplified) :

@Entity
public class Building {

    @OneToMany(mappedBy = "building", fetch = FetchType.EAGER)
    private List<Employee> employees;

}  

@Entity
public class Employee {

    @NotNull
    @ManyToOne
    @JoinFetch(INNER)
    @JoinColumn
    private Building building;

    @PostLoad
    private void onLoad(){
          if (this.plannedOrder == null) {
            //For old entities in this DB, update plannedOrder if null
            if (this.order < FIRST.getCode()) {
                this.plannedOrder = FIRST;
            } else if (this.order >= FIRST.getCode() && this.order < SECOND.getCode()) {
                this.plannedOrder = SECOND;
            } else if (this.order >= DEFAULT.getCode() && this.order < SEC_LAST.getCode()) {
                this.plannedOrder = DEFAULT;
            } else if (this.order >= SEC_LAST.getCode() && this.order < LAST.getCode()) {
                this.plannedOrder = SEC_LAST;
            } else if (this.order >= LAST.getCode()) {
                this.plannedOrder = LAST;
            } else {
                this.plannedOrder = DEFAULT;
            }
    }

}

The problem is when I save a Employee entity. In the Building entity you will have all employees modified because the @PostLoad annotation, so JPA will try to update these entities. However, I only want to update the Employee entity.

Is some way to do this without changing the relations in my model? Could be a solution to remove the onLoad function?

Thanks!

EDITED:

Added the PostLoad code of PostLoad. This is a fix executed for old entities in DB without this field to be updated when saved.

EDITED 2

And this is how I'm saving my entity Employee with my EntityManager (if is a new Object, persist, else update it)

    if (entity.getId() == null) {
        entityManager.persist(entity);
        return entity;
    }
    return entityManager.merge(entity);        
jpadilladev
  • 1,756
  • 4
  • 16
  • 23
  • You haven't defined any cascade here. Show the code of @PostLoad.. – Tobb Jan 20 '16 at 10:32
  • There is nothing in your supplied code that would cascade an update of one `Employee` to `Building` or the other `Employee`s of the `Building`. Show have you "save" the `Employee`. – Tobb Jan 20 '16 at 10:52
  • Yeah, I'm agree with you. I added how I'm saving this entity. – jpadilladev Jan 20 '16 at 11:02
  • 1
    Could it be that you are in a transaction that also modifies the other `Employee`s? Remember that any changes to retrieved objects within a transaction will be persisted, without the need to call the `merge`-method. Also, when you say that employees are "modified", what does this mean? – Tobb Jan 20 '16 at 11:26
  • When I said the Employee entities are modified I mean the changes made by the PostLoad method when retrieving a Building from my Employee entity. I don't think that I'm changing any Employee inside a Transaction, only the PostLoad, but I will take a closer look. – jpadilladev Jan 20 '16 at 11:44
  • I see, I'll add an answer then.. – Tobb Jan 20 '16 at 11:46

2 Answers2

2

Just extending what Tobb answered a little: This is occurring because you are loading in an Employee with the merge call. This entity has a 1:1 relationship to building which defaults to be Eagerly fetched, forcing building to also be loaded.

Building then also has a 1:M to Employees, and you have marked this with fetch = FetchType.EAGER, forcing all employees to also be loaded.

If you do not want all employees loaded, thereby forcing JPA to call their postLoad methods, you need to prevent one or more of these relationships from being loaded. Options:

  1. Mark one or more as fetch = FetchType.LAZY
    • This is trivial for collections, but with 1:1 mappings can require additional support. EclipseLink for instance requires weaving
  2. Remove one or more of the mappings from the entities.
  3. Remove the postLoad logic that changes entities. This doesn't seem like the best spot to manipulate Entities, as any access to them will force updates even though there may be no other changes in the transaction. You might come up with a different strategy, such as a one time bulk update of legacy data, rather then something that affects your application long term. If you must, maybe use @PreUpdate instead

If you choose to use Lazy fetching, this can still be overridden on a query by query basis as required through fetch joins, query hints and or fetch/load groups.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • The problem was the PostLoad inside the same transaction. Removing the PostLoad logic to a Service was the solution. Thx! – jpadilladev Feb 16 '16 at 14:48
1

When loading an Employee-entity, you might also load the Building-entity (depends on your JPA-provider and version, whether fetchtype EAGER or LAZY is the default), and when loading a Building-entity you will also load all Employees connected to it.

This means that loading one Employee-entity will/might load all Employees connected to the same Building, and thus run the @PostLoad-method for all these. When this happens inside a transaction, I would assume that the changes made by the @PostLoad-method are persisted when the transaction commits.

Tobb
  • 11,850
  • 6
  • 52
  • 77