I am working on a project and due to certain issue I am changing pessimistic locking to optimistic locking. While doing so i am getting an error while updating the entity in the code as i tried it as a standalone application so there is no chance that two threads are updating it simultaneously. I checked the value of version in the different part of the code. Its showing it as 0. While calling flush or commit its giving an excepion that it is updated to 1.
Note: I already added #version int version_id field in entity for optimisticlocking.
The code is as below:
WorkItem tmp_workItem=entityManager.find(WorkItem.class , workItem.getEntityKey());
logger.info("Before merge"+ tmp_workItem.getVersion());
entityManager.merge(workItem);
tmp_workItem=entityManager.find(WorkItem.class , workItem.getEntityKey());
logger.info("After merge"+tmp_workItem.getVersion()+" "+workItem.getVersion());
//logger.info(entityManager.getLockMode(WorkItem.class).toString());
entityManager.flush();
logger.info("After flush"+tmp_workItem.getVersion());
response = true;
This code is throwing an exception :
getVersion : 1
] cannot be updated because it has changed or been deleted since it was last read.
Class> com.csg.ib.cobra.domain.WorkItem Primary Key> [9553]
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:549)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1559
The Values of version in logger are :
before merge : 0 0
after merge : 0 0
before flush: 0 0
Then how it can get incresed by 1 while calling entityManger.flush()
)
In general, in contrast with the id field, which is updated on persist operation, version field is updated when data is actually flushed to the database, either using commit or flush. This is the reason why version field value is 0 until entityManager.flush is called. You could alternatively be using transaction.commit if using resource local transaction.
Regarding the exception that you are getting in the application code, it would be useful to have a more complete code example, maybe in the form of a JUnit test. I have created the following simple tests to demonstrate correct behavior, hope it will be helpful:
#Test
public void testOptimisticLocking() {
OptimisticEntity entity = new OptimisticEntity();
entity.setName("Some name");
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
try {
entityManager.persist(entity);
transaction.commit();
} catch (Exception x) {
transaction.rollback();
Assert.fail("Failed to commit: " + x.getMessage());
}
int id = entity.getId();
int version = entity.getVersion();
Assert.assertTrue(id > 0);
Assert.assertTrue(version > 0);
entity = entityManager.find(OptimisticEntity.class, id);
Assert.assertNotNull("Entity could not be retrieved", entity);
Assert.assertEquals("Entity version retrieved not expected", version, entity.getVersion());
entity.setName("Another name");
transaction.begin();
try {
entityManager.merge(entity);
transaction.commit();
} catch (Exception x) {
transaction.rollback();
Assert.fail("Failed to merge: " + x.getMessage());
}
Assert.assertEquals("Entity version not incremented after merge", version+1, entity.getVersion());
}
#Test
public void testOptimisticLockingOneTransaction() {
OptimisticEntity entity = new OptimisticEntity();
entity.setName("Some name");
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
try {
entityManager.persist(entity);
int id = entity.getId();
int version = entity.getVersion();
Assert.assertTrue(id > 0);
Assert.assertEquals(0, version);
OptimisticEntity retrievedEntity = entityManager.find(OptimisticEntity.class, id);
Assert.assertNotNull("Entity could not be retrieved", retrievedEntity);
Assert.assertEquals("Entity version retrieved not expected", 0, retrievedEntity.getVersion());
entity.setName("Another name");
entityManager.merge(entity);
Assert.assertEquals("Entity version changed after merge", 0, entity.getVersion());
retrievedEntity = entityManager.find(OptimisticEntity.class, id);
Assert.assertNotNull("Entity could not be retrieved", retrievedEntity);
Assert.assertEquals("Entity version retrieved not expected", 0, retrievedEntity.getVersion());
entityManager.flush();
Assert.assertEquals("Entity version not incremented after flush", 1, entity.getVersion());
transaction.commit();
} catch (Exception x) {
transaction.rollback();
Assert.fail("An error occurred: " + x.getMessage());
}
}
Related
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
We have to implement a logic to write the unique code generation in Java. The concept is when we generate the code the system will check if the code is already generate or not. If already generate the system create new code and check again. But this logic fails in some case and we cannot able to identify what is the issue is
Here is the code to create the unique code
Integer code = null;
try {
int max = 999999;
int min = 100000;
code = (int) Math.round(Math.random() * (max - min + 1) + min);
PreOrders preObj = null;
preObj = WebServiceDao.getInstance().preOrderObj(code.toString());
if (preObj != null) {
createCode();
}
} catch (Exception e) {
exceptionCaught();
e.printStackTrace();
log.error("Exception in method createCode() - " + e.toString());
}
return code;
}
The function preOrderObj is calling a function to check the code exists in the database if exists return the object. We are using Hibernate to map the database functions and Mysql on the backend.
Here is the function preOrderObj
PreOrders preOrderObj = null;
List<PreOrders> preOrderList = null;
SessionFactory sessionFactory =
(SessionFactory) ServletActionContext.getServletContext().getAttribute(HibernateListener.KEY_NAME);
Session Hibernatesession = sessionFactory.openSession();
try {
Hibernatesession.beginTransaction();
preOrderList = Hibernatesession.createCriteria(PreOrders.class).add(Restrictions.eq("code", code)).list(); // removed .add(Restrictions.eq("status", true))
if (!preOrderList.isEmpty()) {
preOrderObj = (PreOrders) preOrderList.iterator().next();
}
Hibernatesession.getTransaction().commit();
Hibernatesession.flush();
} catch (Exception e) {
Hibernatesession.getTransaction().rollback();
log.debug("This is my debug message.");
log.info("This is my info message.");
log.warn("This is my warn message.");
log.error("This is my error message.");
log.fatal("Fatal error " + e.getStackTrace().toString());
} finally {
Hibernatesession.close();
}
return preOrderObj;
}
Please guide us to identify the issue.
In createCode method, when the random code generated already exist in database, you try to call createCode again. However, the return value from the recursive call is not updated to the code variable, hence the colliding code is still returned and cause error.
To fix the problem, update the method as
...
if (preObj != null) {
//createCode();
code = createCode();
}
...
Such that the code is updated.
By the way, using random number to generate unique value and test uniqueness through query is a bit strange. You may try Auto Increment if you want unique value.
I want to insert a List of 170.000 entities into my local installed MySQL 8.0 database using Hibernate 4.2.
Currently I'm doing this via the Session#save method. But inserting those many entities lasts so long. So is there a possibility to do this faster?
for (Agagf x : list) {
create(x);
}
// ------------------------
public static void create(Object obj) throws DatabaseException {
Session hsession = null;
try {
hsession = SqlDataHibernateUtil.getSessionFactory().openSession();
Transaction htransaction = hsession.beginTransaction();
hsession.save(obj);
htransaction.commit();
} catch (HibernateException ex) {
throw new DatabaseException(ex);
} finally {
if (hsession != null)
hsession.close();
}
}
There is this article on Hibernate page: http://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch15.html
According to them, you would need something like this:
create(list);
// ------------------------
public static void create(List<Object> objList) throws DatabaseException {
Session hsession = null;
try {
hsession = SqlDataHibernateUtil.getSessionFactory().openSession();
Transaction htransaction = hsession.beginTransaction();
int count = 0;
for(Agagf x: objList) {
hsession.save(obj);
if ( ++count % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
hsession.flush();
hsession.clear();
count = 0;
}
}
} catch (HibernateException ex) {
throw new DatabaseException(ex);
} finally {
htransaction.commit();
if (hsession != null) {
hsession.close();
}
}
}
Also, the configuration to enable batch processing:
if you are undertaking batch processing you will need to enable the use of JDBC
batching. This is absolutely essential if you want to achieve optimal
performance. Set the JDBC batch size to a reasonable number (10-50, for example):
hibernate.jdbc.batch_size 20
Edit: In your case, play with the batch size to better fit your volume. Just remember to make the same size in both configuration and if statement for flush.
I need to programmatically create a Neo4j DB and then change the initial password. Creating the DB works, but I don't know how to change the password.
String graphDBFilePath = Config.getNeo4jFilesPath_Absolute() + "/" + schemaName; // This is an absolute path. Do not let Neo4j see it.
GraphDatabaseService graphDb = new GraphDatabaseFactory().newEmbeddedDatabase( new File(graphDBFilePath) ); // See http://neo4j.com/docs/java-reference/current/javadocs/org/neo4j/graphdb/GraphDatabaseService.html
// This transaction works
try (Transaction tx = graphDb.beginTx()) {
Node myNode = graphDb.createNode();
myNode.setProperty( "name", "my node");
tx.success();
}
// This transaction throws an error
Transaction tx = graphDb.beginTx();
try {
graphDb.execute("CALL dbms.changePassword(\'Spoon_XL\')"); // "Invalid attempt to change the password" error
tx.success();
} catch (Exception ex) {
Log.logError("createNewGraphDB() Change Initial Password: " + ex.getLocalizedMessage());
} finally {tx.close(); graphDb.shutdown();}
This may be version dependent (and I don't know what version you are running), but I think the procedure is actually dbms.security.changePassword
Hope this helps,
Tom
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.