Michele Mariotti
Michele Mariotti

Reputation: 7449

JPA handle merge() of relationship

I have a unidirectional relation Project -> ProjectType:

@Entity
public class Project extends NamedEntity
{
    @ManyToOne(optional = false)
    @JoinColumn(name = "TYPE_ID")
    private ProjectType type;
}

@Entity
public class ProjectType extends Lookup
{
    @Min(0)
    private int progressive = 1;
}

Note that there's no cascade.

Now, when I insert a new Project I need to increment the type progressive.

This is what I'm doing inside an EJB, but I'm not sure it's the best approach:

public void create(Project project)
{
    em.persist(project);

    /* is necessary to merge the type? */
    ProjectType type = em.merge(project.getType());

    /* is necessary to set the type again? */
    project.setType(type);

    int progressive = type.getProgressive();
    type.setProgressive(progressive + 1);
    project.setCode(type.getPrefix() + progressive);
}

I'm using eclipselink 2.6.0, but I'd like to know if there's a implementation independent best practice and/or if there are behavioral differences between persistence providers, about this specific scenario.


UPDATE

to clarify the context when entering EJB create method (it is invoked by a JSF @ManagedBean):

I am not asking about the difference between persist() and merge(), I'm asking if either

  1. if em.persist(project) automatically "reattach" project.projectType (I suppose not)
  2. if it is legal the call order: first em.persist(project) then em.merge(projectType) or if it should be inverted
  3. since em.merge(projectType) returns a different instance, if it is required to call project.setType(managedProjectType)

An explaination of "why" this works in a way and not in another is also welcome.

Upvotes: 1

Views: 1015

Answers (3)

jabu.10245
jabu.10245

Reputation: 1892

You need merge(...) only to make a transient entity managed by your entity manager. Depending on the implementation of JPA (not sure about EclipseLink) the returned instance of the merge call might be a different copy of the original object.

MyEntity unmanaged = new MyEntity();
MyEntity managed = entityManager.merge(unmanaged);
assert(entityManager.contains(managed));  // true if everything worked out
assert(managed != unmanaged);  // probably true, depending on JPA impl.

If you call manage(entity) where entity is already managed, nothing will happen.

Calling persist(entity) will also make your entity managed, but it returns no copy. Instead it merges the original object and it might also call an ID generator (e.g. a sequence), which is not the case when using merge.

See this answer for more details on the difference between persist and merge.

Here's my proposal:

public void create(Project project) {
    ProjectType type = project.getType(); // maybe check if null
    if (!entityManager.contains(type)) {  // type is transient
        type = entityManager.merge(type); // or load the type
        project.setType(type); // update the reference
    }

    int progressive = type.getProgressive();
    type.setProgressive(progressive + 1); // mark as dirty, update on flush

    // set "code" before persisting "project" ...
    project.setCode(type.getPrefix() + progressive);
    entityManager.persist(project);

    // ... now no additional UPDATE is required after the
    // INSERT on "project".
}

UPDATE

if em.persist(project) automatically "reattach" project.projectType (I suppose not)

No. You'll probably get an exception (Hibernate does anyway) stating, that you're trying to merge with a transient reference.

Correction: I tested it with Hibernate and got no exception. The project was created with the unmanaged project type (which was managed and then detached before persisting the project). But the project type's progression was not incremented, as expected, since it wasn't managed. So yeah, manage it before persisting the project.

if it is legal the call order: first em.persist(project) then em.merge(projectType) or if it should be inverted

It's best practise to do so. But when both statements are executed within the same batch (before the entity manager gets flushed) it may even work (merging type after persisting project). In my test it worked anyway. But as I said, it's better to merge the entities before persisting new ones.

since em.merge(projectType) returns a different instance, if it is required to call project.setType(managedProjectType)

Yes. See example above. A persistence provider may return the same reference, but it isn't required to. So to be sure, call project.setType(mergedType).

Upvotes: 2

gerrytan
gerrytan

Reputation: 41113

Do you need to merge? Well it depends. According to merge() javadoc:

Merge the state of the given entity into the current persistence context

How did you get the instance of ProjectType you attach to your Project to? If that instance is already managed then all you need to do is just

type.setProgessive(type.getProgressive() + 1)

and JPA will automatically issue an update effective on next context flush.

Otherwise if the type is not managed then you need to merge it first.

Although not directly related this quesetion has some good insight about persist vs merge: JPA EntityManager: Why use persist() over merge()?

With the call order of em.persist(project) vs em.merge(projectType), you probably should ask yourself what should happen if the type is gone in the database? If you merge the type first it will get re-inserted, if you persist the project first and you have FK constraint the insert will fail (because it's not cascading).

Upvotes: 0

Ankit
Ankit

Reputation: 603

Here in this code. Merge basically store the record in different object, Let's say One Account pojo is there Account account =null; account = entityManager.merge(account); then you can store the result of this.

But in your code your are using merge different condition like public void create(Project project) { em.persist(project);

/* is necessary to merge the type? */
ProjectType type = em.merge(project.getType());

} here Project and ProjectType two different pojo you can use merge for same pojo. or is there any relationship between in your pojo then also you can use it.

Upvotes: -1

Related Questions