Entity with orphanRemoval not being dropped - java

I have a set entities such as:
public class EntityToDrop {
#NaturalId
#ManyToOne(fetch = FetchType.EAGER)
private ParentEntity parentEntity;
#OneToMany(mappedBy = "entityToDrop", cascade = {CascadeType.REMOVE, CascadeType.REFRESH}, orphanRemoval = true)
private Set<OtherEntity> otherEntities= new HashSet<>();
[...] (other fields and relationships)
}
public class ParentEntity {
#OneToMany(mappedBy = "parentEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<EntityToDrop> childEntities = new HashSet<>();
[...]
}
public class OtherEntity{
#ManyToOne(optional = true,fetch = FetchType.EAGER)
#NaturalId
private EntityToDrop entityToDrop;
[...]
}
In some part of the code I'm calling
parentEntityInstance.getChildEntities().remove(entityToDrop);
[...]
entityManager.persist(parentEntityInstance);
Which I expect should always remove entityToDrop from the database, since it's marked with orphanRemoval. However, when the relationship with OtherEntity exists it's not getting removed, and more interestingly, if I remove the relationship with OtherEntity then try to remove it from the ParentEntity in the same transaction, it's not getting dropped either.
Is there any case where orphanRemoval = true will not work as I expect it to? And as a followup how would I figure out what's stopping my entity from being removed in this case?

Related

How this #OneToMany relationship should look like when using Map?

I have Order and Dish Entities. Order contains a map of Dish-Integer ( dish and how many times it was ordered ).
But I can't figure out what to write above map when I have a key as an Entity. How join column should look like.
#Entity
public class Order {
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Map<Dish, Integer> dishesQuantity = new HashMap<>();
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
public class Dish {
private Order order;
}
P.S. I would also appreciate it if you could tell me what happens if I skip annotating the join column.
try this
public class Order {
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Map<Dish, Integer> dishesQuantity = new HashMap<>();
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name ="dishes_quantity")
public class Dish {
private Order order;
}
As I know to use #JoinColumnis mapping foreign key. To use #JoinColumn annotation you can set foriegnkey name. If you don't use it. It will be automapping with Entity_ID of opposite entity field.
But I usually set #JoinColumn(name ='') to mapping field clearly.

Entity loses state after calling merge on entitymanager

I'm running into a very annoying issue whereby an entity loses its state after the object is merged into the EntityManager.
In the application there's a "Dossier" with ExpenditureStatements which has a number of expenditures.
These expenditures can be (partially) claimed from multiple debtors.
An ExpenditureStatementClaim is created for the ExpenditureStatement.
An ExpenditureClaim is created for each Expenditure on the ExpenditureStatement.
Both the ExpenditureClaims and the ExpenditureStatementClaim are persisted without any issues.
The expenditures however lose their state after the merge on the entitymanager is called:
em.merge(dossier).
However, the data in each expenditure reverts back to its last state in the database.
I've already tried cascading only top down, i've made changes to equals/hashcode but this didn't change anything.
Does anyone have a clue as to what might be causing this issue?
Dossier:
#Entity
#Table(name = "DOSSIER")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DOSSIER_TYPE", discriminatorType = DiscriminatorType.STRING, length = 48)
public abstract class Dossier {
#OneToMany(mappedBy = ExpenditureStatement.PROP_DOSSIER, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<ExpenditureStatement> expenditureStatements = new ArrayList<ExpenditureStatement>();
}
ExpenditureStatement:
#Entity
#Table(name = "EXPENDITURE_STATEMENT")
public class ExpenditureStatement {
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE})
#JoinColumn(name = "DOSSIER_ID", nullable = false)
private Dossier dossier;
#OneToMany(mappedBy = Expenditure.PROP_EXPENDITURE_STATEMENT, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Expenditure> expenditures = new ArrayList<Expenditure>();
#OneToMany(mappedBy = ExpenditureStatementClaim.PROP_EXPENDITURE_STATEMENT, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureStatementClaim> expenditureStatementClaims = new ArrayList<ExpenditureStatementClaim>();
}
Expenditure:
#Entity
#Table(name = "EXPENDITURE")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "EXPENDITURE_ORIGIN_CD", discriminatorType = DiscriminatorType.STRING, length = 48)
public abstract class Expenditure extends EntityObject<Long> {
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_STATEMENT_ID", nullable = false)
private ExpenditureStatement expenditureStatement;
#OneToMany(mappedBy = ExpenditureClaim.PROP_EXPENDITURE, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureClaim> claims = new HashSet<>();
#NotNull
#Column(name = "EXPENDITURE_STATUS_CD", nullable = false)
#Enumerated(EnumType.STRING)
private ExpenditureStatus status;
public abstract BigDecimal getAmount();
}
ExpenditureStatementClaim:
#Entity
#Table(name = "DEEL_STAAT")
public class ExpenditureStatementClaim {
#NotNull
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "EXPENDITURE_STATEMENT_ID", nullable = false)
private ExpenditureStatement expenditureStatement;
#OneToMany(mappedBy = ExpenditureClaim.PROP_EXPENDITURE_STATEMENT_CLAIM, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureClaim> expenditureClaims = new ArrayList<>();
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "INVOICE_ID")
private Invoice invoice;
}
ExpenditureClaim:
#Entity
#Table(name = "EXPENDITURE_CLAIM")
public class ExpenditureClaim extends EntityObject<Long> {
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_ID", nullable = false)
private Expenditure expenditure;
#Column(name = "AMOUNT", precision = NumberConstants.CURRENCY_PRECISION, scale = NumberConstants.CURRENCY_OPERATION_SCALE)
private BigDecimal amount;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_ST_CLAIM_ID", nullable = false)
private ExpenditureStatementClaim expenditureStatementClaim;
}
The issue was not caused by a problem with the mapping, but due to a bug in the (quite old) version of EclipseLink the project was using, whereby a newly created object would not cascade changes made to an already existing object.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=340802
Just a good reminder that whenever possible you should try to update the version of the dependencies in your project...

On CollectionLoadContext#cleanup, localLoadingCollectionKeys contained [3] entries

I'm still new to JPA and Hibernate and I get following warnings:
WARN [org.hibernate.engine.loading.internal.LoadContexts] (default task-4) HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#50dab521<rs=com.mysql.jdbc.JDBC42ResultSet#44f85804>
WARN [org.hibernate.engine.loading.internal.CollectionLoadContext] (default task-4) HHH000160: On CollectionLoadContext#cleanup, localLoadingCollectionKeys contained [3] entries
The thing is: I have different users, and for some of them (with very small data), I am able log in, but for the others (with a bit more data, but still small) I am not and then the warnings come instead. My debug logs show, that when the user should be loaded, it's null. (google says, it might come from my entity relationships and the fetch types and modes). Hope someone can help me, I have no idea so far.
The following shows my two main entities and the method, which is invoked after subit login button in the view.
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#Entity
public class UserProfile extends StringIdEntity implements Serializable {
private Address address;
private String fname;
private String lname;
#OneToOne(cascade = {CascadeType.ALL}, fetch= FetchType.EAGER)
private PhysicalProfile physicalProfile;
#OneToOne(cascade = {CascadeType.ALL}, fetch= FetchType.EAGER)
private DietPlan dietPlan;
#OneToMany(mappedBy = "user", fetch= FetchType.EAGER, cascade = CascadeType.ALL)
private List<DiaryPage> diaryPages;
#OneToMany(mappedBy = "user",fetch= FetchType.LAZY, cascade = CascadeType.ALL)
private List<Meal> meals;
#OneToMany(mappedBy = "user",fetch= FetchType.LAZY, cascade = CascadeType.ALL)
private List<Invoice> invoices;
#OneToMany(mappedBy = "user",fetch= FetchType.EAGER, orphanRemoval = true)
#Fetch(value = FetchMode.SUBSELECT)
private List<GroceryOrder> orders;
#Entity #IdClass(DiaryPageID.class)
public class DiaryPage implements Comparable{ //extends StringIdEntity {
#Id #ManyToOne
private UserProfile user;
#Id #Temporal(TemporalType.DATE)
private Date date;
private KcalRevenue kcalRevenue;
#OneToMany(mappedBy = "diaryPage", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<Meal> meals;
#OneToMany(mappedBy = "page", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<Grocery> groceries;
#OneToMany(mappedBy = "diaryPage", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<Workout> workouts;
#OneToMany(mappedBy = "diaryPage", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<Activity> activities;
public UserProfile checkUserLogin(String email, String password) {
UserProfile user = em.find(UserProfile.class, email);
if(user != null) {
try {
if(EntityUtils.hashPassword(password, user.getSalt(), UserProfile.HASH_ALGORITHM).equals(user.getPassword())) {
return user;
} else return null;
} catch (EntityUtils.EntityUtilException e) {
throw new RuntimeException("Passwort konnte nicht gehashed werden.");
}
} else return null;
}
I solved the problem this way:
I changed all collections of entity diaryPage and the orders collection from entity userProfile to be fetched lazily. Probably in the beginning it was okay with less data, but now it's too much to be fetched eagerly in subselects.
Because: UserProfile -> n diaryPages -> each page hast 4 collections
You get the same error, when you load existing data that uses an enum value you've removed in your code. E.g. if you previously had your enum defined as:
public enum SomeType {
A,
B,
C
}
Then having stored entities such that there are entries with each enum.
If you then say change your enum to:
public enum SomeType {
A,
B,
D
}
Now when you load your existing persisted entities, this same error is thrown because it tries to instantiate your entity with enum value C which is no longer valid.
Sadly this is not clearly communicated in the logs when this is the cause.
If you put a break point in your debugger at org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing (from spring-tx version 5.3.14 / spring boot version 2.6.2)
You'll see that the throwable param contains a message such as this: No enum constant <your enum>.OLD_VALUE

JPA: How to cascade modification of one field to reflect another?

I have the following Schema:
#Entity
public class A{
#OneToMany(mappedBy = "dest",cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<C> myDest; //Owns these
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<C> myParents; //can be reached from these
#ManyToMany
private Collection<B> myOwnB;;
}
#Entity
public class B{
#OneToMany(mappedBy = "forB",cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<C> associatedC;
#ManyToMany(mappedBy = "myOwnB")
private Collection<A> associatedA;
}
#Entity
public class C{
#JoinColumn(referencedColumnName = "myDest")
#ManyToOne
private A dest;
#JoinColumn(referencedColumnName = "myParents")
#ManyToOne
private A owner;
#JoinColumn(referencedColumnName = "associatedC")
#ManyToOne
private B forB;
}
Here, if I delete a record of A or B, all the associated Cs get deleted. Perfect.
So my problem is this:
If I delete a reference of B from A (in that ManyToMany relation), I need all the Cs deleted as well for that A and B. (All Cs where forB=removedB and owner=sourceA). I have now written a similar query and it works fine now that I explicitly execute in my EJB. Is there a workaround or a annotation saying that a record of C gets deleted if one of the attributes(column) become null?

Can't delete element from #OneToMany collection

i have an entity Entity1 that have one to many relation with Entity2 as follows:
1- Entity1:
#Entity
#Table(name = "Entity1", catalog = "mydb")
public class Entity1 implements java.io.Serializable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "entity1", cascade = javax.persistence.CascadeType.ALL)
#OrderBy("id")
private Set<Entity2> collection = new HashSet<Entity2>(
0);
}
2- Entity2: (equals and hashcode method overridden)
#Entity
#Table(name = "entity2", catalog = "advertisedb")
public class Entity2 implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pkid", unique = true, nullable = false)
#Basic(fetch = FetchType.EAGER)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "fk_entity1", nullable = false)
private Entity1 entity1;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "apid", nullable = false)
private Entity3 entity3;
}
3- Here's how i am removing the entity from the collection:
entity1Obj.getEntity2().remove(entity2);
log.debug("size after remove: "+ entity1Obj.getEntity2().size()); // size decreases correctly, so the entity is removed from the collection
entity1Dao.updateEntity1(entity1);
4- DAO method:
public void updateEntity1(Entity1 entity1) {
getCurrentSession().update(getCurrentSession().merge(entity1));
}
Problem: what i get in the console, is a select query for the entity2 that should be removed, and no delete query, and nothing is getting deleted.
please advise how to fix this issue.
i replaced cascade = CascadeType.ALL with orphanRemoval = true and it works fine now.

Categories

Resources