Safely clone a Hibernate Spring JPA entity - Javassist - java

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.

Related

Synchronize changed entities in the same transaction

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.

Spring Data Rest: Limit sending values on Update method

I have implemented by project using Spring-Data-Rest. I am trying to do an update on an existing record in a table. But when I try to send only a few fields instead of all the fields(present in Entity class) through my request, Spring-Data-Rest thinking I am sending null/empty values. Finally when I go and see the database the fields which I am not sending through my request are overridden with null/empty values. So my understanding is that even though I am not sending these values, spring data rest sees them in the Entity class and sending these values as null/empty. My question here is, is there a way to disable the fields when doing UPDATE that I am not sending through the request. Appreciate you are any help.
Update: I was using PUT method. After reading the comments, I changed it to PATCH and its working perfectly now. Appreciate all the help
Before update, load object from database, using jpa method findById return object call target.
Then copy all fields that not null/empty from object-want-to-update to target, finally save the target object.
This is code example:
public void update(Object objectWantToUpdate) {
Object target = repository.findById(objectWantToUpdate.getId());
copyNonNullProperties(objectWantToUpdate, target);
repository.save(target);
}
public void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] propDesList = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(PropertyDescriptor propDesc : propDesList) {
Object srcValue = src.getPropertyValue(propDesc.getName());
if (srcValue == null) {
emptyNames.add(propDesc.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
As some of the comments pointed out using PATCH instead of PUT resolved the issue. Appreciate all the inputs. The following is from Spring Data Rest Documentation:
"The PUT method replaces the state of the target resource with the supplied request body.
The PATCH method is similar to the PUT method but partially updates the resources state."
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
Also, I like #Tran Quoc Vu answer but not implementing it for now since I dont have to use custom controller. If there is some logic(ex: validation) involved when updating the entity, I am in favor of using the custom controller.

Overriding isBinaryContent() method in CXF LoggingOutInterceptor

I need to extend the Content Types CXF uses to check for binary data, and came across this answer:
Stop Apache CXF logging binary data of MultipartBody attachments
However, when I try to implement the override of isBinaryData() method:
#Override
public boolean isBinaryContent(String contentType) {
return contentType != null && BINARY_CONTENT_MEDIA_TYPES.contains(contentType) || "myContentType".equals(contentType);
}
I get an error accessing BINARY_CONTENT_MEDIA_TYPES:
The field AbstractLoggingInterceptor.BINARY_CONTENT_MEDIA_TYPES is not
visible
In the CXF implementation, this BINARY_CONTENT_MEDIA_TYPES is implemented through a static block as shown in the question referenced at the start. Do I simply just re-declare this List:
private static final List<String> BINARY_CONTENT_MEDIA_TYPES;
static {
BINARY_CONTENT_MEDIA_TYPES = new ArrayList<String>();
BINARY_CONTENT_MEDIA_TYPES.add("application/octet-stream");
BINARY_CONTENT_MEDIA_TYPES.add("image/png");
BINARY_CONTENT_MEDIA_TYPES.add("image/jpeg");
BINARY_CONTENT_MEDIA_TYPES.add("image/gif");
}
If so, I could just add another entry into the List with my required content type.
The field BINARY_CONTENT_MEDIA_TYPES is private and is not accessible from subclasses. The answer in the linked question may have been from an earlier version of CXF, where the list were not private.
As you say you can redeclare the list with your content type also in it. You can also append your logic to the existing logic in the parent implementation:
#Override
protected boolean isBinaryContent(String type)
{
return super.isBinaryContent(type) || "myContentType".equals(type);
}
If future versions of CXF alters the BINARY_CONTENT_MEDIA_TYPES list, this implementation automatically inherit those changes. While if the list is copied, then you manually have to change your implementation.

Getting hibernate persistent object at the time of pre update event

I am implementing pre update event listener in java hibernate 4.3.
I need to get old persistent object value before update occures.
I have tried using event.getOldState() in PreUpdateEventListener. But it gives Object[] as return type. I want the persistent object as return value.
How to get complete persistent object in preUpdateEvent?
The preUpdateEventListener is implemented correctly.
Just need to get Complete persisted object instead i get Object[].
Also tried event.getSession().get(id,persisted.class); //this gives new object as session has set new object to update
Below is code that gives Object[]
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
public class MyEventListener implements PreUpdateEventListener {
public void onPreUpdate(PreUpdateEvent event) {
Object newEntity=event.getEntity(); //Gives new Object which will be updated.
Object[] oldEntity=evetn.getOldState(); //gives old Object[] which can't be converted to persisted Object
//Code here which will give me old persisted objects, hibernate fetches object in array format.
}
}
If i remember well the object array contains all attribute values of given entity :
the index of the associated property can be resolved using the property name array
String[] propertyNames = event.getPersister().getEntityMetamodel.getPropertyNames();
this link may be usefull
I am not sure how listeners work with pure Hibernate, but if you use JPA event listeners, the entity is passed as a parameter to the listener method:
public class MyUpdateListener {
#PreUpdate
public void onPreUpdate(MyEntiy e) {
e.getAttribute();
// do something
}
...
If you define a listener method inside the entity, you can simply access the state of this
#PreUpdate
public void onPreUpdate() {
getAttribute();
// do something
}

Cleanse Hibernate from my object but don't lazy load

I have an JPA+Hibernate entity that I need to send via RMI to a client that doesn't know Hibernate, so I've made a method to "cleanse" Hibernate from it:
// shortened
public class Player {
private Set<Item> ownedItems;
public void makeSerializable() {
ownedItems = new HashSet<Item>(ownedItems);
}
}
However, when I call makeSerializable Hibernate will attempt to lazy-load ownedItems if it's not loaded yet, which I don't want, and which is also impossible because there is Hibernate session. Instead, if ownedItems is not loaded, I'd like to set it to null or an empty set.
How can I do that?
if (!Hibernate.isInitialized(ownedItems)) {
ownedItems = new HashSet<Item>();
}
This is the way to test if a collection is initialized without the need for a session.

Categories