Reputation: 7449
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
):
project.projectType
is DETACHEDproject
is NEWI am not asking about the difference between persist()
and merge()
, I'm asking if either
em.persist(project)
automatically "reattach" project.projectType
(I suppose not)em.persist(project)
then em.merge(projectType)
or if it should be invertedem.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
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".
}
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
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
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