#Transactional on #Async methods in Spring Boot with shared object - java

I have a requirement where I have to save a lot of data into an Oracle DB.
I want to use multithreading to speed things up. Everything is in a transaction.
I have two tables in a many to one relation.
First, I synchronously save the one-side entity with repo.save()
Next I build batches of 25 many-side entity entries (from an initial list of a few hundred) and start a new thread using #Async to save them.
Each of this entities has to reference the one-side entity so I pass the new one-side entity created previously to the #Async methods.
I wait for all of them to complete before exiting.
Problem is I get the following exception :
java.sql.BatchUpdateException: ORA-02291: integrity constraint (USER.ENTITY_FK1) violated - parent key not found
Which basically means that the one-side entity I am trying to reference from the many-side does not exist.
From here the transaction fails and everything gets rolled back.
Can someone help me ?
Thanks
Update with code :
In OrchestratorService.class :
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void importDataFromFile(MultipartFile file) throws Exception {
MyDto myDto = convertor.convertFileToDto(file);
log.info("myDto : {}", myDto.toString());
ParentEntity savedParent = parentEntityService.saveParent(myDto.getFileName());
childService.saveAllChildren(savedParent, myDto);
}
ParentEntityService.class :
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public ParentEntity saveParent(String fileName){
ParentEntity parentEntity= ParentEntity.builder()
.fileName(fileName)
.loadingDate(LocalDate.now())
.build();
ParentEntity savedParentEntity = parentEntityRepository.save(parentEntity);
log.info("Saved ParentEntity : {} ", savedParentEntity );
return savedPpassImportCtrlEntity;
}
ChildService.class :
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void saveAllChildren(ParentEntity parentEntity, MyDto myDto) throws Exception{
List<CompletableFuture<ProcessStatus>> completableFutures = new ArrayList<>();
List<List<ChildrenData>> lists = ListUtils.partition(myDto.getChildrenDataList(), 25);
for (List<ChildrenData> list :lists) {
CompletableFuture<ProcessStatus> result = asyncSaveBatchService.saveBatch(parentEntity, list);
completableFutures.add(result);
}
// wait for all to finish
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]));
combinedFuture.get();
for(CompletableFuture<ProcessStatus> result : completableFutures){
if(result.get().getStatus().equals(Boolean.FALSE)){
throw new RuntimeException("Process failed : \n " + result.get().getErrorMessage());
}
}
}
And finally AsyncSaveBatchService.class :
#Async("threadPoolTaskExecutor")
public CompletableFuture<ProcessStatus> saveBatch(ParentEntity parentEntity, List<ChildrenData> list){
try{
List<ChildrenEntity> childrenEntitiesToSave = new ArrayList<>();
for (ChildrenData childrenData: list) {
ChildrenEntity childrenEntity = ChildrenEntity .builder()
.firstName(childrenData.getFirstName())
.lastName(childrenData.getLastName())
.parent(parentEntity)
.build();
childrenEntitiesToSave.add(childrenEntity);
}
childrenEntityRepository.saveAll(childrenEntitiesToSave);
return CompletableFuture.completedFuture(ProcessStatus.builder().status(Boolean.TRUE).errorMessage(null).build());
}catch (Exception ex){
return CompletableFuture.completedFuture(ProcessStatus.builder().status(Boolean.FALSE).errorMessage(ExceptionUtils.getStackTrace(ex)).build());
}

Related

How to disable transaction rollback a method in transaction rollback enable class in spring boot

I have two methods named saveNewCustormerDetails and storeFailCustomerSaveDetails. If any exception jump inside saveNewCustormerDetails method then transaction will be the rollback, when an exception occurs, I need to store the message of exception in the database with customer mobile number. To do it, I use the storeFailCustomerSaveDetails method. but It does not work because also rollback store fails customers saving details. So, I need to disable the rollback process only for the storeFailCustomerSaveDetails method. does there any way to do it?
#Service
#Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class CustomerServiceImpl implements CustormerService {
private final CustomerRepository customerRepository;
private final CustomerSaveFailsRepository customerSaveFailsRepository;
#Autowired
public CustomerServiceImpl(CustomerRepository customerRepository, CustomerSaveFailsRepository customerSaveFailsRepository){
this.customerRepository = customerRepository;
this.customerSaveFailsRepository = customerSaveFailsRepository;
}
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
#Override
public void saveNewCustormerDetails(CustomerDTO customerDTO) {
try{
CustomerEntity customerEntity = new CustomerEntity();
BeanUtils.copyProperties(customerDTO, customerEntity);
customerRepository.save(customerEntity);
}catch(Exception e){
LOGGER.error("Method saveNewCustormerDetails : " + e.getMessage(), e);
storeFailCustomerSaveDetails(customerDTO, e);
throw e;
}
}
private void storeFailCustomerSaveDetails(CustomerDTO customerDTO, Exception ex){
try{
CustomerSaveFailsEntity customerSaveFailsEntity = new CustomerSaveFailsEntity(customerDTO.getContact(), e.getMessage());
customerSaveFailsRepository.save(customerSaveFailsEntity);
}catch(Exception e){
LOGGER.error("Method storeFailCustomerSaveDetails : " + e.getMessage() ,e);
}
}
}

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

Optimistic Locking not working Spring Data JPA when updates run at the exact same time

I can't get optimistic locking to work on a Spring Boot 2 project with Spring Data JPA. I have a test that runs 2 simple updates in different threads but they are both successful (no optimistic lock exception) and one of the updates is overwritten by the other.
(Please look at edit at the bottom)
This is my entity:
#Entity
#Table(name = "User")
public class User {
#Column(name = "UserID")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "FirstName")
#NotBlank()
private String fistName;
#Column(name = "LastName")
#NotBlank
private String lastName;
#Column(name = "Email")
#NotBlank
#Email
private String email;
#Version
#Column(name = "Version")
private long version;
// getters & setters
}
This is my repository:
public interface UserRepository extends JpaRepository<User, Integer> {
}
This is my service:
#Service
public class UserService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User updateUser(User user)
throws UserNotFoundException {
final Optional<User> oldUserOpt = userRepository.findById(user.getId());
User oldUser = oldUserOpt
.orElseThrow(UserNotFoundException::new);
logger.debug("udpateUser(): saving user. {}", user.toString());
oldUser.setFistName(user.getFistName());
oldUser.setLastName(user.getLastName());
oldUser.setEmail(user.getEmail());
return userRepository.save(oldUser);
}
}
and finally this is my test:
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class UserControllerIntegrationTest {
#Test
public void testConcurrentUpdate() throws Exception {
String body1 = "{\"fistName\":\"John\",\"lastName\":\"Doe\",\"email\":\"johno#gmail.com\"}";
String body2 = "{\"fistName\":\"John\",\"lastName\":\"Watkins\",\"email\":\"johno#gmail.com\"}";
Runnable runnable1 = () -> {
try {
mvc.perform(put("/v1/users/1")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.content(body1));
} catch (Exception e) {
System.out.println("exception in put " + e);
}
};
Runnable runnable2 = () -> {
try {
mvc.perform(put("/v1/users/1")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("UTF-8")
.content(body2));
} catch (Exception e) {
System.out.println("exception in put " + e);
}
};
Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("user after updates: " + userRepository.findById(1).get().toString());
}
}
when the test runs there is only this record in DB (using h2 in-memory):
insert into User(UserID, FirstName, LastName, Email, Version)
values (1, 'John', 'Oliver', 'johno#gmail.com', 1);
These are the logs. I noticed that version is being checked and set in the sql, so that is working fine. update statement is executed when transaction ends, but both transactions are executed successfully, no exception.
By the way, I tried overriding the save method in the repository to add #Lock(LockModeType.OPTIMISTIC) but nothing changed.
[ Thread-4] c.u.i.service.UserService : updateUser(): saving user. User{id=1, fistName='John', lastName='Doe', email='johno#gmail.com', version=1}
[ Thread-5] c.u.i.service.UserService : updateUser(): saving user. User{id=1, fistName='John', lastName='Watkins', email='johno#gmail.com', version=1}
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [com.company.app.service.UserService.updateUser]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [com.company.app.service.UserService.updateUser]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-4] org.hibernate.SQL : select user0_.UserID as Use1_3_0_, user0_.Email as Email2_3_0_, user0_.FirstName as FirstNam4_3_0_, user0_.LastName as LastName5_3_0_, user0_.Version as Version9_3_0_ from User user0_ where user0_.UserID=1
[ Thread-5] org.hibernate.SQL : select user0_.UserID as Use1_3_0_, user0_.Email as Email2_3_0_, user0_.FirstName as FirstNam4_3_0_, user0_.LastName as LastName5_3_0_, user0_.Version as Version9_3_0_ from User user0_ where user0_.UserID=1
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]
[ Thread-5] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]
[ Thread-4] o.s.t.i.TransactionInterceptor : Completing transaction for [com.company.app.service.UserService.updateUser]
[ Thread-5] o.s.t.i.TransactionInterceptor : Completing transaction for [com.company.app.service.UserService.updateUser]
[ Thread-5] org.hibernate.SQL : update User set Email=johno#gmail.com, FirstName=John, LastName=Watkins, Version=2 where UserID=1 and Version=1
[ Thread-4] org.hibernate.SQL : update User set Email=johno#gmail.com, FirstName=John, LastName=Doe, Version=2 where UserID=1 and Version=1
user after updates: User{id=1, fistName='John', lastName='Watkins', email='johno#gmail.com', version=2}
EDIT:
I think the issue is because the inserts are done at the exact same time.
I added some this code in the service just before calling save():
double random = Math.random();
long wait = (long) (random * 500);
logger.debug("waiting {} ms", wait);
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
e.printStackTrace();
}
With this code, I always get the Optimistic Lock exception, because the inserts are not executed at the same time. Without this ugly workaround I never get the exception. Is there a way to solve this? (other than this workaround). Or I shouldn't worry about this scenario happening in production?
Optimistic Locking ensures that no other changes have been done to an entity between loading and saving it. Since your service loads the entity right before saving it, it is very unlikely for another thread to interfere in this short time frame, which is why you only see a conflict if you make your thread sleep.
If you want to extend the protection offered by optimistic locking beyond a database transaction, you can pass a previously loaded entity to the client and back, and save it without loading it again:
public User updateUser(User user) {
return userRepository.save(user);
}
(this calls entityManager.merge(), which automatically checks the version)
or, if you need more fine grained control over which fields are updated, you can just pass those fields and the version, and check the version yourself when saving:
public User updateUser(UserDto user) {
User savedUser = userRepository.findById(user.getId());
if (savedUser.getVersion() != user.getVersion()) {
throw new OptimisticLockingViolationException();
}
savedUser.setName(user.getName());
}
You could use ExecutorService to manage multithread and CyclicBarrier in order to synchronize threads execution(or at least shorten execution time gap between threads).
I did an example making a call to your UserService class:
Repository
public interface UserRepository extends CrudRepository<User, Long> {
#Lock(value = LockModeType.OPTIMISTIC)
Optional<User> findById(Long id);
}
JUnit Test Case
// Create a Callable that updates the user
public Callable<Boolean> createCallable(User user, int tNumber, CyclicBarrier gate) throws OptimisticLockingFailureException {
return () -> {
// Create POJO to update, we add a number to string fields
User newUser = new User(user.getId(),
user.getFistName() + "[" + tNumber + "]",
user.getLastName() + "[" + tNumber + "]",
user.getEmail());
// Hold on until all threads have created POJO
gate.await();
// Once CyclicBarrier is open, we run update
User updatedUser = userService.updateUser(newUser);
return true;
};
}
#Test(expected = ObjectOptimisticLockingFailureException.class)
public void userServiceShouldThrowOptimisticLockException() throws Throwable {
final int threads = 2; // We need 2 threads
final CyclicBarrier gate = new CyclicBarrier(threads); // Barrier will open once 2 threads are awaiting
ExecutorService executor = Executors.newFixedThreadPool(threads);
// Create user for testing
User user = new User("Alfonso", "Cuaron", "alfonso#ac.com");
User savedUser = userRepository.save(user);
// Create N threads that calls to service
List<Callable<Boolean>> tasks = new ArrayList<>();
for(int i = 0; i < threads; i++) {
tasks.add(createCallable(savedUser, i, gate));
}
// Invoke N threads
List<Future<Boolean>> result = executor.invokeAll(tasks);
// Iterate over the execution results to browse exceptions
for (Future<Boolean> r : result) {
try {
Boolean temp = r.get();
System.out.println("returned " + temp);
} catch (ExecutionException e) {
// Re-throw the exception that ExecutorService catch
throw e.getCause();
}
}
}
We use Callable, because it can throw Exceptions that we can recover from ExecutorService.
Notice, the more instructions are between your thread call and save statement, the more chances are that they will not be in sync to cause a OptimisticLockException. Since you will be calling controller I would suggest to increase the amount of threads for better chances.

Spring Catch JpaSystemException in #Transactional Method and Roll Back Transaction

I have a method that is annotated with
#Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
and calls several repository methods. Now when one repository tries to alter a DB-row that is locked and not rolled back by another instance of the method, spring correctly throws
org.springframework.orm.jpa.JpaSystemException: could not execute statement [...] Caused by: java.sql.SQLException: transaction could not be serialized and rolls back the failed transaction.
Now I want to keep all this behaviour but additionally handle the exception and start a retry. Here's a code snippet:
#Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public void messageReceived(Card card) {
this.request = request;
this.numberOfAttempts = reset ? 0 : this.numberOfAttempts++;
this.reset = true;
LOGGER.info("Message received from Queue: " + card);
TransactionDebugUtils.transactionRequired("MessageReceivedController.messageReceived");
try {
[...]
repository1.createKonto(card);
repository2.doStuff(card);
} catch (JpaSystemException e) {
//This is obviously never invoked
LOGGER.error("TRANSACTION FAILED!!!");
} catch (Exception e) {
LOGGER.error("Error mapping json request to data model", message, e);
}
}
#ExceptionHandler(JpaSystemException.class)
//This is also never invoked
public void handleJpaSystemException(JpaSystemException ex) {
this.messageReceived(this.request);
this.reset = false;
}
I had this issue recently. As it is a method level #Transactional annotation, Transaction commit occurs after finishing method execution.
When you are using this annotation 2 concepts should be considered
persistence context
database transaction
After messageReceived() method is executed, those 2 things will happen and JPA exception is thrown at #Transactional level which means you need to handle this exception from where you are calling this method(controller; if you are calling from a controller).
More regarding #Transactional can be found in this link.

Projection Queries: How to load raw String using JPA for new Entity in AppEngine?

How do I load a list of Strings for a brand new, empty, non-existent table in AppEngine? I tried to follow this example:
http://www.objectdb.com/java/jpa/query/jpql/select#Projection_of_Path_Expressions_
but it gives me an error:
Caused by: javax.persistence.PersistenceException: Class AdminUser for query has not been resolved. Check the query and any imports/aliases specification
Caused by: org.datanucleus.exceptions.ClassNotResolvedException: Class AdminUser for query has not been resolved. Check the query and any imports/aliases specification
Here is the code:
public java.util.List<String> getAdmin() {
EntityManager em = EMF.get().createEntityManager();
try {
TypedQuery<String> tq = em.createQuery("select au.email from AdminUser as au", String.class);
return tq.getResultList(); ///// <=== EXCEPTION
I don't actually want to use an AdminUser class. I only want the single column of Strings. It is not obvious how to create a new empty table on AppEngine.
GAE Datastore is a schemaless NoSQL database. There are no tables. Only entities that must have a kind, an id and can have an arbitrary set of properties.
You can use Datastore via JPA API, to give you nice typed Java classes instead of low-level untyped entities.
AppEngine does not support JPA raw primitive queries as in the example link. I had to use the DatastoreService API directly. Here is what I tried. Needs some cleaning as many of the things did not work, but this did.
public java.util.List<String> getAdmin() {
log.info("getAdmin()");
// AppEngine does not support strong consistency... will frequently return stale results. Setting read policy won't work.
// Construct a read policy for strong consistency
ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.STRONG);
// Set the read policy
DatastoreServiceConfig consistentConfig = DatastoreServiceConfig.Builder.withReadPolicy(policy);
// Get Datastore service with the given configuration
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService(consistentConfig);
com.google.appengine.api.datastore.Query query = new com.google.appengine.api.datastore.Query("AdminUser");
// DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
java.util.List<Entity> events = datastoreService.prepare(query).asList(FetchOptions.Builder.withDefaults());
ArrayList<String> al = new ArrayList<String>();
for(Entity entity: events) {
String s = entity.getProperty("email").toString();
al.add(s);
}
if(true) return al;
#SuppressWarnings("unused")
EntityManager em = EMF.get().createEntityManager();
try {
TypedQuery<String> tq = em.createQuery("select au.email from AdminUser as au", String.class);
return tq.getResultList();
} catch (ClassNotResolvedException | javax.persistence.PersistenceException cnre) {
// catch the exception because AppEngine DataStore has no way to create a new empty Entity table.
log.warning("AdminUser entity does not exist or is empty.");
return new java.util.ArrayList<String>();
} finally {
em.close();
}
// log.info("getAdmin(): al.size(): " + al.size());
}
public void setAdmin(java.util.List<String> admin) {
log.info("setAdmin(), admin.size():"+admin.size());
// delete all AdminUsers
com.google.appengine.api.datastore.Query query = new com.google.appengine.api.datastore.Query("AdminUser");
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
java.util.List<Entity> events = datastoreService.prepare(query).asList(FetchOptions.Builder.withDefaults());
for(Entity entity: events) {
datastoreService.delete(entity.getKey());
}
// EntityManager em = EMF.get().createEntityManager();
// Query q = em.createQuery("delete from AdminUser");
// q.executeUpdate();
// DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
for (String s: admin) {
log.info("creating AdminUser Entity...");
Entity e = new Entity("AdminUser");
e.setProperty("email", s);
datastoreService.put(e);
}
// Must wait for Datastore to actually write records. Ignores consistency even with strong consistency set.
// this doesn't even work sometimes...
//try { Thread.sleep(5000); } catch (InterruptedException e) {}
// EntityManager em = EMF.get().createEntityManager();
// for (String s: admin) {
// Entity e = new Entity("AdminUser");
// e.setProperty("email", s);
// em.persist(e);
// }
// em.close();
} // setAdmin()

Categories

Resources