JPA2.0: Delete Entity in OneToMany RelationShip - jpa

How do I delete an entity in a OneToMany relationship.
#Entity
#NamedQueries({
#NamedQuery(name="User.findByUserNamePassword",
query="select c from User c where c.userName = :userName AND c.password = :password")
})
#Table(name="\"USER\"")
public class User implements Serializable {
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemove=true)
private List<Profession> professions;
public List<Profession> getProfessions() {
return professions;
}
public void setProfessions(List<Profession> professions) {
this.professions = professions;
}
public void addProfession(Profession profession){
if(this.professions == null){
this.professions = new ArrayList<Profession>();
}
this.professions.add(profession);
profession.setUser(this);
}
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null);
}
}
}
Inside Profession Entity
#Entity
public class Profession implements Serializable {
#ManyToOne
#JoinColumn(name="UserId", nullable=false)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
Then inside my EJB I have this
#Stateless
#LocalBean
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ScholarEJB{
/**
* Add a profession to a target user
* #param user
* #param profession
*/
public void addProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.addProfession(profession);
this.create(user); //This is persist action
}
public void removeProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.remove(user);
this.update(user); //merge action
//this.create(user) //also try this as well, but it does not work
}
}
Now addProfession work beautifully, but removeProfession does not work. Not sure why? Help please. Do I need to evict caches?

If professions are only part of this relationship, then you can guarantee that when a profession is removed from the User's set it will also be removed from the database by turning on orphanRemoval on the OneToMany side of the relationship.
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Profession> professions;
This is what the JPA 2.0 specification states
The JPA 2.0 specification states that
Associations that are specified as
OneToOne or OneToMany support use of
the orphanRemoval option. The
following behaviors apply when
orphanRemoval is in effect:
If an entity that is the target of the
relationship is removed from the
relationship (by setting the
relationship to null or removing the
entity from the relationship
collection), the remove operation will
be applied to the entity being
orphaned. The remove operation is
applied at the time of the flush
operation. The orphanRemoval
functionality is intended for entities
that are privately "owned" by their
parent entity. Portable applications
must otherwise not depend upon a
specific order of removal, and must
not reassign an entity that has been
orphaned to another relationship or
otherwise attempt to persist it. If
the entity being orphaned is a
detached, new,or removed entity, the
semantics of orphanRemoval do not
apply.
If the remove operation is applied to
a managed source entity, the remove
operation will be cascaded to the
relationship target in accordance with
the rules of section 3.2.3, (and hence
it is not necessary to specify
cascade=REMOVE for the
relationship)[20].

My guess as to what is occurring is that your User has a OneToMany relationship to Profession and you user object has the profession. When you delete the Profession the user still has the reference. Because the mapping is cascade persist, it re persists the Profession.
You need to ensure that you remove the profession from the user's professions before deleting it.
If you are using EclipseLink there is a property that may also help, but fixing your code to maintain your model correctly is the best solution. You could also remove the cascade persist.
"eclipselink.persistence-context.persist-on-commit"="false"
or,
"eclipselink.persistence-context.commit-without-persist-rules"="true"

I just added orphanRemoval = true in the OneToMany relationship and I resolved it.
Class SolicitudRetorno:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;
Class RetornoMenor:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;

You might try clearing the user field in profession:
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null); // disassociate profession from user
}
}
To be on the safe side, I would also check that the passed in profession's current user equals this, just in case someone passes in a profession belonging to another user.

This is the solution to my original question, however, I do not know if this is the best
My EJB bean
#PersistenceContext(unitName="Bridgeye2-ejbPU")
private EntityManager em;
public <T> T create(T t) {
em.persist(t);
return t;
}
public <T> T find(Class<T> type, Object id) {
return em.find(type, id);
}
public <T> void delete(T t) {
t = em.merge(t);
em.remove(t);
}
public <T> void removeAndClearCaches(T t){
this.delete(t);
clearCaches();
}
public <T> T update(T t) {
return em.merge(t);
Now in my Managed Bean, I do this
/**
* Add a new profession
*/
public void addNewProfession(){
Profession profession = new Profession();
newProfessions.add(profession);
}
/**
* Remove the profession
* #param profession
*/
public void removeProfession(Profession profession){
//This will remove the `profession` of the list
//at the presentation layer
this.myProfessions.remove(profession);
//This will remove the `profession` of the list
//at the persistence layer
scholarEJB.removeAndClearCaches(profession);
}

Related

Entity with CascadeType.ALL in OneToMany not persisting children

I'm working with a 3rd party library provided to our team where one of the entities has a OneToMany relationship to entities of the same type of itself. I've changed the entity name to keep it anonymous.
Probably there's a better way of annotating entities with this type of relationship but as it's provided by a 3rd party I'm avoiding making to many changes so that it's compatible with future patches and updates.
It's using OpenJPA 2.4.0-ep2.0
#Entity
#Table(name = Person.TABLE_NAME)
public class Person {
private Long parentUid;
private List<Person> children = new ArrayList<>();
#OneToMany(targetEntity = Person.class, cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
#ElementJoinColumn(name = "PARENT_UID")
#ElementForeignKey
#ElementDependent
public List<Person> getChildren() {
return this.children;
}
}
When I try to persist a person with children, only the main entity gets persisted and children ignored.
However, if I change the fetch attribute to FetchType.EAGER it works (it persists both the parent and children). My understanding was that the fetch type only affects the loading, not the inserting. Any ideas why is it happening?
Also, is there a way of making it work while keeping the fetch type to FetchType.LAZY?
I've tried the following (modify the setter):
protected void setChildren(final List<Person> children) {
if (Objects.nonNull(children)) {
for (Person child : children) {
child.setParentUid(parentUid);
}
this.childItems = children;
} else {
this.childItems = new ArrayList<>();
}
}
Problem is in the child entity ,you should use #ManyToOne annotation in child entity.
add following code to Person :
public class person {
.
.
#MantToOne(fetch=FetchType.LAZY)
#JoinClolumn(name="PARENT_UID")
private Person parent;
public void setParent(Person parent){
}
.
.
}
then revise setChildren Code like this:
protected void setChildren(final List<Person> children) {
if (Objects.nonNull(children)) {
for (Person child : children) {
child.setParent(this);
}
this.childItems = children;
} else {
this.childItems = new ArrayList<>();
}
}
one important point is ،always fetch type must be sync in parent and child.

Check entity existence before calling merge

I have a simple abstract DAO, and I have created the following method:
protected T update(T entity) {
return em.merge(entity);
}
where the entity is just any object annotated with #Entity in my application. Now... I want to throw an exception if you try to update a non existing object. I was going to perform a find before the merge, throwing an exception if the find operation returns null and merging if the entity exists. I was wandering if a better way exists for doing this.
A possible solution: You can do a check based on your primary key. An entity must (should?) have an #Id field:
#Entity
public class Entity implements EntityInterface{
#Id
private Long id;
#Override
public Long getId(){
return this.id;
}
}
with the interface
public interface EntityInterface{
public Long getId();
}
By default, when you instantiate your entity, id is null and a value is assigned only after persisting in the database: The id will be generated by the method you defined via #GeneratedValue. Consequently, the following check should meet your requirement:
public abstract class AbstractService<T extends EntityInterface>{
protected T update(T entity){
// if by any chance you have to call this method on an entity with a null
// primary key, it means that the entity has not been persisted in the
// database yet
if(entity.getId() == null){
// or whatever
return null;
}
return em.merge(entity);
}
}
Hope this help
Source: JB Nizet's comment and personal code

OpenJPA auditing capabilities

I have a webservice that will be persisting and deleting data to a Database. I want to track in the database which usernames touched which rows of the database. In each table there are columns for usernames to be stored (update columns if you will). There are also triggers on the tables that will take a userID for the transaction and update that table with the username and password that attempted to insert. Is there a way in open JPA where I can get the username (which will be passed from the client) and update some kind of JPA object so that when JPA persists data, that user name will be thrown into the table?
One of the cleanest ways is to implement a common "mapped" superclass for your entities and use a method with #PrePersist annotation to populate the fields.
#MappedSuperclass
public class AuditedEntity {
#Id protected Integer id;
protected String lastUpdatedBy;
// Setters and getters here
#PreUpdate
#PrePersist
public void onChange() {
String user = .... // Do whatever is needed to get the current user
setLastUpdatedBy(user);
}
}
#Entity
public class Employee extends AuditedEntity {
// ....
}
Another option is to use a separate listener:
public interface AuditedEntity {
public static void setLastUpdatedBy(String username);
}
#Entity
#EntityListeners({ MyLogger.class, ... })
public class Employee implements AuditedEntity {
// ...
}
public class MyLogger {
#PreUpdate
#PrePersist
public void onChange(Object o) {
if(o instanceof AuditedEntity) {
String user = .... // Do whatever is needed to get the current user
((AuditedEntity) o).setLastUpdatedBy(user);
}
}
#PostPersist
#PostUpdate
public void logChange(Object o) {
// Log the successful operation
}
}

Get eagerly collection of entities containing other eagerly got collections

I've got stuck on the M:N relation between entity and strings. An user can have more than one role and each role can be assigned to more than one user. Role is just a string. Roles are contained in table with two columns: roleId and roleName.
I've created two entities, but I'm absolutely unable to made it work. First entity is the user:
#Entity
#Table(name="appUsers")
public class UserEntity {
#Id
private String login;
private String password;
#OneToMany(fetch=FetchType.EAGER,mappedBy="user") //we always need to load user's roles
private Collection<UsersToRoles> roles;
#Transient
private Collection<String> roleNames;
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
#PostLoad
void prepareRoleNames() {
roleNames = new HashSet<String>(roles.size());
for (UsersToRoles mapping : roles)
roleNames.add(mapping.getNameOfRole());
}
public Collection<String> getRoles() {
return roleNames;
}
}
The second is entity associated with connecting table:
#Entity
#IdClass(UsersToRolesId.class)
public class UsersToRoles {
#Id
#SuppressWarnings("unused")
#Column(name="login")
private String login;
#Id
#SuppressWarnings("unused")
#Column(name="roleId")
private int roleId;
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name="userRoles", joinColumns={#JoinColumn(name="roleId")})
private List<String> roleName;
#ManyToOne
#JoinColumn(name="login")
#SuppressWarnings("unused")
private UserEntity user;
public String getNameOfRole() {
if (roleName.isEmpty())
throw new CommonError("Role name for roleId=" + roleId, AppErrors.ACCESSOR_UNAVAILABLE);
return roleName.get(0);
}
}
class UsersToRolesId {
private String login;
private int roleId;
/**
* Implicit constructor is not public. We have to
* declare public non-parametric constructor manually.
*/
public UsersToRolesId() {
}
#Override
public int hashCode() {
return 17*login.hashCode() + 37*roleId;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof UsersToRolesId))
return false;
UsersToRolesId ref = (UsersToRolesId)obj;
return (this.login.equals(ref.login) && this.roleId == ref.roleId);
}
}
And the problem is, that the roleName collection is always null. I'm unable to get it work. When I make a mistake in table name in #CollectionTable annotation, it still works. The JPA does not fetch the subcollection at all. It makes select from table of user joined with table UsersToRoles, but the join to table userRoles is missing.
Can I ever do that? Can I get eagerly collection of entities containing another eagerly fetched collections?
Your mapping is completely wrong. UsersToRoles has a roleId column. Thus it refers to a single role. How could it have a collection of role names? The login column is mapped twice in the entity. Moreover, this looks like a simple join table to me, without any other attribute than the roleId and the login, which are foreign keys to the IDs of User and Role, respectively.
You should have two entities : User and Role, with a ManyToMany association using the UsersToRoles table as join table. That's it. The UsersToRoles table should not be mapped as an entity: it's a pure join table.
JPA providers usually have a configuration property denoting default eager fetch depth, i.e. hibernate.max_fetch_depth for Hibernate. Check if you can see more when you increase it.
Also, think about your design. Fetching subcollections of a collection eagerly might be a good idea only in limited scenarios (performance-wise). When you annotate your entity like that, you're going to use eager fetching in all use cases. Perhaps you'd be better off with "lazy" and fetching it eagerly only explicitly, with a query with a JOIN FETCH clause?

JPA merge readonly fields

We have the simplest CRUD task with JPA 1.0 and JAX-WS.
Let's say we have an entity Person.
#Entity
public class Person
{
#Id
private String email;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private ReadOnly readOnly;
#Column
private String name;
#XmlElement
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#XmlElement
public Long getReadOnlyValue()
{
return readOnly.getValue();
}
// more get and set methods
}
Here is scenario.
Client make Web Service request to create person. On the server side everything is straightforward.
And it does work as expected.
#Stateless
#WebService
public class PersonService
{
#PersistenceContext(name = "unit-name")
private EntityManager entityManager;
public Person create(Person person)
{
entityManager.persist(person);
return person;
}
}
Now client tries to update person and this is where, as for me, JPA shows its inconsistence.
public Person update(Person person)
{
Person existingPerson = entityManager.find(Person.class, person.getEmail());
// some logic with existingPerson
// ...
// At this point existingPerson.readOnly is not null and it can't be null
// due to the database.
// The field is not updatable.
// Person object has readOnly field equal to null as it was not passed
// via SOAP request.
// And now we do merge.
entityManager.merge(person);
// At this point existingPerson.getReadOnlyValue()
// will throw NullPointerException.
// And it throws during marshalling.
// It is because now existingPerson.readOnly == person.readOnly and thus null.
// But it won't affect database anyhow because of (updatable = false)
return existingPerson;
}
To avoid this problem I need to expose set for readOnly object and do something like this before merge.
Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!
My questions:
Is it a feature or just
inconsistence?
How do you (or would
you) handle such situations? Please
don't advice me to use DTOs.
Is it a feature or just inconsistence?
I don't know but I'd say that this is the expected behavior with merge. Here is what is happening when calling merge on a entity:
the existing entity gets loaded in the persistence context (if not already there)
the state is copied from object to merge to the loaded entity
the changes made to the loaded entity are saved to the database upon flush
the loaded entity is returned
This works fine with simple case but doesn't if you receive a partially valued object (with some fields or association set to null) to merge: the null fields will be set to null in the database, this might not be what you want.
How do you (or would you) handle such situations? Please don't advice me to use DTOs.
In that case, you should use a "manual merge": load the existing entity using find and update yourself the fields you want to update by copying the new state and let JPA detect the changes and flush them to the database.