hibernate optimistic lock mechanism - java

I am so curious about the hibernate optimistic lock (dedicated version way), I checked hibernate source code which tells that it checks version before current transaction commits, but if there is another transaction happens to committed after it query the version column from DB(in a very short time gap), then current transaction considers there is no change, so the old transaction would be replaced wrongly.
EntityVerifyVersionProcess.java
#Override
public void doBeforeTransactionCompletion(SessionImplementor session) {
final EntityPersister persister = entry.getPersister();
if ( !entry.isExistsInDatabase() ) {
// HHH-9419: We cannot check for a version of an entry we ourselves deleted
return;
}
final Object latestVersion = persister.getCurrentVersion( entry.getId(), session );
if ( !entry.getVersion().equals( latestVersion ) ) {
throw new OptimisticLockException(
object,
"Newer version [" + latestVersion +
"] of entity [" + MessageHelper.infoString( entry.getEntityName(), entry.getId() ) +
"] found in database"
);
}
}
is such case possible?
Hope there are DB domain experts who would help me on this.
Many thanks.

Based on a quick glance of the code, EntityVerifyVersionProcess is used for read transactions, so there's no potential for data loss involved. This would only check that when the transaction commits, it's not returning data that's already stale. With a READ COMMITTED transaction, I suppose this might return data that is instantly going stale, but hard to say without going into details.
Write transactions on the other hand use EntityIncrementVersionProcess, which is a completely different beast and leaves no chance for race conditions.
public void doBeforeTransactionCompletion(SessionImplementor session) {
final EntityPersister persister = entry.getPersister();
final Object nextVersion = persister.forceVersionIncrement( entry.getId(), entry.getVersion(), session );
entry.forceLocked( object, nextVersion );
}

Related

Spring boot application gets stuck when executing a repository method call

I have a service method which is transactional. This method saves an Entity called FascicoloENT on the database after some updates done with mapstruct. Before the entity is saved, FascicoloRepository is called by internal business logig to get the maximum value of a column in the fascicolo Table. After the Entity is saved, onother method is called which, given the Fascicolo id, saves a new entity called RicorsoENT in the db. FacicoloENT and RicorsoENT are in a one to many jpa relation.
It happens that the java application gets stuck when the FacicoloRepository is called to get the maximum value needed by business logic. Although, from logs I can see that the application stops on a different query, when saving FascicoloENT, even though the code in debug hasn't yet reached that point...
Here is my service method:
#Override
#Transactional(rollbackFor = {Exception.class})
public FascicoloDTO updateFascicoloInCompleto(FascicoloDTO fascicolo, Long id) throws LedaException {
boolean isFirstSave = false;
var optionalFascicoloEntity = fascicoloRepository.findById(id);
if (optionalFascicoloEntity.isEmpty()) {
log.error(FascicoloApiConstants.FASCICOLO_CON_ID + " {}" + FascicoloApiConstants.NON_TROVATO, id);
throw new LedaNotFoundException(ErroreEnum.FASCICOLO_NON_TROVATO, FascicoloApiConstants.FASCICOLO_CON_ID + " " + id + FascicoloApiConstants.NON_TROVATO);
}
var fascicoloEntity = optionalFascicoloEntity.get();
LedaUtils.checkUpdatedId(id, fascicoloEntity.getId(), FascicoloApiConstants.ID_RISORSA_AGGIORNATA);
if (StatoEnum.BOZZA.equals(fascicoloEntity.getStato())) {
isFirstSave = true;
}
fascicoloMapper.updateFromDto(fascicolo, fascicoloEntity);
fascicoloEntity.setStato(StatoEnum.COMPLETO);
//Generazione numero fascicolo
var count = fascicoloRepository.getMaxCounter();
if (count == null) {
count = 1L;
fascicoloEntity.setCounterNumeroFascicolo(count);
} else {
count++;
fascicoloEntity.setCounterNumeroFascicolo(count);
}
String numeroFascicolo = "" + fascicoloEntity.getAnnoDiCompetenza() + count + fascicoloEntity.getUnitaOrganizzativa();
fascicoloEntity.setNumeroFascicolo(numeroFascicolo);
var fascicoloSalvato = fascicoloRepository.save(fascicoloEntity);
//Se il fascicolo passa in stato COMPLETO per la prima volta allora creo il ricorso
if (isFirstSave)
ricorsoService.createRicorso(fascicoloSalvato.getId());
log.info(FascicoloApiConstants.FASCICOLO_CON_ID + " {} " + FascicoloApiConstants.AGGIORNATO, id);
return fascicoloMapper.toApiModel(fascicoloSalvato);
}
The method where the code stops is the fascicoloRepository.getMaxCounter()
Here is the createRicorso method of the RicorsoService class:
#Transactional(rollbackFor = {Exception.class})
public RicorsoDTO createRicorso(Long idFascicolo) throws LedaException {
LedaUtils.checkNull(idFascicolo, "id risorsa");
var fascicoloENT = fascicoloRepository.getReferenceById(idFascicolo);
var ricorsoENT = new RicorsoENT();
ricorsoENT.setFascicolo(fascicoloENT);
ricorsoENT.setStato(StatoRicorsoEnum.APERTO);
return ricorsoMapper.toApiModelRicorso(ricorsoRepository.save(ricorsoENT));
}
Operations on FascicoloENT are not cascaded on RicorsoENT. There is no cascade type.
This is the query taken from logs where the code stops:
Hibernate:
update
tb_fascicoli
set
row_updated_dttm=?,
row_created_dttm=?,
row_updated_user=?,
row_created_user=?,
anno_competenza=?,
count_numero_fascicolo=?,
data_apertura=?,
flag_unito=?,
id_motivo_apertura=?,
id_motivo_arichiviazione=?,
note=?,
numero_fascicolo=?,
numero_fascicolo_unito=?,
numero_ricorrenti=?,
fk_ricorrente_principale=?,
id_sede_tar=?,
stato=?,
step=?,
terzo_interessato=?,
tipologia_ricorso=?,
unita_organizzativa=?
where
id=?
I tried deliting #Transactional on CreateRicorso method but it still doesn't work.
I think the problem is a metter of transactions or threads...
Can anyone help me understand why the application gets stuck and how to solve?
PS: let me know if you need extra information
Many thanks,
Saverio

Why is CQLDataLoader returning keyspace already exists when it doesn't?

I checked to see if the keyspace exists before creation, but both checks returns null. However, when I execute the keyspace creation cql file, it always returns that the keyspace already exists. I'm using org.cassandra:cassandra:cassandra-unit-spring:3.1.3.2 package. Not sure how to fix this issue. I have another method, '''EmbeddedCassandraServerHelper.cleanEmbeddedCassandra()''', that seemingly drops the keyspace since it also returns null when checking for library keyspace. So at this point, I'm assuming its a logic error on my part but I can't figure it out.
public static void setupEmbeddedCassandra(String cqlFile, String keyspaceName)
throws IOException, TTransportException, InterruptedException {
synchronized (lock) {
if (finalKeyspace == null) {
System.out.println(CLASS_NAME + " Setting up local embedded cassandra environment");
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
int embeddedCassandraPort = EmbeddedCassandraServerHelper.getNativeTransportPort();
System.setProperty("embeddedCassandraPort", String.valueOf(embeddedCassandraPort));
cluster =
(new Cluster.Builder())
.addContactPoints(new String[] {"localhost"})
.withPort(embeddedCassandraPort)
.build();
session = cluster.connect();
cluster.getConfiguration();
System.out.println(cluster.getMetadata().getKeyspace("library"));
CQLDataLoader dataLoader = new CQLDataLoader(session);
System.out.println(cluster.getMetadata().getKeyspace("library"));
dataLoader.load(new ClassPathCQLDataSet(cqlFile, true, keyspaceName));
System.out.println(
CLASS_NAME
+ " Embedded Server started on port "
+ EmbeddedCassandraServerHelper.getNativeTransportPort());
}
finalKeyspace = keyspaceName;
}
}

Distributed transaction with multiple database servers seems unreliable

After googling for some days i got to understand something about JTA and
then wrote an application that uses JTA, JDBC and MySQL server for performing distributed transaction.
Below is my code.
We are using 3 different database servers and res1, res2 and res3(these are 3 different XAResources) denote them respectively.
try{
res1.start(xid1, XAResource.TMNOFLAGS);
dao.addEmployee(e, con);
res1.end(xid1, XAResource.TMSUCCESS);
res2.start(xid2, XAResource.TMNOFLAGS);
dao.addDepartment(d, tournamentCon);
res2.end(xid2, XAResource.TMSUCCESS);
res3.start(xid3, XAResource.TMNOFLAGS);
dao.addAsset(a, testCon);
res3.end(xid3, XAResource.TMSUCCESS);
int result1 = res1.prepare(xid1);
int result2 = res2.prepare(xid2);
int result3 = res3.prepare(xid3);
if(result1 == XAResource.XA_OK && result2 == XAResource.XA_OK &&
result3 == XAResource.XA_OK) {
res1.commit(xid1, false);
res2.commit(xid2, false);
res3.commit(xid3, false);
}
}
catch(Exception e){
res1.rollback(xid1);
res2.rollback(xid2);
res3.rollback(xid3);
}
Now, the problem with this approach is :-
let's say during res2.commit or res3.commit, there is an exception
then the commits which have already been done i.e res1.commit, coult not be rolled back
but we want all these trxs to be done completely or nothing at all.
Could someone please let me know how to do this?
FYI : we are not using any application server.
Please also let me know what a transaction manager is ? Is it a s/w component which needs to be used explicitly or the code that i have posted is what would be called a transaction manager.
Any help would be highly appreciated.

OneToOne relationship doesn't always fetch

I have a OneToOne relationship between two tables, as shown below:
PreRecordLoad.java:
#OneToOne(mappedBy="preRecordLoadId",cascade = CascadeType.ALL)
private PreRecordLoadAux preRecordLoadAux;
PreRecordLoadAux.java:
#JoinColumn(name = "PRE_RECORD_LOAD_ID", referencedColumnName = "PRE_RECORD_LOAD_ID")
#OneToOne
private PreRecordLoad preRecordLoadId;
I'm using this method to pull back the PreRecordLoad object:
public PreRecordLoad FindPreRecordLoad(Long ID)
{
print("Finding " + ID + "f");
Query query;
PreRecordLoad result = null;
try
{
query = em.createNamedQuery("PreRecordLoad.findByPreRecordLoadId");
query.setParameter("preRecordLoadId", ID);
result = (PreRecordLoad)query.getSingleResult();
//result = em.find(PreRecordLoad.class, ID);
}
catch(Exception e)
{
print(e.getLocalizedMessage());
}
return result;
}
The '+ "f"' is to see if the passed value somehow had something at the end. It didn't.
I originally used em.find, but the same issue occurred no matter which method I used.
I used to use a BigDecimal for the ID because it was the default, and noticed I was getting a precision difference when it worked, and when it didn't work. Specifically the precision was 4 when it didn't work, but 0 when it did. I couldn't work out why this was, so I changed the BigDecimal to a Long, as I never really needed it to be a BigDecimal anyway.
When I save the new PreRecordLoad and PreRecordLoadAux objects to the database (inserting them for the first time), and then try and run this method to recall the objects, it retrieves the PreRecordLoad, but the PreRecordLoadAux is null. This is despite the entry being in the database and what looks to be full committed, as I can access it from SQLDeveloper, which is a separate session.
However, if I stop and re-run the application, then it successfully pulls back both objects. The ID being passed is the same both times, or at least appears to be.
Anyway suggestions would be greatly appreciated, thankyou.
Edit:
Here is the code for when I am persisting the objects into the DB:
if(existingPreAux==null) {
try {
preLoad.setAuditSubLoadId(auditLoad);
em.persist(preLoad);
print("Pre Record Load entry Created");
preAux.setPreRecordLoadId(preLoad);
em.persist(preAux);
print("Pre Record Load Aux entry Created");
}
catch(ConstraintViolationException e) {
for(ConstraintViolation c : e.getConstraintViolations()) {
System.out.println (c.getPropertyPath() + " " + c.getMessage());
}
}
}
else {
try {
preLoad.setPreRecordLoadId(existingPreLoad.getPreRecordLoadId());
preLoad.setAuditSubLoadId(auditLoad);
em.merge(preLoad);
print("Pre Record Load entry found and updated");
preAux.setPreRecordLoadAuxId(existingPreAux.getPreRecordLoadAuxId());
preAux.setPreRecordLoadId(preLoad);
em.merge(preAux);
print("Pre Record Load Aux entry found and updated");
}
catch(ConstraintViolationException e) {
for(ConstraintViolation c : e.getConstraintViolations()) {
System.out.println (c.getPropertyPath() + " " + c.getMessage());
}
}
}
That's in a method, and after that code, the method ends.
It's your responsibility to maintain the coherence of the object graph. So, when you do preAux.setPreRecordLoadId(preLoad);, yo must also do preLoad.setPreRecordLoadAux(preAux);.
If you don't, then every time you'll load the preAux from the same session, it will be retrieved from the first-level cache, and will thus return your incorrectly initialized instance of the entity.

Creating multiple transactions in a single hibernate session

I have created a Quartz Job which runs in background in my JBoss server and is responsible for updating some statistical data at regular interval (coupled with some database flags)
To load and persist the I am using Hibernate 4. Everything works fine except one hick up.
The entire thread i.e. Job is wrapped in a Single transaction which over the period of time (as the amount of data increases) becomes huge and worry some. I am trying to break this single large transaction into multiple small ones, such that each transaction process only a sub group of data.
Problem: I tried very lamely to wrap a code into a loop and start/end transaction at start/end of the loop. As I expected it didn't work. I have been looking around various forums to figure out a solution but have not come across anything that indicates managing multiple transaction in a single session (where in only 1 transaction will be active at a time).
I am relatively new to hibernate and would appreciate any help that points me to a direction on achieving this.
Update: Adding code demonstrate what I am trying to achieve and mean when I say breaking into multiple transaction. And stack trace when this is executed.
log.info("Starting Calculation Job.");
List<GroupModel> groups = Collections.emptyList();
DAOFactory hibDaoFactory = null;
try {
hibDaoFactory = DAOFactory.hibernate();
hibDaoFactory.beginTransaction();
OrganizationDao groupDao = hibDaoFactory.getGroupDao();
groups = groupDao.findAll();
hibDaoFactory.commitTransaction();
} catch (Exception ex) {
hibDaoFactory.rollbackTransaction();
log.error("Error in transaction", ex);
}
try {
hibDaoFactory = DAOFactory.hibernate();
StatsDao statsDao = hibDaoFactory.getStatsDao();
StatsScaledValuesDao statsScaledDao = hibDaoFactory.getStatsScaledValuesDao();
for (GroupModel grp : groups) {
try {
hibDaoFactory.beginTransaction();
log.info("Performing computation for Group " + grp.getName() + " ["
+ grp.getId() + "]");
List<Stats> statsDetail = statsDao.loadStatsGroup(grp.getId());
// Coputing Steps here
for (Entry origEntry : statsEntries) {
entry.setCalculatedItem1(origEntry.getCalculatedItem1());
entry.setCalculatedItem2(origEntry.getCalculatedItem2());
entry.setCalculatedItem3(origEntry.getCalculatedItem3());
StatsDetailsScaledValues scValues = entry.getScaledValues();
if (scValues == null) {
scValues = new StatsDetailsScaledValues();
scValues.setId(origEntry.getScrEntryId());
scValues.setValues(origEntry.getScaledValues());
} else {
scValues.setValues(origEntry.getScaledValues());
}
statsScaledDao.makePersistent(scValues);
}
hibDaoFactory.commitTransaction();
} catch (Exception ex) {
hibDaoFactory.rollbackTransaction();
log.error("Error in transaction", ex);
} finally {
}
}
} catch (Exception ex) {
log.error("Error", ex);
} finally {
}
log.info("Job Complete.");
Following is the exception stacktrace I am getting upon execution of this Job
org.hibernate.SessionException: Session is closed!
at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:127)
at org.hibernate.internal.SessionImpl.createCriteria(SessionImpl.java:1555)
at sun.reflect.GeneratedMethodAccessor469.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:352)
at $Proxy308.createCriteria(Unknown Source)
at com.blueoptima.cs.dao.impl.hibernate.GenericHibernateDao.findByCriteria(GenericHibernateDao.java:132)
at com.blueoptima.cs.dao.impl.hibernate.ScrStatsManagementHibernateDao.loadStatsEntriesForOrg(ScrStatsManagementHibernateDao.java:22)
... 3 more
To my understanding from what I have read so far about Hibernate, sessions and transactions. It seems that when a session is created it is attached to the thread and lives through out the threads life or when commit or rollback is called. Thus, when the first transaction is committed the session is being closed and is unavailable for the rest of the threads life.
My question remains: How can we have multiple transactions in a single session?
More detail would be great and some examples but I think I should be able to help with what you have written here.
Have one static SessionFactory (this is big on memory)
Also with your transactions you want something like this.
SomeClass object = new SomeClass();
Session session = sessionFactory().openSession() // create the session object
session.beginTransaction(); //begins the transaction
session.save(object); // saves the object But REMEMBER it isn't saved till session.commit()
session.getTransaction().commit(); // actually persisting the object
session.close(); //closes the transaction
This is how I used my transaction, I am not sure if I do as many transaction as you have at a time. But the session object is light weight compared to the SessionFactory in memory.
If you want to save more objects at a time you could do it in one transaction for example.
SomeClass object1 = new SomeClass();
SomeClass object2 = new SomeClass();
SomeClass object2 = new SomeClass();
session.beginTransaction();
session.save(object1);
session.save(object2);
session.save(object3);
session.getTransaction().commit(); // when commit is called it will save all 3 objects
session.close();
Hope this help in some way or points you in the right direction.
I think you could configure you program to condense transactions as well. :)
Edit
Here is a great youtube tutorial. This guy really broke it down for me.
Hibernate Tutorials

Categories