public class InsertOrFetcher extends Object
Reads an instance from a database, creating it just in time if it doesn't exist yet.
It is sometimes desirable to create rows "just in time" in a database.
For example, to maintain a count of the number of SMS sent per day, a database table with columns (day, count) might be created. Each time an SMS is sent, a row should be created for that day if none exists yet, and the count in the previously existing row should be incremented otherwise.
Hibernate provides no facility for managing such "just in time" creation. (The saveOrUpdate method looks like it might help, but assumes the application knows whether the row already exists or not. In reality, in a concurrent environment, as soon as the application has determined whether the row exists or not, the information is already stale, as some other session might have changed the data.)
Session s = .... ; // Hibernate Session DailyLog newObj = new DailyLog(); newObj.setDay(getTodaysDate()); newObj.setCount(0); Collection<String> uniqueIdentifier = Arrays.asList("day"); DailyLog todaysLog = InsertOrFetcher.loadAndLock( DailyLog.class, s, newObj, uniqueIdentifier); todaysLog.incrementCount();
If the row didn't exist in the database, then newObj is inserted and returned. If the row existed then it is fetched and returned.
In addition, the returned object is:
The database table backing DailyLog must have not only a primary key constraint defined, but in addition another constraint which will make sure that only one row per day can be created. The decision about if the object already exists or if it needs to be inserted is taken by attempting an insert and seeing if a constraint violation error occurs (see "strategy" later.)
The method loadAndLock locks the object when returning it (with "select for update"), intended for read/write access; the method load loads the object without locking it, intended for read-only access.
As mentioned earlier, to "just in time" insert an object it is no good doing a "select" to see if the object exists and inserting it if it doesn't. Between the time the select is done and the time the insert is done, another session might have done an insert. The only way to proceed is to perform the "insert", and if that succeeds then one can be certain that the row now exists, and if that fails with a constraint violation then one can be certain that the row already existed.
However, although this is the only strategy that can be adopted, it is not easy to implement in Hibernate. Hibernate states (in the Session Javadoc) that if a statement fails, then the Session must be discarded. Therefore the strategy which is adopted is to create a new Session with its own Transaction, perform the insert. Afterwards one can be certain that the row exists in the database, so the Session is destroyed, and the row is loaded in the original Session and returned.
This may have performance penalties, however it is the only way to ensure correct behavior.
See Programming with unique constraints.Constructor and Description |
---|
InsertOrFetcher() |
Modifier and Type | Method and Description |
---|---|
static <T> T |
load(Class<T> cl,
Session mainSession,
T objectForInsertion,
Collection<String> domainKey)
See class documentation.
|
static <T> T |
loadAndLock(Class<T> cl,
Session mainSession,
T objectForInsertion,
Collection<String> domainKey)
See class documentation.
|
public static <T> T load(Class<T> cl, Session mainSession, T objectForInsertion, Collection<String> domainKey)
cl
- The type of Hibernate-managed to be inserted/fetchedmainSession
- Hibernate session with active transactionobjectForInsertion
- Not managed by Hibernate yetdomainKey
- Which attributes of objectForInsertion should be used for the WHERE to re-find the objectpublic static <T> T loadAndLock(Class<T> cl, Session mainSession, T objectForInsertion, Collection<String> domainKey)
cl
- The type of Hibernate-managed to be inserted/fetchedmainSession
- Hibernate session with active transactionobjectForInsertion
- Not managed by Hibernate yetdomainKey
- Which attributes of objectForInsertion should be used for the WHERE to re-find the object