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
}
Related
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.
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.
I have two classes that share the same flow: I need to get info from the DB, process it and update it on a nosql DB.
The difference is very small: in one case I am sure I will get only one entity for each nosql record, so I can just process them, store them temporarily in a list and then get all the items in the list after the entities were processed.
In the second case I can have more than one entity for each index record. In this case I have to retrieve the current processed data, merge, and store again. After every entity was processed I am able to get the map.values() so I can update to the nosql.
As seen the difference is only on how the items are processed and how the data structure is temporarily stored.
My parent class has something like this:
public void run()
{
process();
sendToNosql(getProcessed())
}
protected abstract void process();
protected abstract List<Stuff> getProcessed();
The simple children is something like this:
List<Stuff> myStuff = new ArrayList<>();
#Override
protected void process()
{
for(Entity e : loadFromDB()){
myStuff.add(process(e));
}
}
#Override
protected List<Stuff> getProcessed(){
return myStuff;
}
And the complex one:
Map<int, Stuff> myStuff = new HashMap<>();
#Override
protected void process()
{
for(Entity e : loadFromDB()){
myStuff.put(e.getId(), process(e));
}
}
#Override
protected List<Stuff> getProcessed(){
return myStuff.values();
}
Because everything will be processed and the results will be used later, myStuff is an object attribute. This works fine.
But because these are stateless beans, I am only safe while I am in the run() method, if any other method is called, the myStuff attribute is not reliable.
I would like to avoid this risk, so I would like to have the run() method as myStuff attribute scope. How can I improve my architecture to achieve this while abstracting the underlying data structure?
Or is this problem telling me I made something very wrong?
For information in case anyone finds this question:
I used the State pattern, so the child class generates a state when the process method starts. The state hides the way the information is stored (hash or list).
I am in a process of creating a library for Windows Azure. So, here is a simple generic method to insert a new record:
public <TEntity extends SyncableBase> void addRemoteItem(TEntity itemToAdd) {
MobileServiceTable<TEntity> remoteTable = (MobileServiceTable<TEntity>)mobileServiceClient.getTable(itemToAdd.getClass());
Gson gson = new Gson();
String json = gson.toJson(itemToAdd);
remoteTable.insert(itemToAdd, new TableOperationCallback<TEntity>() {
public void onCompleted(TEntity entity, Exception exception, ServiceFilterResponse response) {
if (exception == null) {
Log.e("SuccessMe", "Success");
// Insert succeeded
}
else {
Log.e("SuccessMe", "Nah "+ exception.getMessage());
// Insert failed
}
}
});
}
Now, here is my SyncableBase class:
public class SyncableBase {
#SerializedName("Bingo")
private int localId;
//#SerializedName("id")
private String remoteId;
private boolean isDeleted;
}
And my ToDoItem class:
public class ToDoItem extends SyncableBase {
private String name;
}
Now, the problem is: This fails with Error processing request. But if I don't extend ToDoItem from SyncableBase and move all those members directly to the former, everything works just fine. As can be seen, I tried serializing my Java object just before calling inset. The serialized json is exactly the same in both the cases. What am I doing wrong?
After days of debugging, I have come up with a potential problem and it's definite solution. This holds valid for the Android Azure SDK valid at the time of writing this. A couple of notes:
For seamless transactions, the id member must be present in the inherited class and not the super class. While validating the object, Azure SDK uses reflection and tries to find a filed with name (or serialized name) equal to id or Id. Somehow, the member isn't found if it is present in super class and we get error.
GSON (the thing which serializes Java object to JSON) is configured inside SDK so that it serializes even the null members. So, when there are no columns in WAMS table (fresh table) and try to insert an item with null fields, the error is thrown. The filed must hold a value so that the type of corresponding column to be generated can be determined. A new field with null value will give you an error.
Here's an example of an item being put in a fresh table.
{
"id": "Awesome unique id",
"name": Beautiful Wallpaper",
"description": null
}
Here, the WAMS would know that it has to generate a column called description; however, it wouldn't know the type of this column. Hence, first object must always have non-null values.
In my particular case, both the problems are there. Taking care of these things solved them.
This is a simplifed version of what I'm trying to do. I have a map which maps an integer id to a list of strings. One of these lists from the map is displayed by a JTable at all times. (Depending on which id needs to be displayed) All information for the map is coming from a database, and is constantly being added to and removed from.
My DataClass which stores the full map and receives the updates from the database:
DataClass {
Map(Integer, List<String>) map;
// TabelModel gets list here
public List<String> getList(int id) {
return map.get(id);
}
// Updates from database come here
public updateList(int id, String info) {
if (map.containsKey(id) {
map.get(id).add(info);
}
else {
map.put(id, new List<String>(info));
}
}
// Remove from list, etc all down here
...
}
My Table model class:
MyTableModel extends DefaultTableModel {
List data;
public void updateData(int id) {
data = getList(id)
fireTableDataChanged();
}
... All the other stuff needed ...
}
Since database updates occur in batches, I know that at the end of a batch I have to update the table. So the DataClass informs the UI class which informs the table to update. This causes the updateData(id) to be called which retrieves a new set of data from the DataClass and calls fireTableDataChanged();
My Question are:
Is this the right way to go about updating/storing the data in the table?
Should getList return a clone of the data? If I just return the reference all updates from the database would be accessing that same reference and since these updates aren't running in the EDT wouldn't that be bad/frowned upon/wrong?
How can I do this using Java Events? PropertyChangeEvent? If so, how?
To the extent that your questions are related,
No; when new data is available, your updateData() method should update the internal data structure of your TableModel and fire an appropriate event; because DefaultTableModel knows nothing of your List, extend AbstractTableModel, as shown here; the JTable will update itself in response.
No; your database access layer should retain no references to queried objects, and no cloning should be necessary; forward queried objects to your display layer from the process() method of SwingWorker, or similar.
Use a TableModelListener to learn when and how the TableModel has changed; update the database accordingly.