EclipseLink JPA Tracking Changes - java

I try to log any changes of my JPA entities. For this reason each entity inherits from an abstract entity class which has a list of LogEntry objects.
AbstractEntity class:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#EntityListeners(ChangeListener.class)
public abstract class AbstractEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
#Temporal(TemporalType.DATE)
private Date validFrom;
#Temporal(TemporalType.DATE)
private Date validTo;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "abstractEntity")
private List<LogEntry> logEntry = new ArrayList<LogEntry>();
//getter and setter
}
LogEntry class:
#Entity
public class LogEntry extends AbstractEntity {
#ManyToOne
#JoinColumn
protected AbstractEntity abstractEntity;
#ManyToOne
#JoinColumn
protected Person person; // creator or updater
#Column(updatable=false, insertable=false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
protected Date changeDate;
protected String comment;
//getter and setter
}
My approach for this was to create a new LogEntry object and add it to the entity's LogEntry list before an entity got updated or persisted.
I tried the following solutions:
Using callback annotations (#PreUpdate, #PrePersist etc.) directly in the entity class or separated in an entity listener connected with AbstractEntity
Using EclipsLink's DescriptorEvent and the corresponding callback methods in an entity listener. This was the most promising trial. In preUpdate I could add a new LogEntry to the affected object. The added LogEntry was even correctly persisted, but preUpdate will be invoked by any database operation (select also leads to invocation of preUpdate), so I can't differ between a changed object and an object with no changes. The provided changesets from the descriptor event, the related query or the unitOfWork are in each case null. A comparison of the current object and the old object (provided by the descriptor event) is IMHO too complex, isn't it?
On the other hand, in preUpdateWithChanges I could easily detect a changed entity, but at this point it is apparently too late for adding a logentry. The logentry won't be persisted.
Nearly each of these trials enables me to change attributes (like name, or validTo) of the affected entity. But no solution provides the opportunity to create a new LogEntry instance, or rather to persist this LogEntry instance. I also tried to get an instance of a session bean via jndi lookup to persists the LogEntry manually. The jndi lookup works, but calling create or update methods of the session bean has no effect.
My current entity listener looks like this:
public class ChangeListener extends DescriptorEventAdapter {
#Override
public void preUpdate(DescriptorEvent event) {
AbstractEntity entity = (AbstractEntity) event.getObject();
if (!(entity instanceof LogEntry)) {
LogEntry logEntry = new LogEntry();
logEntry.setPerson(getSessionController().getCurrentUser());
logEntry.setAbstractEntity(entity);
entity.getLogEntries().add(logEntry);
}
}
}
Hibernate Envers is for various reasons no option.
EclipseLink Version is 2.3.2.

Persisting new object during events from the commit process can be difficult.
You could obtain the changes before the commit and persist you logs (get change set from the UnitOfWork).
See,
http://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_access_what_changed_in_an_object_or_transaction.3F
Otherwise, it is possible to directly insert and object from inside an event.
i.e.
event.getSession().insertObject(logEntry);

Temporarily I solved this issue by comparing the current entity and the old entity in preUpdate. The comparison is done with EqualsBuilder from Apache Commons Lang.
public class ChangeAbstractEntityListener extends DescriptorEventAdapter {
#Override
public void preUpdate(DescriptorEvent event) {
AbstractEntity originalEntity = (AbstractEntity) event.getOriginalObject();
AbstractEntity entity = (AbstractEntity) event.getObject();
if (!EqualsBuilder.reflectionEquals(originalEntity, entity, true)) {
if (!(entity instanceof LogEntry)) {
LogEntry logEntry = new LogEntry();
logEntry.setPerson(getSessionController().getCurrentUser());
logEntry.setAbstractEntity(entity);
entity.getLogEntries().add(logEntry);
}
}
}
//jndi lookup to get the user object
}

You can try Tracking Changes Using History Policy
EclipseLink provides extended support for tracking all changes made to the database. The EclipseLink HistoryPolicy can be configured on a ClassDescriptor to store a mirror table of the original that will store the state of the object at any point in time. This can be used for auditing purposes, or to allow querying as of past points in time, or to allow restoring old data.

If you want simple entity auditing, then look no further than Hibernate's Envers module.
It works with JPA (and Spring-Data), and can store each changed version along with who changed it if you run say Spring Security.
Envers is part of Hibernate since 3.6

Related

How to add existing value in many to many relationship spring boot [duplicate]

I have a JPA-persisted object model that contains a many-to-one relationship: an Account has many Transactions. A Transaction has one Account.
Here's a snippet of the code:
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
#Entity
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
I am able to create an Account object, add transactions to it, and persist the Account object correctly. But, when I create a transaction, using an existing already persisted Account, and persisting the the Transaction, I get an exception:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.paulsanwald.Account
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
So, I am able to persist an Account that contains transactions, but not a Transaction that has an Account. I thought this was because the Account might not be attached, but this code still gives me the same exception:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
How can I correctly save a Transaction, associated with an already persisted Account object?
The solution is simple, just use the CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL.
I have had the same problem and CascadeType.MERGE has worked for me.
I hope you are sorted.
This is a typical bidirectional consistency problem. It is well discussed in this link as well as this link.
As per the articles in the previous 2 links you need to fix your setters in both sides of the bidirectional relationship. An example setter for the One side is in this link.
An example setter for the Many side is in this link.
After you correct your setters you want to declare the Entity access type to be "Property". Best practice to declare "Property" access type is to move ALL the annotations from the member properties to the corresponding getters. A big word of caution is not to mix "Field" and "Property" access types within the entity class otherwise the behavior is undefined by the JSR-317 specifications.
Remove cascading from the child entity Transaction, it should be just:
#Entity class Transaction {
#ManyToOne // no cascading here!
private Account account;
}
(FetchType.EAGER can be removed as well as it's the default for #ManyToOne)
That's all!
Why? By saying "cascade ALL" on the child entity Transaction you require that every DB operation gets propagated to the parent entity Account. If you then do persist(transaction), persist(account) will be invoked as well.
But only transient (new) entities may be passed to persist (Transaction in this case). The detached (or other non-transient state) ones may not (Account in this case, as it's already in DB).
Therefore you get the exception "detached entity passed to persist". The Account entity is meant! Not the Transaction you call persist on.
You generally don't want to propagate from child to parent. Unfortunately there are many code examples in books (even in good ones) and through the net, which do exactly that. I don't know, why... Perhaps sometimes simply copied over and over without much thinking...
Guess what happens if you call remove(transaction) still having "cascade ALL" in that #ManyToOne? The account (btw, with all other transactions!) will be deleted from the DB as well. But that wasn't your intention, was it?
Don't pass id(pk) to persist method or try save() method instead of persist().
Removing child association cascading
So, you need to remove the #CascadeType.ALL from the #ManyToOne association. Child entities should not cascade to parent associations. Only parent entities should cascade to child entities.
#ManyToOne(fetch= FetchType.LAZY)
Notice that I set the fetch attribute to FetchType.LAZY because eager fetching is very bad for performance.
Setting both sides of the association
Whenever you have a bidirectional association, you need to synchronize both sides using addChild and removeChild methods in the parent entity:
public void addTransaction(Transaction transaction) {
transcations.add(transaction);
transaction.setAccount(this);
}
public void removeTransaction(Transaction transaction) {
transcations.remove(transaction);
transaction.setAccount(null);
}
Using merge is risky and tricky, so it's a dirty workaround in your case. You need to remember at least that when you pass an entity object to merge, it stops being attached to the transaction and instead a new, now-attached entity is returned. This means that if anyone has the old entity object still in their possession, changes to it are silently ignored and thrown away on commit.
You are not showing the complete code here, so I cannot double-check your transaction pattern. One way to get to a situation like this is if you don't have a transaction active when executing the merge and persist. In that case persistence provider is expected to open a new transaction for every JPA operation you perform and immediately commit and close it before the call returns. If this is the case, the merge would be run in a first transaction and then after the merge method returns, the transaction is completed and closed and the returned entity is now detached. The persist below it would then open a second transaction, and trying to refer to an entity that is detached, giving an exception. Always wrap your code inside a transaction unless you know very well what you are doing.
Using container-managed transaction it would look something like this. Do note: this assumes the method is inside a session bean and called via Local or Remote interface.
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
...
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
entityManager.persist(account);
}
Probably in this case you obtained your account object using the merge logic, and persist is used to persist new objects and it will complain if the hierarchy is having an already persisted object. You should use saveOrUpdate in such cases, instead of persist.
My Spring Data JPA-based answer: I simply added a #Transactional annotation to my outer method.
Why it works
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
Reference:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
An old question, but came across the same issue recently . Sharing my experience here.
Entity
#Data
#Entity
#Table(name = "COURSE")
public class Course {
#Id
#GeneratedValue
private Long id;
}
Saving the entity (JUnit)
Course course = new Course(10L, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Fix
Course course = new Course(null, "testcourse", "DummyCourse");
testEntityManager.persist(course);
Conclusion : If the entity class has #GeneratedValue for primary key (id), then ensure that you are not passing a value for the primary key (id)
If nothing helps and you are still getting this exception, review your equals() methods - and don't include child collection in it. Especially if you have deep structure of embedded collections (e.g. A contains Bs, B contains Cs, etc.).
In example of Account -> Transactions:
public class Account {
private Long id;
private String accountName;
private Set<Transaction> transactions;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Account))
return false;
Account other = (Account) obj;
return Objects.equals(this.id, other.id)
&& Objects.equals(this.accountName, other.accountName)
&& Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
}
}
In above example remove transactions from equals() checks. This is because hibernate will imply that you are not trying to update old object, but you pass a new object to persist, whenever you change element on the child collection.
Of course this solutions will not fit all applications and you should carefully design what you want to include in the equals and hashCode methods.
In your entity definition, you're not specifying the #JoinColumn for the Account joined to a Transaction. You'll want something like this:
#Entity
public class Transaction {
#ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
#JoinColumn(name = "accountId", referencedColumnName = "id")
private Account fromAccount;
}
EDIT: Well, I guess that would be useful if you were using the #Table annotation on your class. Heh. :)
Even if your annotations are declared correctly to properly manage the one-to-many relationship you may still encounter this precise exception. When adding a new child object, Transaction, to an attached data model you'll need to manage the primary key value - unless you're not supposed to. If you supply a primary key value for a child entity declared as follows before calling persist(T), you'll encounter this exception.
#Entity
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
In this case, the annotations are declaring that the database will manage the generation of the entity's primary key values upon insertion. Providing one yourself (such as through the Id's setter) causes this exception.
Alternatively, but effectively the same, this annotation declaration results in the same exception:
#Entity
public class Transaction {
#Id
#org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
#GeneratedValue(generator="system-uuid")
private Long id;
....
So, don't set the id value in your application code when it's already being managed.
Here is my fix.
Below is my Entity. Mark that the id is annotated with #GeneratedValue(strategy = GenerationType.AUTO), which means that the id would be generated by the Hibernate. Don't set it when entity object is created. As that will be auto generated by the Hibernate.
Mind you if the entity id field is not marked with #GeneratedValue then not assigning the id a value manually is also a crime, which will be greeted with IdentifierGenerationException: ids for this class must be manually assigned before calling save()
#Entity
#Data
#NamedQuery(name = "SimpleObject.findAll", query="Select s FROM SimpleObject s")
public class SimpleObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String key;
#Column
private String value;
}
And here is my main class.
public class SimpleObjectMain {
public static void main(String[] args) {
System.out.println("Hello Hello From SimpleObjectMain");
SimpleObject simpleObject = new SimpleObject();
simpleObject.setId(420L); // Not right, when id is a generated value then no need to set this.
simpleObject.setKey("Friend");
simpleObject.setValue("Bani");
EntityManager entityManager = EntityManagerUtil.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(simpleObject);
entityManager.getTransaction().commit();
List<SimpleObject> simpleObjectList = entityManager.createNamedQuery("SimpleObject.findAll").getResultList();
for(SimpleObject simple : simpleObjectList){
System.out.println(simple);
}
entityManager.close();
}
}
When I tried saving that, it was throwing that
PersistentObjectException: detached entity passed to persist.
All I needed to fix was remove that id setting line for the simpleObject in the main method.
Maybe It is OpenJPA's bug, When rollback it reset the #Version field, but the pcVersionInit keep true. I have a AbstraceEntity which declared the #Version field. I can workaround it by reset the pcVersionInit field. But It is not a good idea. I think it not work when have cascade persist entity.
private static Field PC_VERSION_INIT = null;
static {
try {
PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
PC_VERSION_INIT.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
}
}
public T call(final EntityManager em) {
if (PC_VERSION_INIT != null && isDetached(entity)) {
try {
PC_VERSION_INIT.set(entity, false);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
em.persist(entity);
return entity;
}
/**
* #param entity
* #param detached
* #return
*/
private boolean isDetached(final Object entity) {
if (entity instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable) entity;
if (pc.pcIsDetached() == Boolean.TRUE) {
return true;
}
}
return false;
}
You need to set Transaction for every Account.
foreach(Account account : accounts){
account.setTransaction(transactionObj);
}
Or it colud be enough (if appropriate) to set ids to null on many side.
// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());
foreach(Account account : accounts){
account.setId(null);
}
transactionObj.setAccounts(accounts);
// just persist transactionObj using EntityManager merge() method.
cascadeType.MERGE,fetch= FetchType.LAZY
Resolved by saving dependent object before the next.
This was happened to me because I was not setting Id (which was not auto generated). and trying to save with relation #ManytoOne
#OneToMany(mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
worked for me.
In my case I was committing transaction when persist method was used.
On changing persist to save method , it got resolved.
If above solutions not work just one time comment the getter and setter methods of entity class and do not set the value of id.(Primary key)
Then this will work.
Another reason I have encountered this issue is having Entities that aren't versioned by Hibernate in a transaction.
Add a #Version annotation to all mapped entities
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "orders")
private CustomerOrders orders;
}
#Entity
public class CustomerOrders {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Version
private Integer version;
private BigDecimal value;
}
This error comes from the JPA Lifecycle.
To solve, no need to use specific decorator. Just join the entity using merge like that :
entityManager.merge(transaction);
And don't forget to correctly set up your getter and setter so your both side are sync.
So I stumbled across this Question and Answers because I got the same Error but a very basic object with just Strings and Integers.
But in my case I was trying to set a Value to a Field which was annotated with #Id.
So if you are using #Id it seems that you can't create a new Object on a Class and set an Id by yourself and persist it to Database. You should then leave the Id blank. I wasn't aware and maybe this helps anyone else.
The problem here is lack of control.
When we use the CrudRepository/JPARepository save method we loose the transactional control.
To overcome this issue we have Transaction Management
I prefer the #Transactional mechanism
imports
import javax.transaction.Transactional;
Entire Source Code:
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
#Entity
#Data
#ToString(exclude = {"employee"})
#EqualsAndHashCode(exclude = {"employee"})
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "ps")
#SequenceGenerator(name = "ps",sequenceName = "project_seq",initialValue = 1000,allocationSize = 1)
#Setter(AccessLevel.NONE)
#Column(name = "project_id",updatable = false,nullable = false)
private Integer pId;
#Column(name="project_name",nullable = false,updatable = true)
private String projectName;
#Column(name="team_size",nullable = true,updatable = true)
private Integer teamSize;
#Column(name="start_date")
private Date startDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="projectemp_join_table",
joinColumns = {#JoinColumn(name = "project_id")},
inverseJoinColumns = {#JoinColumn(name="emp_id")}
)
private List<Employee> employees;
}
package com.oracle.dto;
import lombok.*;
import javax.persistence.*;
import java.util.List;
#Entity
#Data
#EqualsAndHashCode(exclude = {"projects"})
#ToString(exclude = {"projects"})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "es")
#SequenceGenerator(name = "es",sequenceName = "emp_seq",allocationSize = 1,initialValue = 2000)
#Setter(AccessLevel.NONE)
#Column(name = "emp_id",nullable = false,updatable = false)
private Integer eId;
#Column(name="fist_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#ManyToMany(mappedBy = "employees")
private List<Project> projects;
}
package com.oracle.repo;
import com.oracle.dto.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepo extends JpaRepository<Employee,Integer> {
}
package com.oracle.repo;
import com.oracle.dto.Project;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProjectRepo extends JpaRepository<Project,Integer> {
}
package com.oracle.services;
import com.oracle.dto.Employee;
import com.oracle.dto.Project;
import com.oracle.repo.ProjectRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
#Component
public class DBServices {
#Autowired
private ProjectRepo repo;
#Transactional
public void performActivity(){
Project p1 = new Project();
p1.setProjectName("Bank 2");
p1.setTeamSize(20);
p1.setStartDate(new Date(2020, 12, 22));
Project p2 = new Project();
p2.setProjectName("Bank 1");
p2.setTeamSize(21);
p2.setStartDate(new Date(2020, 12, 22));
Project p3 = new Project();
p3.setProjectName("Customs");
p3.setTeamSize(11);
p3.setStartDate(new Date(2010, 11, 20));
Employee e1 = new Employee();
e1.setFirstName("Pratik");
e1.setLastName("Gaurav");
Employee e2 = new Employee();
e2.setFirstName("Ankita");
e2.setLastName("Noopur");
Employee e3 = new Employee();
e3.setFirstName("Rudra");
e3.setLastName("Narayan");
List<Employee> empList1 = new LinkedList<Employee>();
empList1.add(e2);
empList1.add(e3);
List<Employee> empList2 = new LinkedList<Employee>();
empList2.add(e1);
empList2.add(e2);
List<Project> pl1=new LinkedList<Project>();
pl1.add(p1);
pl1.add(p2);
List<Project> pl2=new LinkedList<Project>();
pl2.add(p2);pl2.add(p3);
p1.setEmployees(empList1);
p2.setEmployees(empList2);
e1.setProjects(pl1);
e2.setProjects(pl2);
repo.save(p1);
repo.save(p2);
repo.save(p3);
}
}

override identitycolumn hibernate mssql

I have to manage a Datatransfer between 2 DBs (mssql) with hibernate.
When i load an object from one DB with session.get() it already has a private key. Then i need to persist it to the other DB with anotherSession.replicate(Object o).
My Problem ist, that the given PK is not persisted but replaced by another one.
PS: Both the srcTable and the destTable have PK generation Identity and it needs to stay that way.
If you map an Entity ID with generation "identity", Hibernate will always generate a new ID as soon as you try to persist it. You will have to switch the generation to "assigned" to keep your old ID.
If you have something like it
#Entity
public class Project {
#Id #GeneratedValue long id; // still set automatically
}
You have to remove the #GeneratedValue annotation from id field. Else jpa will generate a value for id before insertion.
#Entity
public class Project {
#Id long id; // must be initialized by the application
:
}
Solution To Your Problem
Create a an entity containing all the mapping definition.
Create a ID field in your new class without the #Generated value annotation.
Clone the old entity to this new one.
Persist this new entity.
Now if you create a subclass extending your entity then the whole
process becomes very easy.
Sample Code For This Solution
Existing Entity
#Entity
#Table(name="EJB_PROJECT")
public class OldEntity implements Serializable {
#Id
#Column(name="PROJECT_ID", primaryKey=true)
#GeneratedValue
Integer id;
}
New Entity
#Entity
#Inheritance(strategy=SINGLE_TABLE)
#Table(name="EJB_PROJECT")
public class NewEntity extends OldEntity {
#Id
#Column(name="PROJECT_ID", primaryKey=true)
Integer id;
// Constructor to clone old entity's id
public NewEnity(OldEntity old) {
this.id = old.id;
}
}
Persisting code
em.persist(new NewEntity(oldEntity));

Logical delete at a common place in hibernate

I am using Spring and Hibernate for my application.
I am only allowing logical delete in my application where I need to set the field isActive=false. Instead of repeating the same field in all the entities, I created a Base Class with the property and getter-setter for 'isActive'.
So, during delete, I invoke the update() method and set the isActive to false.
I am not able to get this working. If any one has any idea, please let me know.
Base Entity
public abstract class BaseEntity<TId extends Serializable> implements IEntity<TId> {
#Basic
#Column(name = "IsActive")
protected boolean isActive;
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
isActive= isActive;
}
}
Child Entity
#Entity(name="Role")
#Table(schema = "dbo")
public class MyEntity extends BaseEntity {
//remaining entities
}
Hibernate Util Class
public void remove(TEntity entity) {
//Note: Enterprise data should be never removed.
entity.setIsActive(false);
sessionFactory.getCurrentSession().update(entity);
}
Try to replace the code in setIsActive method with:
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
in your code the use of variable name without this could be ambiguos...
I think you should also add #MappedSuperclass annotation to your abstract class to achieve field inheritance.
The issue with the proposed solution (which you allude to in your comment to that answer) is that does not handle cascading delete.
An alternative (Hibernate specific, non-JPA) solution might be to use Hibernate's #SQLDelete annotation:
http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/querysql.html#querysql-cud
I seem to recall however that this Annotation cannot be defined on the Superclass and must be defined on each Entity class.
The problem with Logical delete in general however is that you then have to remember to filter every single query and every single collection mapping to exclude these records.
In my opinion an even better solution is to forget about logical delete altogether. Use Hibernate Envers as an auditing mechanism. You can then recover any deleted records as required.
http://envers.jboss.org/
You can use the SQLDelete annotation...
#org.hibernate.annotations.SQLDelete;
//Package name...
//Imports...
#Entity
#Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
private long id;
...
private char deleted;
Source: http://featurenotbug.com/2009/07/soft-deletes-using-hibernate-annotations/

Error setting foreign key in jpa entity: You cannot flush unmanaged objects... that have persistent associations to unmanaged objects

I am trying to setup some foreign key links to a static lookup table using JPA 2.0. the entity that contains the link and the static lookup table is defined like this in class A and Status respectively. I also have an entity called AHistory that is a clone of A in all regards. When I call the logNewA method, I find the already existing Entity Status from the em, create a new A entity and then I create a corresponding AHistory entity as seen below. I am getting this error when i try to do this. can any one help me out?
#Entity
#Table(name = "ATABLE")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class A extends BaseBusinessObject {
#Id
#Column(name = "AID"
private long id;
#JoinColumn(name = "STATUSID")
#ManyToOne
private Status status;
// other irrelevant fields in BaseBusinessObject class.
}
public class AHistory extends BaseBusinessObject {
#Id
#Column(name = "AHISTORYID"
private long id;
#JoinColumn(name = "STATUSID")
#ManyToOne
private Status status;
public AHistory(Status status){
this.status = status;
}
// other irrelevant fields in BaseBusinessObject class.
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Table(name = "STATUSTABLE")
public class Status extends BaseBusinessType {
#Id
#Column(name = "STATUSID")
private long id;
// other irrelevant fields in BaseBusinessType class.
}
#Stateless
#LocalBean
public class SomeEJB{
#PersistenceContext
EntityManager em;
#EJB
ProcessorBean processor;
...
public A logNewA()
throws SystemException {
Status status = em.find(/*...NEW-STATUS*/); //assume this is now attached
A a = new A(status);
return processor.saveA(a);
}
}
#Stateless
#LocalBean
public class ProcessorBean {
#PersistenceContext
EntityManager em;
...
public A saveA(A a) throws SystemException {
AHistory history = new AHistory(a.getStatus());
process.getAHistorys().add(history);
return em.merge(a);
}
}
Caused by: <openjpa-2.1.2-SNAPSHOT-r422266:1497841 nonfatal user error> org.apache.openjpa.persistence.InvalidStateException: Encountered unmanaged object "au.com.combined.domain.Status-1" in life cycle state unmanaged while cascading persistence via field "au.com.combined.domain.AHistory.status" during flush. However, this field does not allow cascade persist. You cannot flush unmanaged objects or graphs that have persistent associations to unmanaged objects.
Suggested actions: a) Set the cascade attribute for this field to CascadeType.PERSIST or CascadeType.ALL (JPA annotations) or "persist" or "all" (JPA orm.xml),
b) enable cascade-persist globally,
c) manually persist the related field value prior to flushing.
d) if the reference belongs to another context, allow reference to it by setting StoreContext.setAllowReferenceToSiblingContext().
FailedObject: au.com.combined.domain.Status-1
at org.apache.openjpa.kernel.SingleFieldManager.preFlushPC(SingleFieldManager.java:775)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:610)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:578)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:494)
at org.apache.openjpa.kernel.StateManagerImpl.preFlush(StateManagerImpl.java:2971)
at org.apache.openjpa.kernel.PNewProvisionalState.nonprovisional(PNewProvisionalState.java:45)
at org.apache.openjpa.kernel.StateManagerImpl.nonprovisional(StateManagerImpl.java:1222)
at org.apache.openjpa.kernel.SingleFieldManager.preFlushPC(SingleFieldManager.java:812)
at org.apache.openjpa.kernel.SingleFieldManager.preFlushPCs(SingleFieldManager.java:751)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:653)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:578)
at org.apache.openjpa.kernel.SingleFieldManager.preFlush(SingleFieldManager.java:494)
at org.apache.openjpa.kernel.StateManagerImpl.preFlush(StateManagerImpl.java:2971)
at org.apache.openjpa.kernel.PNewState.beforeFlush(PNewState.java:40)
at org.apache.openjpa.kernel.StateManagerImpl.beforeFlush(StateManagerImpl.java:1047)
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2096)
at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2056)
at org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:1974)
at com.ibm.ws.uow.ComponentContextSynchronizationWrapper.beforeCompletion(ComponentContextSynchronizationWrapper.java:65)
at com.ibm.tx.jta.impl.RegisteredSyncs.coreDistributeBefore(RegisteredSyncs.java:291)
at com.ibm.ws.tx.jta.RegisteredSyncs.distributeBefore(RegisteredSyncs.java:152)
at com.ibm.ws.tx.jta.TransactionImpl.prePrepare(TransactionImpl.java:2367)
at com.ibm.ws.tx.jta.TransactionImpl.stage1CommitProcessing(TransactionImpl.java:575)
at com.ibm.tx.jta.impl.TransactionImpl.processCommit(TransactionImpl.java:1015)
... 85 more
Solved
The problem was that inside the saveA function (which i paraphrased for secrecy's sake), i was calling a Bean that had a reference to a different persistence context. after explicitly stating the unitName in the #PersistenceContext(unitName = "Domain") EntityManager em for the bean, the problem dissappeared.
Because the other entity manager was finding the Status object, the different entitymanager (in charge of actually calling em.merge()) did not recognise the object as managed, therefore it attempted to persist it, thus causing the error.

How to achive lazy loading using spring data jpa?

In my project, I am using Spring Data JPA and extend the JpaRepository interface for my data fetching class.
OrganizationMaster class :
#Entity
#Table(name="organization_master")
public class OrganizationMaster {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="organization_id")
private int organizationId;
#OneToMany(mappedBy="organizationMaster")
private List<CompanyMaster> companyMasters;
}
CompanyMaster Class:
Entity
#Table(name="company_master")
public class CompanyMaster {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="company_id")
private int companyId;
#ManyToOne
#JoinColumn(name="organization_id")
private OrganizationMaster organizationMaster;
}
My Controller
#RequestMapping(value = "/GetOrganization", method = RequestMethod.GET)
public
#ResponseBody
List<OrganizationMaster> getOrganization(){
return organizationService.getOrganization();
}
OrganizationService:
public interface OrganizationService {
List<OrganizationMaster> getOrganization();
}
OrganizationServiceImpl:
#Service
public class OrganizationServiceImpl implements OrganizationService{
#Autowired
private OrganizationDao organizationDao;
#Override
public List<OrganizationMaster> getOrganization() {
return organizationDao.findAll();
}
}
OrganizationDao Interface:
public interface OrganizationDao extends JpaRepository<OrganizationMaster,Long> {
}
My Output Response is:
[{"organizationId":5,"companyMasters":[{"companyId":29},{"companyId":30}]}]
But my need is
[{"organizationId":5}]
When I am trying to get data from the organization master using findall() method it also fetches data from the company master based on the relationship. How can I achieve lazy fetching (get data only from organization master) using spring data JpaRepository
All XToOne associations are default EAGER. You have a bidirectional relationship, so you should use FetchType.LAZY on your #ManyToOne side.
#ManyToOne(fetch = FetchType.LAZY)
Also if you use any serializer (like json serializer) when it serialize it calls getter methods and it may causes to load lazy items unneccessarly.
Another consideration is while using Lombok, #Data annotation causes to load lazy items without need. So be careful when using Lombok.
So in your case, beacuse of you return entity itself, while serialization it serializes the child too, it causes to load lazly child entity.
You need to return a dto which represents only your parent entity to prevent serialization of child entity. If you call child with getter method, it laods lazly child entity from database.
Take a look for further information associations:
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
I believe this question is asked before!you can use this annotation:
#OneToMany( fetch = FetchType.LAZY )
read this article for better view in this point:
https://howtodoinjava.com/hibernate/lazy-loading-in-hibernate/

Categories