1

Here are the relevant pieces of the code I inherited. The object "process" is the old process that is passed to the method. The object "newProcess" is what I am replacing it with, using different fields of the user's choosing.

try
{
   final EntityManager em = getEntityManager();
   em.getTransaction().begin();
   JpaProcessDAO pDao = new JpaProcessDAO(em);
   Process newProcess = pDao.findById(processId);

    newProcess.setName(process.getName());
    newProcess.setDataBaseVersion(process.getDataBaseVersion());
    newProcess.setNotes(process.getNotes());
    newProcess.setReadyForUse(process.getReadyForUse();
    newProcess.setSteps(process.getSteps());
    em.merge(newProcess);   <---- WHERE PROBLEM OCCURS
    em.persist(newProcess);
    em.getTrasaction().commit();
}

RESULT: Every field that I change is changed in newProcess EXCEPT "Steps". During the merge step in the code, that list goes back to whatever the steps were in the original object "process".

Now this could be because that "Step" is an object itself, not a primitive like all of the other fields I set in "newProcess":

Mapping in Process.java

@OneToMany(mappedBy="process")
private List<Step>
// getter, setter

In Step.java there is a collection of objects, some of which are lists of nonprimitive objects themselves.

Step.java

public class Step implements Serializable {
    @Id
    @Column(name = "step_id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int stepId;

    private String duration;
    private String name;
    private String notes;
    private Integer sort;

    @OneToMany(mappedBy="step", cascade=CascadeType.REMOVE)
    private List<Constituent> constituents;

    @OneToMany(mappedBy="step")
    private List<Reference> references;

    @ManyToOne
    @JoinColumn(name ="process_id")
    private Process process;

    @OneToMany(mappedBy="step",cascade=CascadeType.REMOVE)
    private List<StepEquipment> stepEquipments;

    public Step() {
    }

    // getters/setters
}

Does anybody know what this inherited code I have could possibly do wrong?


ADDITIONS TO CODE ON 11/29:

public T findById(final Integer id) throws CPDPersistenceExceptin {
   return findByPrimaryKey(id,templateClass);
}

public T findBYPrimaryKey(Object key, Class<T> clazz)  {
   T t = getEntityManager().find(clazz,key);
   getEntityManager.merge(t);
   getEntityManager.refresh(t);
   return t; <--------------  newProcess is returned by this statement.
}

newProcess does not have the steps that were in the original process,nor does it have the ProcessCategories that were in process. The Hibernate logs say that select is going on for process_id, database_version, process_name, process_notes, and process_ready_to_use only in the merge and refresh statements.

calvinnme
  • 17
  • 6
  • 1
    Maybe you want to reveal the code for entity `Step`? – pirho Nov 16 '18 at 18:41
  • In the original post I added most of the code for entity Step, as requested. – calvinnme Nov 16 '18 at 19:08
  • Possible duplicate of [JPA - difference in the use of the mappedBy property to define the owning entity](https://stackoverflow.com/questions/10968536/jpa-difference-in-the-use-of-the-mappedby-property-to-define-the-owning-entity) – crizzis Nov 16 '18 at 20:50
  • What does findById do - is it using any special options like a refresh call? What are the relationships that point back to this progress entity, as it seems likely one of them is being merged in this graph and because it is a different instance (i.e. in the steps graph) it doesn't have your changes and so overwrites them. – Chris Nov 16 '18 at 22:45

2 Answers2

0

You need to synchronize both sides of the association. In your code you're only setting newProcess.setSteps(...), but each Step doesn't set a Process. From here:

However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.

So in other words, you would need to do something along the lines of:

newProcess.setSteps(process.getSteps());
process.getSteps().forEach(s -> s.setProcess(newProcess));
dyslexit
  • 689
  • 1
  • 9
  • 18
  • Thanks I was wondering how exactly to use the other post for my problem. I will try setting the process of each step to newProcess. What actually happens after the merge is that there are place holders for the steps that were added in newProcess, but the name of the step is now null. – calvinnme Nov 16 '18 at 22:58
  • So I did what dyslexit suggested and added the process.getSteps step...The program still behaves as before. Problem is still not fixed. – calvinnme Nov 16 '18 at 23:28
  • Can you describe what happens after the merge a little more? I'm confused by "During the merge step in the code, that list goes back to whatever the steps were in the original object "process"." Are you seeing the relationships being updated correctly thin the database? If not, I would recommend turning on `spring.jpa.properties.hibernate.show_sql = true` and `spring.jpa.properties.hibernate.format_sql = true` in your application properties for debugging to see what's going on. – dyslexit Nov 16 '18 at 23:48
  • @pirho Did you mean to respond to calvinnme? I think you may have responded on the wrong answer ;) – dyslexit Nov 28 '18 at 23:43
  • @dyslexit Yes :D . Thanks for notifying me. – pirho Nov 29 '18 at 06:15
0

As in answer from dyslexit told you need to set the Process to each Step.

But in addition you need to have the new Steps persisted and old ones removed. You can do this manually per Step but easier way would be to alter your code a bit.

Mofify the mapping annotation in step like:

@OneToMany(mappedBy = "process", cascade=CascadeType.PERSIST, orphanRemoval=true)
private List<Step> steps;

so tell persist to cascade to Steps also and to remove all Steps that are detached from Process.

Modify the update logic:

// newProcess.setSteps(process.getSteps());
// em.merge(newProcess);   <---- WHERE PROBLEM OCCURS
// em.persist(newProcess);

newProcess.getSteps().clear(); // remove old steps
newProcess.getSteps().addAll(process.getSteps()); // add new steps
// You need to set the other side of association also as below
newProcess.getSteps().forEach(s -> s.setProcess(newProcess));
// em.persist(newProcess); // not sure if needed

SO: do not REPLACE the list but instead MODIFY the original list.

ALSO: there might not be a need for any merge/persist operation (and certainly doing both in series is not something that should ever be done). But because you use mystical JpaProcessDAO I can not be sure so check that.

And also see for what those are really used, great explanation here.

I am guessing that entity manager might handle everything just fine - without persist/merge stuff -because I think you already got managed entity when called pDao.findById(processId);, that is why I have commented it out.

Another story is then the mappings you have in your Step class. Those might also need changes to persistence & cascade setting.

As a side note: have also a look at this question how you might have update done easier with ModelMapper.

pirho
  • 11,565
  • 12
  • 43
  • 70
  • I am unclear on some things. Should I leave dyslexit's stuff in? And where did steps2 come from? Thanks for your time. – calvinnme Nov 19 '18 at 19:57
  • @calvinnme Step2 was copy/paste typo, sorry for that. Yes, the other side of association is also needed, see my updates. – pirho Nov 19 '18 at 20:41
  • I tried this yesterday. The problem is that the line "newProcess.getSteps().forEach(s ->s.setProcess(newProcess)" nulls out a parameter that is entitled "ProcessCategories", because newProcess does not have this parameter set. Should I change the getSteps....lines to be after everything else has been modified? Thanks for your time. – calvinnme Nov 27 '18 at 15:35
  • @calvinnme Well, you need to set it like you set any other field. You fetch `newProcess` with `pDao` and if it is not in db then where? – pirho Nov 27 '18 at 16:42
  • I am not quite sure why ProcessCategories is not in newProcess when it is cloned from process. Hopefully I can answer tomorrow when I have my code. The internet at work is useless right now or else I would have answered this afternoon. Thanks for replying. – calvinnme Nov 28 '18 at 03:46
  • Note that in your code is no clone used. You fetch newProcess from db and re-populate some fields. – pirho Nov 29 '18 at 06:14
  • I added some code to original post to clarify where newProcess comes from. – calvinnme Nov 29 '18 at 16:58