Synchronized block throwing DuplicateKeyException - java

Below is my class which is under threaded spring executors. Based on the type of source/service TAXP, TAXS, TAXT methods are getting called.
Logic is if the 'taxInfo.getGroupingId()' is already present in primary tax table do not insert, else insert primary table.
All secondary and tertiary tables records are inserted. TAXP, TAXS, TAXT are the topics and they receive data anytime. there might be milllisecond gap or at the same time the data would be sent so the blocks are synchroized.
All the 3 methods are called from 3 different thread executors.
executor1.insertPrimaryTaxInfo(taxInfo);
executor2.insertSecTaxInfo(taxInfo);
executor3.insertTerTaxInfo(taxInfo);
#Service
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class TaxServiceImpl implements TaxService {
private static final Logger LOG = LogManager.getLogger(ScanServiceImpl.class);
// TAXP
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertPrimaryTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
// TAXS
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertSecTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
taxMapper.insertIntoSecTable(taxInfo); // secondary tax table
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
// TAXT
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertTerTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
taxMapper.insertIntoSecTable(taxInfo); // secondary tax table
taxMapper.insertIntoTerTable(taxInfo); // Tertiary tax table
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
}
The issue is when TAXP, TAXS, TAXT are getting data at the same time, and the 3 above methods are called simultaneously. At a millisecond difference one of the thread inserts into primary table and the other thread trying to do the same but finds a record already exisitng in the table and throws duplicate key excepiton.
Im getting the below exception:
"com.data.exception.TaxServiceException: org.springframework.dao.DuplicateKeyException:
### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (TAXDB2.TAX_PK) violated
The reason for synchrnozing the block is to overcome this exception. What is wrong with the above code?

It looks like you may be attempting to generate non guid based primary keys in your application instead of letting the database generate them and running into conflicts.
Its a losing battle to attempt to synchronize database access in your application. You should be letting the database manage the concurrency through its existing mechanisms. For more information refer to ACID. Additionaly, it is very instructive to lookup the current Isolation level on your database implementation and what it does in comparison to the others. For example SQL Server docs Understanding Isolation Levels

Related

Hibernate JPA update multi threads single entity

I have message queue, that gives messages with some entity field update info. There are 10 threads, that process messages from the queue.
For example
1st thread processes message, this thread should update field A from my entity with id 123.
2nd thread processes another message, this thread should update field B from my entity with id 123 at the same time.
Sometimes after updates database don't contain some updated fields.
some updater:
someService.updateEntityFieldA(entityId, newFieldValue);
some service:
public Optional<Entity> findById(String entityId) {
return Optional.ofNullable(new DBWorker().findOne(Entity.class, entityId));
}
public void updateEntityFieldA(String entityId, String newFieldValue) {
findById(entityId).ifPresent(entity -> {
entity.setFieldA(newFieldValue);
new DBWorker().update(entity);
});
}
db worker:
public <T> T findOne(final Class<T> type, Serializable entityId) {
T findObj;
try (Session session = HibernateUtil.openSessionPostgres()) {
findObj = session.get(type, entityId);
} catch (Exception e) {
throw new HibernateException("database error. " + e.getMessage(), e);
}
return findObj;
}
public void update(Object entity) {
try (Session session = HibernateUtil.openSessionPostgres()) {
session.beginTransaction();
session.update(entity);
session.getTransaction().commit();
} catch (Exception e) {
throw new HibernateException("database error. " + e.getMessage(), e);
}
}
HibernateUtil.openSessionPostgres() gets each time new session from
sessionFactory.openSession()
Is it possible do such logic without threads locks / optimistic locking and pessimistic locking?
If you use sessionFactory.openSession() to always open a new session, on the update hibernate may be lossing the info about the dirty fields it needs to update, so issues and update to all fields.
Setting hibernate.show_sql property to true will show you the SQL UPDATE statements generated by hibernate.
Try refactoring your code to, in the same transaction, load the entity and update the field. A session.update is not needed, as the entity is managed, on transaction commit hibernate will flush changes and issue a SQL update.

#Transactional method insert value on exception and multithread wildfly CDI

I have a method in CDI bean which is transactional, on error it creates an entry in database with the exception message. This method can be called by RESTendpoint and in multithread way.
We have a SQL constraint to avoid duplicity in database
#Transactional
public RegistrationRuleStatus performCheck(RegistrationRule rule, User user) {
try {
//check if rule is dependent of other rules and if all proved, perform check
List<RegistrationRule> rules = rule.getRuleParentDependencies();
boolean parentDependenciesAreProved = true;
if (!CollectionUtils.isEmpty(rules)) {
parentDependenciesAreProved = ruleDao.areParentDependenciesProved(rule,user.getId());
}
if (parentDependenciesAreProved) {
Object service = CDI.current().select(Object.class, new NamedAnnotation(rule.getProvider().name())).get();
Method method = service.getClass().getMethod(rule.getProviderType().getMethod(), Long.class, RegistrationRule.class);
return (RegistrationRuleStatus) method.invoke(service, user.getId(), rule);
} else {
RegistrationRuleStatus status = statusDao.getStatusByUserAndRule(user, rule);
if (status == null) {
status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.START, new Date());
statusDao.create(status);
}
return status;
}
} catch (Exception e) {
LOGGER.error("could not perform check {} for provider {}", rule.getProviderType().name(), rule.getProvider().name(), e.getCause()!=null?e.getCause():e);
return statusDao.createErrorStatus(user,rule,e.getCause()!=null?e.getCause().getMessage():e.getMessage());
}
}
create Error method:
#Transactional
public RegistrationRuleStatus createErrorStatus(User user, RegistrationRule rule, String message) {
RegistrationRuleStatus status = getStatusByUserAndRule(user, rule);
if (status == null) {
status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.ERROR, new Date());
status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
status.setErrorMessage(message);
create(status);
}else {
status.setStatus(RegistrationActionStatus.ERROR);
status.setStatusDate(new Date());
status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
status.setErrorMessage(message);
update(status);
}
return status;
}
the problem is method is called twice at same time and the error recorded is DuplicateException but we don't want it. We verify at the beginning if object already exists, but I think it is called at exactly same time.
JAVA8/wildfly/CDI/JPA/eclipselink
Any idea?
I'd suggest you to consider following approaches:
1) Implement retry logic. Catch exception, analyze it. If it indicates an unexpected duplicate (like you described), then don't consider it as an error and just repeat the method call. Now your code will work differently: It will notice that a record already exists and will not create a duplicate.
2) Use isolation level SERIALIZABLE. Then within a single transaction your will "see" a consistent behaviour: If select operation hasn't found a particular record, then till the end of this transaction no other transaction will insert such record and there will be no exception related to duplicates. But the price is that the whole table will be locked for each such transaction. This can degrade the application performance essentially.

Using SQL Server's BULK INSERT with EntityManager's createNativeQuery results in TransactionRequiredException

So i've been trying to figure this one out.
SQL Server has a way to Bulk Copy using sql which they outline here:
https://learn.microsoft.com/en-us/sql/connect/jdbc/using-bulk-copy-with-the-jdbc-driver?view=sql-server-2017#single-bulk-copy-operations
They even use executeUpdate().
I've been trying to do this with my EntityManager, however I always get the following:
javax.persistence.TransactionRequiredException: Executing an update/delete query
Here's my code:
#Service
#Transactional
public class UpdateService {
private EntityManager emPreStaging;
#PersistenceContext(unitName = "prestagingEntityManagerFactory")
public void setEmPreStaging(EntityManager emPreStaging) {
this.emPreStaging = emPreStaging;
}
#Async
public void insertData(String filePath) {
_log.info("[Update] Inserting data...");
try {
emPreStaging.createNativeQuery("BULK INSERT [test_table] FROM '" + filePath + "' WITH ( FIRSTROW = 2,FORMATFILE = 'C:\\csv\\location\\test.csv'").executeUpdate();
} catch (Exception e) {
_log.error("[Update] - Insert Data FAILED - " +e.getMessage());
} finally {
emPreStaging.close();
}
}
}
I'm currently using org.springframework.transaction.annotation.Transactional but I have also tried jpa's. I've tried wrapping the function instead of at class level to no avail. What am I doing wrong?

PessimisticLockException when saving data in loop

I am getting pesimistlockexception when trying to persist multiple object of same time through JPA.
Here is my code for reference
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Boolean changeDplListMappingByCustomWatchList(List<Integer> dplIds, Integer customWatchListId,
ServiceRequestor customServiceRequestor) {
for(Integer dplId : dplIds) {
if(dplId != null) {
CustomWatchListDplMapping customWatchListDplMapping = new CustomWatchListDplMapping();
customWatchListDplMapping.setDplId(dplId);
customWatchListDplMapping.setWatchListId(customWatchListId);
this.create(customWatchListDplMapping);
}
}
}
catch(Exception e) {
LOG.error("Exception occured while changing dpl mapping by custom watchList id", e);
}
return true;
}
public void create(Model entity) {
manager.persist(entity);
manager.joinTransaction();
}
After first entity when it iterate through second one it throws an exception. If it has only one entity to save then it works well, but for more than one entity model it throws this exception.
by default pessimistic lock is for 1 second so please do the changes in the properties file it will help you to unlock and you will be able to save into database

Spring - Two hibernate update queries are executed instead of one

I am working Spring and Hibernate. I have a requirement where I need to update a particular field by adding a number to it. Since multiple threads could execute it at the same time, while updating I check the field value with the old value. So if nothing was updated that means it was incremented by some other thread and we trigger a retry.
CompanyService
public Company getAndIncrementRequestId(final int companyId, int retry) throws Exception {
Optional<Company> companyOptional = companyRepository.findById(companyId);
if (!companyOptional.isPresent()) {
throw new EntityNotFoundException("Company not found for given id" + companyId);
}
Company company = companyOptional.get();
int oldRequestId = company.getRequestId();
int requestId;
if (oldRequestId == Integer.MAX_VALUE) {
requestId = 1;
} else {
requestId = oldRequestId + 1;
}
company.setRequestId(requestId); //--------------------------> PROBLEM
int result = companyRepository.updateRequestId(companyId, requestId, oldRequestId);
if (result == 0) {
if (retry < 0) {
throw new Exception("Unable to get requestId");
}
LOG.warn("Retrying since there was some update on requestId by some other thread");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
LOG.warn("Unexpected InterruptException occurred while trying to get requestId");
}
return getAndIncrementRequestId(companyId, retry - 1);
}
return company;
}
CompanyRepository
#Transactional
public interface CompanyRepository extends CrudRepository<Company, Integer> {
Optional<Company> findById(String id);
#Modifying(clearAutomatically = true)
#Query("update Company c set c.requestId = :requestId WHERE c.id = :companyId AND c.requestId = :oldRequestId")
int updateRequestId(#Param("companyId") Integer companyId, #Param("requestId") Integer requestId,#Param("oldRequestId") Integer oldRequestId);
}
But this above code in Service will trigger two hibernate updates one which set the requestId with lastest requestId and the other the actual update. Could observe two queries in the log after setting show-sql as true.
But if the line ,
company.setRequestId(requestId);
Is moved down after the companyRepository.updateRequestId() it works fine.
Working CompanyService
public Company getAndIncrementRequestId(final int companyId, int retry) throws Exception {
Optional<Company> companyOptional = companyRepository.findById(companyId);
if (!companyOptional.isPresent()) {
throw new EntityNotFoundException("Company not found for given id" + companyId);
}
Company company = companyOptional.get();
int oldRequestId = company.getRequestId();
int requestId;
if (oldRequestId == Integer.MAX_VALUE) {
requestId = 1;
} else {
requestId = oldRequestId + 1;
}
int result = companyRepository.updateRequestId(companyId, requestId, oldRequestId);
if (result == 0) {
if (retry < 0) {
throw new Exception("Unable to get requestId");
}
LOG.warn("Retrying since there was some update on requestId by some other thread");
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
LOG.warn("Unexpected InterruptException occurred while trying to get requestId");
}
return getAndIncrementRequestId(companyId, retry - 1);
}
company.setRequestId(requestId); //--------------------------> PROBLEM DOES NOT EXISTS
return company;
}
Sp my question why are there two queries when I have not even passed the entity Company anywhere..?
It is because when you do "companyRepository.findById(companyId);" the returned company is returned in managed state.
So , when in case 1 you set the request id before invoking "companyRepository.updateRequestId(companyId, requestId, oldRequestId);", a transaction object is created in the company repository which executes all the pending updates of the managed entity plus the query of the method "updateRequestId" also gets fired.
While in second case, since you have written set statement after invoking "companyRepository.updateRequestId(companyId, requestId, oldRequestId);", that is why the update on managed object never gets fired because it does not get any transaction

Categories

Resources