Entity with CascadeType.ALL in OneToMany not persisting children - jpa

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.

Related

How to modify / delete owned object from entity in EF core 6

I'm building an Asp.Net core Api project.
I have a Parent entity as Aggregate root. That parent entity has a collection of Owned types.
If instead of collection of Owned types I have collection of Entities, I would simply tell the parent to delete the entity by it's Id like this:
// Parent entity method
public void DeleteChild(int childId)
{
var existing = this._children.FirstOrDefault(x=>x.Id == childId);
...
this._children.Remove(existing);
}
How to delete the owned object in the collection of an aggregate (owned entity does not have an Id property)?
//Parent entity method
public void DeleteChild(?????????) //Don't know what to put here, a surrogate key?
{
...
}
The model is something like this:
public class Parent
{
public int Id {get;set;}
public string Name {get;set;}
public List<Child> Children {get;set;}
... //other non important props
}
public class Child
{
public string Name {get;set;}
... //other non important props
}
And the mapping:
public class ParentEntityConfiguration : IEntityTypeConfiguration<Parent>
{
public void Configure(EntityTypeBuilder<Device> builder)
{
builder.ToTable("Parents");
builder.HasKey(x => x.Id);
builder.OwnsMany<Child>("Children", cfg =>
{
cfg.ToTable("Children");
cfg.WithOwner().HasForeignKey("ParentId");
cfg.HasKey("ParentId", "Name");
cfg.HasIndex("ParentId", "Name").IsUnique();
});
}
}
So there is no "Id" propety in the Child nor the "ParentId" as they are shadowed.
The same goes for modifying the Owned object.
What is the best practice for this?
Thanks
P.S. I'm using EF Core 6.x
Edit: Given the added details in the question.
You are using a composite key for your child which is a combination between the child's name and the parentid. Since child is a owned type it will be automatically included when you retrieve the object of the parent. (If you didn't change the configuration AutoInclude)
So if you get your parent object like:
var parent = dbcontext.Parent.Where(p => p.id = idParent);
//find the one you want to eliminate by name.
var child = parent.children.First(item => item.name == "foo").value;
//remove the child from the list
parent.children.Remove(child);
//save changes
dbcontext.SaveChangesAsync();
================================================================
OLD:
You just have to find the parent first. Then you can iterate through the childrens and delete the ones that you want. Then just call the save changes and they should be removed.
Example:
// Parent entity method
public void DeleteChild(int childId, idParent)
{
var parent= dbcontext.Parent.FirstOrDefault(x=>x.Id == idparent);
for (int i = 0; i < parent.Children.length; i++)
{
if (collection[i].id = childId)
collection[i] = null;
}
dbcontext.SaveChangesAsync();
}
You need to reference the value-object by itself:
public void DeleteChild(Child child)
{
// redacted
}

Update reference in existing ChildEntity on ParentEntity save

I have some Child entities that are already stored in the database. At some point, the user selects some entries adds them to a Parent and saves that Parent. The reference to the Parent in the Child does not get updated when calling the JpaReposity.save function of the Parent. Do I need to manually update/save every child entity in the database?
Parent
#Entity
public class Parent extends Base {
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}, mappedBy = "parent")
private List<Child> children;
public List<Child> getChildren() {
return Collections.unmodifiableList(children);
}
public void addChild(Child child) {
if(this.children== null) { this.children= new ArrayList<>(); }
this.children.add(child);
child.setParent(this);
}
}
Child
#Entity
public class Child extends Base{
#ManyToOne
private Parent parent;
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent= parent
if(!parent.getChildren().contains(this)) {
parent.getChildren().add(this);
}
}
}
Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {}
Service
#Transactional
Parent createAndSaveParent() {
List<Child> children = this.childRepo.findAll();
Parent parent = new Parent();
children.forEach(c -> parent.addChild(c));
return this.parentRepo.save(parent);
}
I am not getting any error, if I look at the returned Parent object after calling save, the children have the corrent parent set, but in the database the reference doesn't get updated.
Yes because of
mappedBy = "parent"
this means that the relationship is maintained by parent in the Child class and is responsible for setting the foreign key.

jpa repository delete children without loading parent collection

I am trying to delete a Child Entity using Child's Repository. I do not want to load the whole Collection of Child in Parent and remove a Child from there because the collection is huge in some cases and can cause memory issues. But after I delete a child when I load the Parent using Parent Repository I get an error that says "Deleted Entity passed to persists".
#Entity
#Table(name="USR")
public class User {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval=true)
private Set<UserApplication> userApplications = new HashSet<UserApplication>();
}
#Entity
#Table(name="USR_2_APL")
public class UserApplication {
#ManyToOne
#JoinColumn(name = "USR_SK")
private User user;
}
#Test
public void testDeleteUserApp() {
List<UserApplication> removedUserApp = userApplicationRepository.findByUserSkAndApplicationSk(1, 5);
userApplicationRepository.delete(removedUserApp);
//*****This is where I see an error that says
//org.springframework.orm.jpa.JpaObjectRetrievalFailureException: deleted entity passed to persist: [UserApplication#<null>]; nested exception is javax.persistence.EntityNotFoundException
userRepository.findByUserLoginName(loginId);
}
I donot know if this will help you but I have something similar and this is what I do to delete the data...
In the repository I have a method like this:-
#Transactional
public Long deleteByByUserSkAndApplicationSk(int userSk, int applicationSk);
The output of the method is the number of rows deleted.
Then you can directly call the method where ever you want to delete.

OpenJPA - lazy fetching does not work

I have a specific problem with an unit test using embedded OpenEJB container. I have a bi-directional relation between two classes. In one direction the relation works properly, but in the opposite direction the relation works only in EAGER-mode. In LAZY-mode the field section stays null. The code snipped follows:
#Entity
#Table(name="tracks")
class TrackEntity implements Track {
#Id
private int trackNumber;
#OneToMany(mappedBy = "track")
private HashSet<SectionEntity> sections;
public TrackEntity() {
sections = new HashSet<SectionEntity>();
}
#Override
public Collection<HistoricalEvent> getEvents() {
if (sections == null)
throw new CommonError("number=" + trackNumber, AppErrors.TRACK_EMPTY);
TreeSet<HistoricalEvent> set = new TreeSet<HistoricalEvent>();
for (SectionEntity se : sections)
set.addAll(se.getEvents());
return set;
}
}
My code is little bit specific. The class uses the field sections just internally to merge all sub-collections. I'm unable to fill sections lazily. I thing, the container expects client to access the field externally via a getter.
Its the problem with life cycle of enties. All enties (track and its sections) must be re-attached to the persistence context. The method collecting events must be in the class using EntityManager. (The entity cannot use the manager to re-attach itself.) Example of updated entity managing class follows:
public class EntityDataAccessor {
#PersistenceUnit(unitName = "someUnit")
private EntityManagerFactory emFactory;
//gets one track
public Track getTrack(int number) {
EntityManager em = emFactory.createEntityManager();
try {
return (Track)em.find(TrackEntity.class, number);
}
finally {
em.close();
}
}
//the new method collecting events
public Collection<HistoricalEvent> getEventsForTrack(TrackEntity te) {
EntityManager em = emFactory.createEntityManager();
te = em.merge(te); //re-attach to the context
Set<SectionEntity> sections = te.getSections();
TreeSet<HistoricalEvent> set = new TreeSet<HistoricalEvent>();
for (SectionEntity se : sections) {
se = em.merge(se); //re-attach to the context
set.addAll(se.getEvents());
}
em.close();
return set;
}
}
See question What's the lazy strategy and how does it work? for more detail.

JPA2.0: Delete Entity in OneToMany RelationShip

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);
}