I came up with interesting situation that I already know how to work around, but I was wondering if there is some elegant solution for this.
I have an Entity, which can not have a #Versio field since it is based on a legacy database, and the table has no column to have this kind of value.
Basically it is something like this:
#Entity
public class MyEntity {
#Id
private int id;
#Temporal(TemporalType.DATE)
private java.util.Date lastUpdated;
}
This is basically just for EULA (End User License Agreement) checking.
I want the Date to be updated when the eula has to be re-accepted (The new eula date is got from other place).
For that I was planning to use:
#PrePersist
#PreUpdate
protected void setPersistTime() {
this.lastUpdated = new Date();
}
The #PrePersist is called correctly when the entity is stored for the first time, but on the subsequent times the JPA seems to think that the entity is the same as before and the #PreUpdate won't be called as there is nothing to change.
I was planning to use
em.refresh(myEntity, LockModeType.OPTIMISTIC_FORCE_INCREMENT);
But that won't work without the #Version which I cannot use due to the legacy db. (no version field I could use and the Date is of wrong type for it).
Btw. Using EclipseLink.
Related
What is the best way to check if a record exists and if it doesn't, create it (avoiding duplicates)?
Keep in mind that this is a distributed application running across many application servers.
I'm trying to avoid these:
Race Conditions
TOCTOU
A simple example:
Person.java
#Entity
public class Person {
#Id
#GeneratedValue
private long id;
private String firstName;
private String lastName;
//Getters and Setters Omitted
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, Long>{
public Person findByFirstName(String firstName);
}
Some Method
public void someMethod() {
Person john = new Person();
john.setFirstName("John");
john.setLastName("Doe");
if(personRepo.findByFirstName(john.getFirstName()) == null){
personRepo.save(john);
}else{
//Don't Save Person
}
}
Clearly as the code currently stands, there is a chance that the Person could be inserted in the database in between the time I checked if it already exists and when I insert it myself. Thus a duplicate would be created.
How should I avoid this?
Based on my initial research, perhaps a combination of
#Transactional
#Lock
But the exact configuration is what I'm unsure of. Any guidance would be greatly appreciated. To reiterate, this application will be distributed across multiple servers so this must still work in a highly-available, distributed environment.
For Inserts: if you want to prevent same recordsto be persisted, than you may want to take some precoutions on DB side. In your example, if firstname should be unique, then define a unique index on that column, or a agroup of colunsd that should be unique, and let the DB handle the check, you just insert & get exception if you're inserting a record that's already inserted.
For updates: use #Version (javax.persistence.Version) annotation like this:
#Version
private long version;
Define a version column in tables, Hibernate or any other ORM will automatically populate the value & also verison to where clause when entity updated. So if someone try to update the old entity, it prevent this. Be careful, this doesn't throw exception, just return update count as 0, so you may want to check this.
I'm using spring data (jpaRepository) + Oracle 11g Database.
Here's the code of my JUnit test:
#Test
public void testAjoutUtilisateur() {
Utilisateur utilisateur = new Utilisateur();
(...)
utilisateur=repository.save(utilisateur);
Utilisateur dbutilisateur = repository.findOne(utilisateur.getIdutilisateur());
assertNotNull(dbutilisateur);
When I debug I find that "utilisateur" object returned by repository.save method has an id like "2100" while the corresponding inserted line in the database have an id like "43".
I have an Oracle database with a sequence and a trigger to have the auto incremented property for the id for my "Utilisateur" table.
Here is the id definition in my Utilisateur entity:
#Entity
#NamedQuery(name="Utilisateur.findAll", query="SELECT u FROM Utilisateur u")
#SequenceGenerator(sequenceName="ID_UTILISATEUR_SEQ", name="ID_UTILISATEUR_SEQ")
public class Utilisateur implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ID_UTILISATEUR_SEQ")
private Long idutilisateur;
Where is the problem? Is it within the save method?
Thank you.
Edit:
I figured out that the problem was already solved by the solution of #jhadesdev and the data lines I was talking about were inserted when the triggers were actives.
Finally, I have to mention that by default the JUnit test seems to not insert data in the database (it inserts then rollback). In order to invalidate this behaviour we have to specify the #TransactionConfiguration(defaultRollback=false) annotation in the test class.
For example (in my case):
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:context/dao-context.xml" })
#TransactionConfiguration(defaultRollback=false)
#Transactional
public class UtilisateurRepositoryTest {
Hope it can help someone.
The problem is that two separate mechanisms are in place to generate the key:
one at Hibernate level which is to call a sequence and use the value to populate an Id column and send it to the database as the insert key
and another mechanism at the database that Hibernate does not know about: the column is incremented via a trigger.
Hibernate thinks that the insert was made with the value of the sequence, but in the database something else occurred. The simplest solution would probably be to remove the trigger mechanism, and let Hibernate populate the key based on the sequence only.
Unfortunately I'm getting an OptimisticLockException in my code and I'm not sure why. Perhaps there is someone who can help me with an answer to a general question.
Following scenario:
#Entity
public class MyEntity {
#Id
#GeneratedValue
private Integer id;
#Version
private int version;
private String value;
}
#Singleton
#TransactionManagement(TransactionManagementType.CONTAINER)
public class MyBean {
#PersistenceContext
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test() {
MyEntity myEntity = em.find(MyEntity.class, 1);
}
}
CMT are used. Method test() requires a new transaction.
Now my question: Can method test() throw an OptimisticLockException if there is another thread in another bean using the same persistence context changing my entity before commit although I only use find and don't update anything in my method test()?
from this blog
JPA Optimistic locking allows anyone to read and update an entity, however a version check is made upon commit and an exception is thrown if the version was updated in the database since the entity was read
So there is no need to do an update to get an OptimisticLockingException. Assume myEntity.getVersion()==1 when you read it. You will have an OptimisticLockingException if, at commit (i.e. when your test() method ends), the actual value in the version column is != 1.
It means that someone updated the entity (in the mean time between the READ and the transaction COMMIT) and so the values you have just read are no more valid at commit time.
I suspect there's no perfect solution to this problem so least worst solution are more than welcome.
I'm implementing a dashboard using PrimeFaces and I would like to persist the model backing it (using JPA2). I've written my own implementation of DashboardModel and DashboardColumn with the necessary annotations and other fields I need. The model is shown below:
#Entity
public class DashboardSettings implements DashboardModel, Serializable{
#Id
private long id;
#OrderColumn( name="COLUMN_ORDER" )
private List<DashboardColumn> columns;
...a few other fields...
public DashboardSettings() {}
#Override
public void addColumn(DashboardColumn column) {
this.columns.add(column);
}
#Override
public List<DashboardColumn> getColumns() {
return columns;
}
...snip...
}
The problem is the columns field. I would like this field to be persisted into it's own table but because DashboardColumn is an interface (and from a third party so can't be changed) the field currently gets stored in a blob. If I change the type of the columns field to my own implementation (DashboardColumnSettings) which is marked with #Entity the addColumn method would cease to work correctly - it would have to do a type check and cast.
The type check and cast is not the end of the world as this code will only be consumed by our development team but it is a trip hazard. Is there any way to have the columns field persisted while at the same time leaving it as a DashboardColumn?
You can try to use targetEntity attribute, though I'm note sure it would be better than explicit cast:
#OrderColumn( name="COLUMN_ORDER" )
#OneToMany(targetEntity = DashboardColumnSettings.class)
private List<DashboardColumn> columns;
Depends on the JPA implementation (you don't mention which one); the JPA spec doesn't define support for interface fields, nor for Collections of interfaces. DataNucleus JPA certainly allows it, primarily because we support it for JDO also, being something that is part of the JDO spec.
I am trying openjpa and jpa. All I have is one entity class as corresponding table in the database. one of the attributes of the entity is username and corresponding row in the db table has varchar2(20). and in my main method what i tried to persist and instance of the entity with username longer than 20.
All I am doing is
em.getTransaction().begin();
em.persist(entity); //entity here is the instance with the username longer than 20
em.getTransaction().commit();
I tried this, hoping to get some other kind of exception, but I don't why I am getting optimisticklockexception.
I do not have any locking setting. I mean I am using default values for locking property.
Does anybody know what's happening here?
Not sure why this happens...I have noticed that the OptimisticLockException can be thrown in weird cases...
Adding a version field to your table and entity can often make OpenJPA work better with locking...
In your entity bean add this (also add the column named VERSION to your table):
private Long version;
#Version
#Column(name="VERSION")
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
Hope this helps...