Tracking last change to an object with #Version annotation in EclipseLink - jpa

Using JPA with EclipseLink, I would like to track the timestamp of the last update made to an entity instance. Assuming that this would be easy to combine with optimistic locking, I defined the entity as follows:
import javax.persistence.Version;
[...]
#Entity
public class Foo {
#Id int id;
#Version Timestamp lastChange;
[...]
}
Updating a changed object is done with the following code:
EntityManager em = Persistence.createEntityManagerFactory("myConfiguration");
em.getTransaction().begin();
em.merge(foo);
em.getTransaction().commit();
I would expect that foo.lastChange would be set to the new timestamp each time an update to a changed instance is committed. However, while the field LASTCHANGE is updated in the database, it is not updated in the object itself. A second attempt to save the same object again thus fails with an OptimisticLockException. I know that EclipseLink allows to choose between storing the version-field in cache or directly in the object and I made sure that the configuration is set to IN_OBJECT.
The obvious question is: How to get the foo.lastChange field set to the updated timestamp value when saving to the database? Would
foo = em.find(Foo.class, foo.id);
be the only option? I suspect there must be a simpler way to this.

merge does not modify its argument. It copies the state from its argument to the attached version of its argument, and returns the attached version. You should thus use
foo = em.merge(foo);
// ...
return foo;

Related

JPA: generate non pk unique and random alphanumeric value

I want to uniquely identity an entity without using the primary key. So I thought about generating an unique and random value. Moreover, value must be easy to read / manually copy and is expected to be 6 or 7 characters long.
Design
My entity A:
public class A{
// ...
#Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
private String value;
// ...
public String getValue(){
return value;
}
protected void setValue(String value){
this.value = value;
}
}
represented in the database by the table
CREATE TABLE IF NOT EXISTS schema.mytable{
-- ...
value TEXT NOT NULL DEFAULT generate_unique_value_for_mytable(),
-- ...
CONSTRAINT "un_value" UNIQUE (value),
-- ...
}
I thought letting the database handling this and then fetch the value...
Problem
With the current design, value is correctly generated in the database but when JPA fetches A entities, value field is empty.
I cannot remove insertable=false otherwise, it will hit against the NOT NULL constraint
If I remove insertable=false and I put some dummy data, the data overrides the value generated by generate_unique_value_for_mytable()
If I remove everything in the Column annotation, I can save the A entity but value is still empty
Ugly solution
I couldn't find a proof but it looks like having the database generating a value is a bad idea. I do have the same problem for a non-primary key field which is generated by a sequence: I cannot fetch the value from the database.
So my ugly solution is to decorate the create() method of the EJB responsible for A entities:
public class Aejb{
public void create(A entity){
// method kind of ensures randomness
String value = MyUtil.generateRandomValue();
A isThereAnyoneHere = findByValue(value);
while(isThereAnyoneHere != null){
String value = MyUtil.generateRandomValue();
isThereAnyoneHere = findByValue(value);
}
// unicity is ensured
entity.setValue(value);
em.persist(entity);
}
}
Questions
Can I fetch a non-primary key value generated by the database from a JPA entity? Value can be generated by a function or a sequence.
Is there a more elegant solution than my ugly workaround to provide an unique and random value?
Yes.You haven't mentioned your database, but it is possible for
Oracle to return the value inserted via triggers, and have
Eclipselink obtain this value in your model - see
https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_returninsert.htm
Set the value using a #PrePersist method that will get executed
before the entity is inserted, but if you are relying on one or more database queries, you will run into performance issues, as inserting a new A will be expensive. You might instead just insert the random value and deal with the occasional conflict, and pick some random that has less chance of overlaps, like a UUID.
If I understand correctly, #Generated annotation should do the trick. This annotation sets the value from database DEFAULT field value.
Example:
#Generated(GenerationTime.INSERT)
#Column(name="value", unique=true, nullable=false, insertable=false, updatable=false)
private String value;
However there is a drawback: if you decide to set value of your field in Java, it would be overwritten by Hibernate using the result from DEFAULT in your database.
Self-answer to mark question as closed
Final solution
We finally went for a combination of
Stored procedures: the database will generate the value. The procedure also ensures that the value is unique across the table
Named queries: to fetch the generated value by the procedure. I did not use NamedStoredProcedures because we are using PostgreSQL and PostgreSQL JDBC driver did not support name parameters which raised some problems.
With this configuration, the EJB is sure to have at most one database call to fetch the requested value.
Response to other answers
Here is a summary of the other answers feedback for self-reference and next readers:
Oracle trigger: we're using PostgreSQL :(
UUID: We had the constraint of having our unique and random code human-readable. An end-user is assumed to be able to manually rewrite it. Consequently, we could not have a long String such as an UUID.
PrePersist: Other business actions take place after the code generation in the same transaction which means that those actions need to be redone in case of collision. I'm not very confident about managing JPA exception (transaction scope and so on) so I preferred not to play with it.
#Generated: This is a Hibernate specific feature. We're using EclipseLink
Database Trigger: If code were purely generated at database level, I encountered the same problems of not fetching the value: the value is properly generated as database level but the entity will have the value as null

jpa2 force em to refresh a field setted like insertable=false

I am working with jpa 2.0 and I have a field in a table of the database that has value by default, I put that field in my definition of entities as insertable = false so that when inserted retain the default value, the insert is done correctly but when requery the object that field is null in the entity, however, that it has been inserted correctly.
This is my code:
#Entity
#Table(name="SOME_TABLE")
public class SomeTable implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private SomeTablePK id;
#Column(name="X1")
private String x1;
**#Column(name="X2", insertable=false)**
private Date x2;
... more fields....
... setters and getters...
}
there any way to force the entity manager to refresh the value of the field that I used as insertable = false? or what can i do to fix it?
Thank you very much.
PS. It is important to mention that in my persistence.xml and place the following line to disable the cache.
<properties>
<property name="javax.persistence.sharedCache.mode" value="NONE"/>
</properties>
You will need to invoke manually the refresh after the flush operation.
The Spec (3.2.4 Synchronization to the Database) says that:
The state of persistent entities is synchronized to the database at transaction commit. This synchroniza- tion involves writing to the database any updates to persistent entities and their relationships as speci- fied above.
An update to the state of an entity includes both the assignment of a new value to a persistent property or field of the entity as well as the modification of a mutable value of a persistent property or field[28].
Pay attention below:
Synchronization to the database does not involve a refresh of any managed entities unless the refresh operation is explicitly invoked on those entities or cascaded to them as a result of the specification of the cascade=REFRESH or cascade=ALL annotation element value.

JPA error "Cannot merge an entity that has been removed" trying to delete and reinsert a row with SpringData

I've an Entity (with a primary key that is not generated by a sequence) like this in a Spring Data JPA/Eclipselink environment :
#Entity
#Table(name="MY_ENTITY")
public class MyEntity implements Serializable {
#Id
#Column(insertable=true, updatable=true, nullable=false)
private String propertyid;
\\other columns
}
and I'm trying to delete a row from the table and reinsert it (with the same primary key).
My approach is to call deleteAll() to clean the table and then save() the new Entity :
#Transactional
public void deleteAndSave(MyEntity entity) {
propertyInfoRepository.deleteAll();
propertyInfoRepository.flush(); // <- having it or not, nothing changes
propertyInfoRepository.save(entity);
}
but this gives me this error :
Caused by: java.lang.IllegalArgumentException: Cannot merge an entity that has been removed: com.xxx.MyEntity#1f28c51
at org.eclipse.persistence.internal.sessions.MergeManager.registerObjectForMergeCloneIntoWorkingCopy(MergeManager.java:912)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfCloneIntoWorkingCopy(MergeManager.java:494)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:271)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeCloneWithReferences(UnitOfWorkImpl.java:3495)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.mergeCloneWithReferences(RepeatableWriteUnitOfWork.java:378)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeCloneWithReferences(UnitOfWorkImpl.java:3455)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.mergeInternal(EntityManagerImpl.java:486)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.merge(EntityManagerImpl.java:463)
....
What am I doing wrong?
I do not understand why it is trying to merge the entity instead of simply reinsert it after its deletion.
Thanks for your help!
Directly to answer your question:
The problem is that the entity that you try to save has already a persistent identity, i.e an ID, which is why your repository will try to merge, and not to persist the entity.
If you see this question it seems that it is triggered (at least) on the level of the Spring Repository, so you might consider overriding the save method of the repository and test whether the problem is still there.
JPA EntityManager keeps track of the state of each managed entity. In your case, you delete the entity and then try to merge it, which raises the exception. I can't tell if your approach is correct (seems weird to delete and then merge) since you don't provide the whole picture but you can try the following:
Assuming em is your EntityManager and entity your entity:
em.remove(entity); //This will perform the delete
MyEntity detachedEntity = em.detach(entity); //Gets a detached copy of the entity, EM will not operated on this unless told to do so (see below)
detachedEntity.setId(null) // Avoid duplicate key violations; Optional since you are deleting the original entity
em.persist(detachedEntity); // This will perform the required insert

version attributes behavior in bulk update

An application uses optimistic locking by defining version attributes in its entity classes. The application performs a bulk update of the entities using a JPQL query.In this case which of below statement is correct?
A.The persistence provider will ensure that the version value in each table is updated.
B.The value of the Version attributes of the updated entitles should be also be explicitly updated by the query.
As per JPA specification
"Bulk update maps directly to a database update operation, bypassing optimistic locking checks. Portable
applications must manually update the value of the version column, if desired, and/or manually validate
the value of the version column."
So as per my understanding option B is correct answer. But some of my colleague are saying other-way. Can you please tell me the correct behavior?
You are right, what it comes to specification A is definitely wrong answer.
Specific implementations of specification are still aloud to update value of #Version when JPQL bulk update is done. How certain implementation behaves is hopefully documented. When not, following can be used to check does bulk update affect version:
#Entity
public class SomeEntity {
#Id private int id;
private String someValue;
#Version private int version;
//getters, setters
}
//creating entity
tx.begin();
SomeEntity se = new SomeEntity();
se.setId(1);
em.persist(se);
tx.commit();
String versionJpql = "SELECT se.version FROM SomeEntity se WHERE se.id = 1";
//original version
Integer version = em.createQuery(versionJpql,Integer.class).getSingleResult();
//bulk update
tx.begin();
em.createQuery(
"UPDATE SomeEntity se SET se.someValue='some' WHERE se.id = 1"
).executeUpdate();
tx.commit();
Integer versionAfterUpdate = em.createQuery(versionJpql, Integer.class)
.getSingleResult();
//has version been changed:
assertEquals(version, versionAfterUpdate);

JPA - #Version - increase on read

I have implemented a simple entity ejb with a #version annotation. I expect that the version number will increase after each update of an entity.
#Version
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
But this seems not to work as expected.
Also each time when I read an entity, the version number increases automatically(!?). I expect that the version only increases after a commit?
Can anybody explain why my version increases also on reads?
Finally I found the reason for the strange behavior.
The problem occurs during a method where I copy all values from my (still attached) entity into a detached domain model object. This all works well, since I read an attached entity contaning a complexe data structure (a vector containg HashMaps). I copied this values with the .addAll method from the List interface:
List activePropertyValue = (List)mapEntry.getValue();
// value contains HashMaps!
List detachePropertyValue = new Vector();
detachePropertyValue.addAll(activePropertyValue);
But it seems that this changed the hash value from the attached entity data property. So after all the entity was updated in the database, and the version number increases.
I solved the problem by detaching the entity before I copy all values:
manager.detach(aEntity);
.....
List activePropertyValue = (List)mapEntry.getValue();
// value contains HashMaps!
List detachePropertyValue = new Vector();
detachePropertyValue.addAll(activePropertyValue);