hibernate column uniqueness question - java

I'm still in the process of learning hibernate/hql and I have a question that's half best practices question/half sanity check.
Let's say I have a class A:
#Entity
public class A
{
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(unique=true)
private String name = "";
//getters, setters, etc. omitted for brevity
}
I want to enforce that every instance of A that gets saved has a unique name (hence the #Column annotation), but I also want to be able to handle the case where there's already an A instance saved that has that name. I see two ways of doing this:
1) I can catch the org.hibernate.exception.ConstraintViolationException that could be thrown during the session.saveOrUpdate() call and try to handle it.
2) I can query for existing instances of A that already have that name in the DAO before calling session.saveOrUpdate().
Right now I'm leaning towards approach 2, because in approach 1 I don't know how to programmatically figure out which constraint was violated (there are a couple of other unique members in A). Right now my DAO.save() code looks roughly like this:
public void save(A a) throws DataAccessException, NonUniqueNameException
{
Session session = sessionFactory.getCurrentSession();
try
{
session.beginTransaction();
Query query = null;
//if id isn't null, make sure we don't count this object as a duplicate
if(obj.getId() == null)
{
query = session.createQuery("select count(a) from A a where a.name = :name").setParameter("name", obj.getName());
}
else
{
query = session.createQuery("select count(a) from A a where a.name = :name " +
"and a.id != :id").setParameter("name", obj.getName()).setParameter("name", obj.getName());
}
Long numNameDuplicates = (Long)query.uniqueResult();
if(numNameDuplicates > 0)
throw new NonUniqueNameException();
session.saveOrUpdate(a);
session.getTransaction().commit();
}
catch(RuntimeException e)
{
session.getTransaction().rollback();
throw new DataAccessException(e); //my own class
}
}
Am I going about this in the right way? Can hibernate tell me programmatically (i.e. not as an error string) which value is violating the uniqueness constraint? By separating the query from the commit, am I inviting thread-safety errors, or am I safe? How is this usually done?
Thanks!

I think that your second approach is best.
To be able to catch the ConstraintViolation exception with any certainty that this particular object caused it, you would need to flush the session immediately after the call to saveOrUpdate. This could introduce performance problems if you need to insert a number of these objects at a time.
Even though you would be testing if the name already exists in the table on every save action, this would still be faster than flushing after every insert. (You could always benchmark to confirm.)
This also allows you to structure your code in such a way that you could call a 'validator' from a different layer. For example, if this unique property is the email of a new user, from the web interface you can call the validation method to determine if the email address is acceptable. If you went with the first option, you would only know if the email was acceptable after trying to insert it.

Approach 1 would be ok if:
There is only one constraint in the entity.
There is only one dirty object in the session.
Remember that the object may not be saved until flush() is called or the transaction commited.
For best error reporting I would:
Use approach two for every constraint violation, so I can give an specific error for each of them..
Implement an interceptor that in case of an constraint exception retries the transaction (a max number of times) so the violation can't be caught in one of the tests. This is only needed depending on the transaction isolation level.

Related

How to check special conditions before saving data with Hibernate

Sample Scenario
I have a limit that controls the total value of a column. If I make a save that exceeds this limit, I want it to throw an exception. For example;
Suppose I have already added the following data: LIMIT = 20
id
code
value
1
A
15
2
A
5
3
B
12
4
B
3
If I insert (A,2) it exceeds the limit and I want to get exception
If I insert (B,4) the transaction should be successful since it didn't exceed the limit
code and value are interrelated
What can I do
I can check this scenario with required queries. For example, I write a method for it and I can check it in the save method. That's it.
However, I'm looking for a more useful solution than this
For example, is there any annotation when designing Entity ?
Can I do this without calling the method that provides this control every time ?
What examples can I give ?
#UniqueConstraint checking if it adds the same values
Using transaction
The most common and long-accepted way is to simply abstract in a suitable form (in a class, a library, a service, ...) the business rules that govern the behavior you describe, within a transaction:
#Transactional(propagation = Propagation.REQUIRED)
public RetType operation(ReqType args) {
...
perform operations;
...
if(fail post conditions)
throw ...;
...
}
In this case, if when calling a method there is already an open transaction, that transaction will be used (and there will be no interlocks), if there is no transaction created, it will create a new one so that both the operations and the postconditions check are performed within the same transaction.
Note that with this strategy both operation and invariant check transactions can combine multiple transactional states managed by the TransactionManager (e.g. Redis, MySQL, MQS, ... simultaneously and in a coordinated manner).
Using only the database
It has not been used for a long time (in favor of the first way) but using TRIGGERS was the canonical option used some decades ago to check postconditions, but this solution is usually coupled to the specific database engine (e.g. in PostgreSQL or MySQL).
It could be useful in the case where the client making the modifications is unable or unwilling (not safe) to check postconditions (e.g. bash processes) within a transaction. But nowadays it is infrequent.
The use of TRIGGERS may also be preferable in certain scenarios where efficiency is required, as there are certain optimization options within the database scripts.
Neither Hibernate nor Spring Data JPA have anything built-in for this scenario. You have to program the transaction logic in your repository yourself:
#PersistenceContext
EntityManager em;
public addValue(String code, int value) {
var checkQuery = em.createQuery("SELECT SUM(value) FROM Entity WHERE code = :code", Integer.class);
checkQuery.setParameter("code", code);
if (checkQuery.getSingleResult() + value > 20) {
throw new LimitExceededException("attempted to exceed limit for " + code);
}
var newEntity = new Entity();
newEntity.setCode(code);
newEntity.setValue(value);
em.persist(newEntity);
}
Then (it's important!) you have to define SERIALIZABLE isolation level on the #Transactional annotations for the methods that work with this table.
Read more about serializable isolation level here, they have an oddly similar example.
Note that you have to consider retrying the failed transaction. No idea how to do this with Spring though.
You should use a singleton (javax/ejb/Singleton)
#Singleton
public class Register {
#Lock(LockType.WRITE)
public register(String code, int value) {
if(i_can_insert_modify(code, value)) {
//use entityManager or some dao
} else {
//do something
}
}
}

Good practice for saving only unique by property records to the database

I would like to know what good practice is in the following situation:
#Entity
#Table(name = "word")
class WordEntity(uuid: UUID? = null,
#Column(nullable = false, unique = true) val name: String
) : BaseEntity(uuid)
If i have an Iterable of this entity and want to call the method saveAll to persist it to the database a ConstraintViolationException can be thrown.
So my goal is to add only unique records to the database. I can loop and do something like this:
fun saveAll(words: List<WordRequest>): List<WordDTO> {
...
for (wordEntity in wordEntities) {
try {
result.add(wordRepository.save(wordEntity))
} catch (e: RuntimeException) { }
}
...
}
OR i can do a findByName on every loop to check if it exists or not.
So my question is which option should i go for and is there a better way to handle this?
You've got 2 options:
Some databases support INSERT IF NOT EXIST syntax. It's doing exactly what you need. But it means you need write a native SQL.
If you want to stick with ORM - you'll have to open a new session (and start new transaction) for each of the records. Because if an exception is thrown you can't keep relying on the same Session (EntityManager), the behavior is undocumented.
Checking if record already exists may lower the number of failed INSERTs (you can do this in 1 SELECT using in() statement), but it doesn't guarantee anything - there's a time between your SELECT and INSERT. Another transaction could INSERT in between. Well, unless you use Serializable isolation level.

Changing JPA entity after persist() but before flush() does not work

I'm using JPA 2 with the Hibernate ver. 4.1.7.Final as JPA implementation. I'm also using Spring framework v. 3.1.2.RELEASE to be clear. And here is my problem.
I have written a method to add/update my User entity.
#Override
#Transactional
public void saveUser(UserForm userForm) {
User user;
if (userForm.getId() == null) { // new user
user = new User();
user.setCreationDate(new Date());
entityManager.persist(user); // !!!
} else {
user = entityManager.find(User.class, userForm.getId());
}
user.setFirstName(userForm.getFirstName());
user.setLastName(userForm.getLastName());
user.setMiddleName(userForm.getMiddleName());
user.setEmail(userForm.getEmail());
user.setRole(entityManager.find(Role.class, 1));//TODO
user.setLogin(userForm.getLogin());
user.setPassword(userForm.getPassword1());
entityManager.flush();
}
I'm testing addition of user (userForm.getId() == null). And the above code doesn't work, giving error:
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Column 'first_name' cannot be null
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1377)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1306)
at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:989)
...
But. If I move call to persist() to the end before flush() all works fine:
#Override
#Transactional
public void saveUser(UserForm userForm) {
User user;
if (userForm.getId() == null) { // new user
user = new User();
user.setCreationDate(new Date());
} else {
user = entityManager.find(User.class, userForm.getId());
}
user.setFirstName(userForm.getFirstName());
user.setLastName(userForm.getLastName());
user.setMiddleName(userForm.getMiddleName());
user.setEmail(userForm.getEmail());
user.setRole(entityManager.find(Role.class, 1));//TODO
user.setLogin(userForm.getLogin());
user.setPassword(userForm.getPassword1());
entityManager.persist(user);// !!!
entityManager.flush();
}
I think what happens is in first (problem) case it tries to store the User object data existed at time of persist call, although the actual save executed during flush() (upd. Wrong. It tries to issue sql insert exactly at persist() call). What make me think this way is when I tried to debug I have found that it still tries to insert correct creation_date somewhere inside, but other values are nulls.
But I swear that in my other project I've been working on some years ago this worked just fine, though then I used Oracle instead of MySQL (I don't think this is the reason) and older versions of frameworks.
What could be the problem here? Maybe there is some configuration option for Hibernate affecting this?
Or this is the correct behaviour and my misunderstanding of JPA API?
UPD. I'm using
#Id
#GeneratedValue
#Column(columnDefinition="INT")
private int id;
for id field in User entiry. I believe it means generation strategy = AUTO, which is appropriate for mysql auto_increment key.
You set your user first name to null (user.setFirstName(userForm.getFirstName());) because userForm.getFirstName() returns null, and your error displays that: Column 'first_name' cannot be null.
You should check why userForm.getFirstName() returns null or allow user's first name to be null because your current entity configuration does not allow that.
Maybe you could show us User entity?
Thats correct behaviour. At the time of your first call persist(), user's firstName is null. Enventhough you have set the first name after this point, but before the flush, JPA/Hibernate prepared commands would be something like,
At the time of persist, the state of user object lets say, user#version1
Insert into user values (id, creationdate, null, null..) from user#version1 object
Now a new user version exists user#version2
At the time of flush,
check for any pending inserts/updates.. bring all the objects to version1, which is at the time of last persist.
Note that, flush may or may not do physical insert operation in DB, but the objects are are brought to the correct state, meaning non persisted changes would be lost.
Dont worry if it was working fine before may be with hibernate 3, and started not working when you migrated to hibernate 4. I have several issues, where hibernate 3 is no problem, but doesnt work on hibernate 4 without additional fixes.

JPA - create-if-not-exists entity?

I have several mapped objects in my JPA / Hibernate application. On the network I receive packets that represent updates to these objects, or may in fact represent new objects entirely.
I'd like to write a method like
<T> T getOrCreate(Class<T> klass, Object primaryKey)
that returns an object of the provided class if one exists in the database with pk primaryKey, and otherwise creates a new object of that class, persists it and returns it.
The very next thing I'll do with the object will be to update all its fields, within a transaction.
Is there an idiomatic way to do this in JPA, or is there a better way to solve my problem?
I'd like to write a method like <T> T getOrCreate(Class<T> klass, Object primaryKey)
This won't be easy.
A naive approach would be to do something like this (assuming the method is running inside a transaction):
public <T> T findOrCreate(Class<T> entityClass, Object primaryKey) {
T entity = em.find(entityClass, primaryKey);
if ( entity != null ) {
return entity;
} else {
try {
entity = entityClass.newInstance();
/* use more reflection to set the pk (probably need a base entity) */
return entity;
} catch ( Exception e ) {
throw new RuntimeException(e);
}
}
}
But in a concurrent environment, this code could fail due to some race condition:
T1: BEGIN TX;
T2: BEGIN TX;
T1: SELECT w/ id = 123; //returns null
T2: SELECT w/ id = 123; //returns null
T1: INSERT w/ id = 123;
T1: COMMIT; //row inserted
T2: INSERT w/ name = 123;
T2: COMMIT; //constraint violation
And if you are running multiple JVMs, synchronization won't help. And without acquiring a table lock (which is pretty horrible), I don't really see how you could solve this.
In such case, I wonder if it wouldn't be better to systematically insert first and handle a possible exception to perform a subsequent select (in a new transaction).
You should probably add some details regarding the mentioned constraints (multi-threading? distributed environment?).
Using pure JPA one can solve this optimistically in a multi-threaded solution with nested entity managers (really we just need nested transactions but I don't think that is possible with pure JPA). Essentially one needs to create a micro-transaction that encapsulates the find-or-create operation. This performance won't be fantastic and isn't suitable for large batched creates but should be sufficient for most cases.
Prerequisites:
The entity must have a unique constraint violation that will fail if two instances are created
You have some kind of finder to find the entity (can find by primary key with EntityManager.find or by some query) we will refer to this as finder
You have some kind of factory method to create a new entity should the one you are looking for fail to exist, we will refer to this as factory.
I'm assuming that the given findOrCreate method would exist on some repository object and it is called in the context of an existing entity manager and an existing transaction.
If the transaction isolation level is serializable or snapshot this won't work. If the transaction is repeatable read then you must not have attempted to read the entity in the current transaction.
I'd recommend breaking the logic below into multiple methods for maintainability.
Code:
public <T> T findOrCreate(Supplier<T> finder, Supplier<T> factory) {
EntityManager innerEntityManager = entityManagerFactory.createEntityManager();
innerEntityManager.getTransaction().begin();
try {
//Try the naive find-or-create in our inner entity manager
if(finder.get() == null) {
T newInstance = factory.get();
innerEntityManager.persist(newInstance);
}
innerEntityManager.getTransaction().commit();
} catch (PersistenceException ex) {
//This may be a unique constraint violation or it could be some
//other issue. We will attempt to determine which it is by trying
//to find the entity. Either way, our attempt failed and we
//roll back the tx.
innerEntityManager.getTransaction().rollback();
T entity = finder.get();
if(entity == null) {
//Must have been some other issue
throw ex;
} else {
//Either it was a unique constraint violation or we don't
//care because someone else has succeeded
return entity;
}
} catch (Throwable t) {
innerEntityManager.getTransaction().rollback();
throw t;
} finally {
innerEntityManager.close();
}
//If we didn't hit an exception then we successfully created it
//in the inner transaction. We now need to find the entity in
//our outer transaction.
return finder.get();
}
I must point out there's some flaw in #gus an's answer. It could lead to an apparent problem in a concurrent situation. If there are 2 threads reading the count, they would both get 0 and then do the insertion. So duplicate rows created.
My suggestion here is to write your native query like the one below:
insert into af_label (content,previous_level_id,interval_begin,interval_end)
select "test",32,9,13
from dual
where not exists (select * from af_label where previous_level_id=32 and interval_begin=9 and interval_end=13)
It's just like an optimistic lock in the program. But we make the db engine to decide and find the duplicates by your customized attributes.
How about use orElse function after findByKeyword? You can return a new instance if no record is found.
SearchCount searchCount = searchCountRepository.findByKeyword(keyword)
.orElse(SearchCount.builder()
.keyword(keyword)
.count(0)
.build()) ;
There is an easy solution to have this tackled even in a concurrent environment.
Use optimistic locking on your entities with
#Version private Long version; and <column name="version" type="BIGINT"/> in your liquibase table creation.
If you try to save a new entity thats (composite) p_pkey already exists, a DataIntegrityViolationException will be thrown up to the JpaRepository. So there's no need to worry about concurrent locking in your service layer - the database will know if an entity exists.

JPA/Hibernate: code based validation of jpa queries

What is the right way do validate a jpa query programmatically. Hibernate validates all annotation based named queries on entities. But how can I call this validation routine on programmatically builded jpa queries, to check for errors?
#Entity
public class Foo {
#Id
public int id;
public String name;
}
main(...) {
Query q = getEntityManager().createQuery("select e from " + Foo.class.getName() + " e where e.name = 'x' ");
// validate q here
}
Don't. Integration test the hell out of your code, using a real database with the same schema as your production environment.
Think about it: if you create a malformed query, that's a programming bug. What are you going to do with the information? Tell the user that a JPA query is malformed? All you can realistically do is log the error and tell the user "something bad happened". You'll know it's a malformed query when you check the logs later anyway...
Edit
It might also be worth investigating the createQuery() call by feeding it bad data - the javadoc here suggests that it can throw a HibernateException, and I'm not sure what it could do with a string other than validate it...
Either you can use createQuery, or you need to put your class name while writing a JPQL.

Categories