Spring Data Mongo, why exception is not instance of DuplicateKeyException? - spring-boot

I am working on Spring Boot Rest MongoDB example. In this example, I have Student collection, where emailId is unique field (I applied indexing where unique=true). When someone creates new Student and uses emailId already present in Mongo, I should get DuplicateKeyException. But somehow ex object is not part of the DuplicateKeyException. Why ex not instance of DuplicateKeyException?
try {
studentRepository.save(student);
} catch (Exception ex) {
if(ex instanceof DuplicateKeyException) {
throw new DuplicateResourceFoundException("studentName already present");
}
throw new DuplicateResourceFoundException("studentName already present");
//throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, env.getProperty("error.db.exception"), ex);
}

I was able to solve this. I made the simple mistake import should have been done from org.springframework.dao.DuplicateKeyException. Now this works fine.

Related

Handling specific DataIntegrityViolationException in a transaction

I have a very basic create user controller.
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createUser(#RequestBody UserInput userInput) {
userControllerService.createUser(userInput);
return ResponseEntity.ok("success");
}
The UserControllerService#createUser method is a transaction containing multiple SimpleJpaRepository#save calls. E.g.
#Transactional
#Override
public void createUser(UserInput userInput) {
userRepository.save(userInput);
profileRepository.save(userInput.getProfile());
}
I would like to be able to have the db handle unique constraint violations and be able to inform the client about a specific violation.
For example if I want to inform the client if and only if I get specific constraint violations.
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createUser(#RequestBody UserInput userInput) {
try {
userControllerService.createUser(userInput);
} catch (DuplicateUserNameException e) {
return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
} catch (DuplicateEmailException e) {
return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return ResponseEntity.ok("success");
}
However any constraint violation throws a DataIntegrityViolationException at the end of UserControllerService#createUser. And DataIntegrityViolationException is too brittle to rely on. It only has the cause SQLState: 23505 (unique constraint violation) and an unstructured message, such as:
ERROR: duplicate key value violates unique constraint "unique_user"
Detail: Key (email)=(john#test.com) already exists.
Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.
#Transactional
#Override
public void createUser(UserInput userInput) {
try
userRepository.save(userInput);
} catch (Exception e) {
throw new DuplicateUserNameException();
}
try {
profileRepository.save(userInput.getProfile());
} catch (Exception e) {
throw new DuplicateEmailException();
}
}
Am I going about this the wrong way? It seems like this is very basic functionality that should be possible somehow.
The best way I can think of is adding some code to parse the message from DataIntegrityViolationException, but this has its limitations, for example, for two inserts into the same table have a different meaning for the application. One insert might be directly from the user and the second might be something the application generates. It may not be possible to distinguish the two from the end of the transaction by just parsing the detailed message.
Are there other implementations I should consider instead?
If I understand correctly , it sounds like you want to have a reliable way to determine when DataIntegrityViolationException is thrown , what is the exact reason that causes it such as whether it is due to the duplicated email or duplicated username for a particular use case or anything else.
The simplest way is not to rely on the thrown exception to determine but actively issue some SQL to validate it before the data is saved to DB such as :
#Transactional
#Override
public void createUser(UserInput userInput) {
if(userRepository.existUsername(userInput.getUsername()){
throw new DuplicateUserNameException();
}
if(userRepository.existEmail(userInput.getEmail())){
throw new DuplicateEmailException();
}
userRepository.save(userInput);
profileRepository.save(userInput.getProfile());
}
The problem you mentioned
Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.
should be solvable by telling Hibernate to execute the transaction right away, by calling
userRepository.flush();
This should cause the exception to be thrown on that line, at least.

SQLIntegrityConstraintViolationException cause Spring Transaction Rollback

Spring 4.1.4
Hibernate 4.2.0
JDK 1.8
My context: I have a Controller calling --> Service --> calling Dao
The business funcionality is to delete ( in 1 to many DB relation) some child ,but not all child .
Then ,after deleting some child I try to delete the Parent and offcourse I got java.sql.SQLIntegrityConstraintViolationException
But the question is why transaction is market for Rollback ? ( in other words why I don't got the deletion of some child ?)
SQLIntegrityConstraintViolationException is a checked exception and stating Spring documentation the behaviour would be the same of EJB : Note that by default, rollback happens for runtime, unchecked exceptions only. The checked exception does not trigger a rollback of the transaction.
I need to remove some child anf trying to remove the parent if possible, if not I need to commit the transaction maintaining the parent and remaining of childs
Note I tried also to specify in Service and Dao methods the Spring Annotation
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
To request explicitly the behaviour expected , but not even like this work for me
Controller code method:
public void delete() {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Data deleted.","");
try{
memoTemplateService.delete(memoTemplate);
memoTemplates.remove(memoTemplate);
}
catch (Exception e){
msg=new FacesMessage(FacesMessage.SEVERITY_WARN, "A","B");
}
reset();
FacesContext.getCurrentInstance().addMessage(null, msg);
}
Service method :
#Override
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,DBConstraintException.class})
public void delete(MemoTemplate memoTemplate)throws BusinessException {
// deleting some ,not all , child
phaseAndMemoGenerator.deleteMemosForVisibleTimeHorizon(memoTemplate);
try{// some times Template cannot be deleted
memoTemplateDao.delete(memoTemplate);
}
catch (Exception e){
throw new DBConstraintException("Partial Delete", "Template cannot be deleted, Memo in the past are present");
}
}
Dao
#Repository(value = "memoTemplateDao")
public class MemoTemplateDaoImpl extends GenericJpaDaoImpl<MemoTemplate, Long> implements MemoTemplateDao {
#Override
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
public void delete(MemoTemplate t) {
super.delete(t);
em.flush();
}
}
Just an Update : it's incredible but I can't catch neither doing the catch in Dao method ,debugger go in catch block but before this still a java.sql.SQLIntegrityConstraintViolationException is fired , incredible !
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,PersistenceException.class})
public void tryToDelete(MemoTemplate t)throws Exception {
super.delete(t);
try{
em.flush();
}
catch (Exception e){
throw new Exception("ddddd");
}
}
If there are constraints defined in DB, you won't be able to bypass them by committing without rollback.

How to rollback a transaction on any Exception

#Override
#Transactional(rollbackFor=Exception.class)
public void deletePerson(String id) throws Exception{
PersonEntity personEntity = personrepository.findById(id);
if(personEntity == null){
throws new Exception("No Person found");
}
ElasticPersonEntity elasticPersonEntity = modelMapper.map(personEntity,ElasticPersonEntity.class);
try{
personrepository.delete(personEntity);
elasticpersonrepository.delete(elasticPersonEntity); // Error Occur as Elastic search is down
}
catch(Exception ex){
throws new RuntimeException();
}
}
In my above code, I have to save Person data at two places Cassandra and Elastic search. Hence I need to perform delete on both as well . However if due to some reason if my elastic search is down the above code does not rollback. i.e Data is deleted from Cassandra but is still present in Elastic. Any idea of how to do so. I need to do similar modification at update, create as well.
I dont understand why #Transactional(rollbackFor=Exception.class) dont do the job but as say in this topic you can try to run rollback manually
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Unable to dynamically provision a GORM-capable data source in Grails 3

We are implementing a multitenant application (database per tenant) and would like to include dynamic provisioning of new tenants without restarting the server. This is Grails 3.2.9 / GORM 6.
Among other things this involves creating a dataSource at runtime, without it being configured in application.yml at the application startup.
According to the documentation (11.2.5. Adding Tenants at Runtime) there exists ConnectionSources API for adding tenants at runtime, but the ConnectionSource created this way doesn't seem to be properly registered with Spring (beans for the dataSource, session and transaction manager) and Grails complain about missing beans when we try to use the new datasource.
We expect that when we use the ConnectionSources API to create a connection source for a new database, Grails should initialise it with all the tables according to the GORM Domains in our application, execute Bootstrap.groovy, etc., just like it does for the sources statically configured in application.yml This is not happening either though.
So my question is whether the ConnectionSources API is intended for a different purpose than we are trying to use it for, or it is just not finished/tested yet.
I meant to come back to you. I did manage to figure out a solution. Now this is for schema per customer, not database per customer, but I suspect it would be easy to adapt. I first create the schema using a straight Groovy Sql object as follows:
void createAccountSchema(String tenantId) {
Sql sql = null
try {
sql = new Sql(dataSource as DataSource)
sql.withTransaction {
sql.execute("create schema ${tenantId}" as String)
}
} catch (Exception e) {
log.error("Unable to create schema for tenant $tenantId", e)
throw e
} finally {
sql?.close()
}
}
Then I run the same code as the Liquibase plugin uses, with some simple defaults, as follows:
void updateAccountSchema(String tenantId) {
def applicationContext = Holders.applicationContext
// Now try create the tables for the schema
try {
GrailsLiquibase gl = new GrailsLiquibase(applicationContext)
gl.dataSource = applicationContext.getBean("dataSource", DataSource)
gl.dropFirst = false
gl.changeLog = 'changelog-m.groovy'
gl.contexts = []
gl.labels = []
gl.defaultSchema = tenantId
gl.databaseChangeLogTableName = defaultChangelogTableName
gl.databaseChangeLogLockTableName = defaultChangelogLockTableName
gl.afterPropertiesSet() // this runs the update command
} catch (Exception e) {
log.error("Exception trying to create new account schema tables for $tenantId", e)
throw e
}
}
Finally, I tell Hibernate about the new schema as follows:
try {
hibernateDatastore.addTenantForSchema(tenantId)
} catch (Exception e) {
log.error("Exception adding tenant schema for ${tenantId}", e)
throw e
}
Anywhere you see me referring to 'hibernateDatastore' or 'dataSource' I have those injected by Grails as follows:
def hibernateDatastore
def dataSource
protected String defaultChangelogTableName = "databasechangelog"
protected String defaultChangelogLockTableName = "databasechangeloglock"
Hope this helps.

Exception handling using hibernate template in Spring framework

I am using Spring with Hibernate in my project.There are many methods written in DAO implementation java file and every method is using the same try/catch/finally lines of code which seem redundant to me.
I am told to optimize/refactor the code since the file LOC exceeds 10k.I read somewhere that using HibernateDaoSupport we need not to worry about exceptions or closing the session. It will be taken care of by Spring itself.
Could somebody please help me how to proceed or do the needful or any better way to handle exceptions?I am pasting below code of one method in DAO layer.
public class CSDDaoImpl extends HibernateDaoSupport implements CSDDao {
public Deal getDealStructure(long dealId) throws CSDServiceException {
Session session = null;
try {
session = getSession();
Deal deal = (Deal) session.createCriteria(Deal.class).add(
Restrictions.eq("dealId", dealId)).uniqueResult();
return deal;
} catch (DataAccessResourceFailureException darfex) {
String message = "Failed to retrieve the deal object.";
CSDServiceException ex = new CSDServiceException(message, darfex);
ex.setStackTrace(darfex.getStackTrace());
ex.setErrorCode(Constants.DATA_ACCESS_FAILURE_EXP);
ex.setMessageToUser(message);
throw ex;
} catch (IllegalStateException isex) {
String message = "Failed to retrieve the deal object.";
CSDServiceException ex = new CSDServiceException(message, isex);
ex.setStackTrace(isex.getStackTrace());
ex.setErrorCode(Constants.ILLEGAL_STATE_EP);
ex.setMessageToUser(message);
throw ex;
} catch (HibernateException hbex) {
String message = "Failed to retrieve the deal object.";
CSDServiceException ex = new CSDServiceException(message, hbex);
ex.setStackTrace(hbex.getStackTrace());
ex.setErrorCode(Constants.HIBERNATE_EXP);
ex.setMessageToUser(message);
throw ex;
} finally {
if (session != null && session.isOpen()) {
try {
session.close();
} catch (HibernateException hbex) {
log.error("Failed to close the Hibernate Session.", hbex);
hbex.printStackTrace();
CSDServiceException ex = new CSDServiceException(
"Failed to close the Hibernate Session.", hbex);
ex.initCause(hbex.getCause());
ex.setStackTrace(hbex.getStackTrace());
throw ex;
}
}
}
}
}
The best approach of handling exceptions is i believe through writing an Exception Interceptor to intercept all your DAO calls and you can catch the ones you only need in your application and wrap it with your own custom application specific exceptions.
You definitely do not need to work directly with session once an exception is thrown. That would defeat the purpose of using HibernateDaoSupport and Spring.
Have a look at this link : http://static.springsource.org/spring/docs/current/spring-framework-reference/html/classic-spring.html
Hope that helps.

Resources