Synchronize changed entities in the same transaction - java

I have a problem with a transactional method that changes an entity and wants to update it in some other way.
At first i get the entity A from the database with the entitymanager method "get".
Then i get a related entity B where A to B is type of one to one (optional). (So the id field of B is inside the table of A). Now i want to remove the entity B via a service method. Therefore i have to use the ID of B.
Inside the service method i get B from the entity manager (now B'). Then i get A' from the aquired B'. Then i remove the link A' to B' when it is present via A'.setB(null) followed by a serviceOfA.save(A').
Then i delete B' via serviceOfB.delete(B').
When the method for removal of B via the id is completed i want to change properties of the instance A.
Create another instance of B for example. Now when i get A via the entitymanager again hibernate throws a org.hibernate.exception.ConstraintViolationException for an object that should be added to the new B'' instance that is added to A.
I think the issue has something to do with the removal method that changed the instance A' and therefore A can not be reloaded. But how can i reload the new state of A?
Please have a look below:
#Transactional
public void compeleteSomething(
#NonNull String location,
#NonNull String nameOfA) throws SomeException{
A a= serviceOfA.get(nameOfA);
B b= a.getB();
someService.removeBAndLinkToA(b.getId()); // <-- maybe here is the error
B newInstanceOfB = someService.createBOn(location);
someService.setBonA(serviceOfA.get(nameOfA), newInstanceOfB); // <-- serviceOfA.get() throws error
[...]
}
And here the method of someService.removeBAndLinkToA(#)
#Transactional
public void removeBAndLinkToA(
#NonNull Long id) {
B b = serviceOfB.get(id);
A a = b.getA();
if (a!= null) {
a.setB(null);
serviceOfA.save(a); // <-- This should cause the error?
}
serviceOfB.delete(b);
}
How can i avoid this issue?
Many thanks!

When working inside a transaction, entitymanager is expected to deal with all relashionships until commit, provided it is injected with proper scope. You do not need to save the entity at each step nor retrieve it again from database: its state is managed by the entitymanager (no pun intended).
In short, your completeSomething method does not need to call another service method. Just make b.setA(null), a.setB(new B()) and return. Everything should work as expected:
#Transactional
public void completeSomething(String aId) {
A a = entityManager.find(A.class, aId);
B b = a.getB();
a.setB(new B());
b.setA(null);
} // container should end transaction here, commiting changes to entities
If the transaction is successful, the container will commit changes to entities and the changes will be reflected on database as long the #PersistenceContext has PersistenceContextType.TRANSACTION type, which is the default.

Related

Outer transaction can not retrieve changes from inner transaction if entity already loaded once

I have a #Transactional main service loading from repository an entity.
Then this service call a sub-service having #Transactional(propagation = Propagation.REQUIRES_NEW) loading the same entity to update one of its properties
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateEntity(Long id) {
repository.findById(id).get().setName("NEW NAME");
}
Unfortunately, the update done from the sub-service is not visible from the main service, even if I try to reload the entity from repository and no matter which isolation level I choose (READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE).
Here is the code of the main service :
#Transactional
public void transactional() {
MyEntity myEntity1 = repository.findById(1L).get();
log.info("{} - {}", myEntity1, myEntity1.getName()); // "OLD NAME"
subService.updateEntity(1L);
MyEntity myEntity2 = repository.findById(1L).get();
log.info("{} - {}", myEntity2, myEntity2.getName()); // "OLD NAME" again, and same reference as myEntity1
}
Nevertheless, If I don't load once the entity in the main service before calling the sub-service, I DO see changes from the inner transaction.
#Transactional
public void transactional() {
subService.updateEntity(1L);
MyEntity myEntity2 = repository.findById(1L).get();
log.info("{} - {}", myEntity2, myEntity2.getName()); // "NEW NAME"
}
I guess what I want to achieve is not a standard way of doing things, but I really want to understand why if an entity is loaded once in an outer transaction, I can not retrieve changes from inner transaction(s).
After edition from the inner transaction, the entity must be explicitly reloaded inside the outer transaction by calling EntityManager#refresh. (Thx to #M.Prokhorov for the clue)
entityManager.refresh(myEntity1);
As #Kayaman said in comment of my question:
It's the 1st level cache in action. It won't reload the entity (unless
explicitly told to), since you've just loaded it.

Hibernate 5.2.9.Final cache not updated

I have a service (which I for some reason call controller) that is injected into the Jersey resource method.
#Named
#Transactional
public class DocCtrl {
...
public void changeDocState(List<String> uuids, EDocState state, String shreddingCode) throws DatabaseException, WebserviceException, RepositoryException, ExtensionException, LockException, AccessDeniedException, PathNotFoundException, UnknowException {
List<Document2> documents = doc2DAO.getManyByUUIDs(uuids);
for (Document2 doc : documents) {
if (EDocState.SOFT_DEL == state) {
computeShreddingFor(doc, shreddingCode); //here the state change happens and it is persisted to db
}
if (EDocState.ACTIVE == state)
unscheduleShredding(doc);
}
}
}
doc2DAO.getManyByUUIDs(uuids); gets an Entity object from the database.
#Repository
public class Doc2DAO {
#PersistenceContext(name = Vedantas.PU_NAME, type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
public List<Document2> getManyByUUIDs(List<String> uuids) {
if (uuids.isEmpty())
uuids.add("-3");
TypedQuery<Document2> query = entityManager.createNamedQuery("getManyByUUIDs", Document2.class);
query.setParameter("uuids", uuids);
return query.getResultList();
}
}
However When I do second request to my API, I see state of this entity object unchanged, that means the same as before the logic above occoured.
In DB there is still changed status.
After the api service restart, I will get the entity in the correct state.
As I understand it, Hibernate uses it's L2 cache for the managed objects.
So can you, please point me to what I am doing wrong here? Obviously, I need to get cached entity with the changed state without service restart and I would like to keep entities attached to the persistence context for the performance reasons.
Now, can you tell me what I am
In the logic I am making some changes to this object. After the completition of the changeDocState method, the state is properly changed and persisted in the database.
Thanks for the answers;

Safely clone a Hibernate Spring JPA entity - Javassist

I'm cloning an entity using Spring BeanUtils.copyProperties(source, target, excludes) method and the issue is there is a method called setHandler that gets called and it basically resets all the properties I set in my exclusion list during the copy. If I exclude handler, then I get an exception saving the new object.
I just want to do a clone of a Hibernate object, excluding 10 properties and save the new object.
public static <T> T cloneClass(T existing, Class<? extends Annotation> ignores)
throws Exception {
final Collection<String> excludes = new ArrayList<>();
Set<Method> annotated = getMethodsWithAnnotation(ignores, existing.getClass());
for (Method method : annotated) {
if (!method.getName().startsWith("get") && !method.getName().startsWith("is"))
continue;
String exclude = ReflectUtil.decap(method.getName());
log.debug("Exclude from copy: " + exclude);
excludes.add(exclude);
}
excludes.add("handler"); <-- must have this
Object newInstance = existing.getClass().newInstance();
String[] excludeArray = excludes.toArray(new String[excludes.size()]);
BeanUtils.copyProperties(existing, newInstance, excludeArray);
return (T) newInstance;
}
If I don't include
excludes.add("handler"); <-- must have this
Then what happens is the target object gets all the properties from the source and basically makes my exclude list useless, but once I try to save that object, Hibernate throws an internal hibernate error.
Is there an easier way to clone an object than what I am doing?
I don't think you really need to do any cloning. Simply retrieve the object and then remove the object from the session and it is effectively a clone: Is it possible to detach Hibernate entity, so that changes to object are not automatically saved to database?. Start another session again once you want to update the instance.

GAE Objectify load object created in transaction within same transaction

I try to use objectify transaction, but I have some issues when I need to reload an object created in the same transaction.
Take this sample code
#Entity
public class MyObject
{
#Parent
Key<ParentClass> parent;
#Index
String foo;
}
ofy().transact(new VoidWork()
{
#Override
public void vrun()
{
ParentClass parent = load();// load the parent
String fooValue = "bar";
Key<ParentClass> parentKey = Key.create(ParentClass.class, parent.getId())
MyObject myObject = new MyObject(parentKey);
myObject.setFoo(fooValue);
ofy().save().entity(myObject).now();
MyObject reloaded = ofy().load().type(MyObject.class).ancestor(parentKey).filter("foo", fooValue).first().now();
if(reloaded == null)
{
throw new RuntimeException("error");
}
}
});
My object reloaded is always null, maybe I miss something, but logically within a transaction I can query an object which was created in the same transaction?
Thanks
Cloud Datastore differs from relational databases in this particular case. The documentation states that -
Unlike with most databases, queries and gets inside a Cloud Datastore
transaction do not see the results of previous writes inside that
transaction. Specifically, if an entity is modified or deleted within
a transaction, a query or lookup returns the original version of the
entity as of the beginning of the transaction, or nothing if the
entity did not exist then.
https://cloud.google.com/datastore/docs/concepts/transactions#isolation_and_consistency

Entity must be managed to call remove?

public Person deletePerson(Person entity) {
EntityManager ems = emf.createEntityManager();
try {
ems.getTransaction().begin();
ems.merge(entity);
ems.remove(entity);
ems.getTransaction().commit();
} finally {
ems.close();
}
return entity;
}
it doesnt work I don't know why? Gives me java.lang.IllegalArgumentException
It doesn't work because remove operation requires managed entity to be passed to it. You could modify your code like this to make it work:
entity = ems.merge(entity);
ems.remove(entity);
Because merge returns managed entity instance, you can call remove with the object it returns, because it is managed by JPA (the object you pass to merge is not affected, which is why your code fails).

Categories