grails.gorm.transactions.Transactional not rolling back - spring

I am having issues rolling back a transaction in my service layer with the following:
Grails 3.3.8
GORM 6.1.10.RELEASE
I have the following service method:
import grails.gorm.transactions.Transactional
#Transactional(rollbackFor = Exception.class)
class TestingService {
void testServiceMethod(List<Factory> factories) {
try {
factories.each {
if (it.name == 'second') {
throw new Exception('second')
}
it.testField = 'Edited'
it.save()
println(it.name + ' saved')
}
} catch (Exception e) {
println('Exception Caught ' + e)
}
}
}
I have the following integration test created then also:
#Integration
#Rollback
class TestServiceIntSpec extends Specification {
#Autowired
TestingService testingService
def setup() {
}
def cleanup() {
}
void "test something"() {
when:
Factory factoryOne = new Factory(name: "first").save(flush: true)
Factory factoryTwo = new Factory(name: "second").save(flush: true)
List<Factory> factories = [factoryOne, factoryTwo]
testingService.testServiceMethod(factories)
then:
factoryOne.testField == null
factoryTwo.testField == null
}
}
I also have the following controller method:
class TestController {
TestingService testingService
def index() {
Factory factoryOne = new Factory(name: "first").save(flush: true)
Factory factoryTwo = new Factory(name: "second").save(flush: true)
List<Factory> factories = [factoryOne, factoryTwo]
testingService.testServiceMethod(factories)
println "First Factory: $factoryOne.testField"
println "First Factory: $factoryTwo.testField"
render 'Check Console'
}
}
I would have expected the test to pass as I thought the transaction would of rolled back after I threw new exception, the it.testField is persisting though however? Also when I ping the TestController it is outputting factoryOne.testField as 'edited'. Am I misunderstanding this correctly from the documentation?
"Services enable transaction demarcation, which is a declarative way of defining which methods are to be made transactional. To enable transactions on a service use the Transactional transform:
The result is that all methods are wrapped in a transaction and automatic rollback occurs if a method throws an exception (both Checked or Runtime exceptions) or an Error."
Source: https://docs.grails.org/latest/guide/services.html#declarativeTransactions
I can't see what I'm doing different from this other Stackoverflow answer either:
https://stackoverflow.com/a/25739582/6887293
The issue can be recreated by pulling the following Github project and running /factory/factory/src/integration-test/groovy/com/mycompany/myapp/TestServiceIntSpec.groovy or pinging /factory/factory/grails-app/controllers/com/mycompany/myapp/TestController.groovy
https://github.com/georgy3k/IntegrationTestRollBack/tree/8addd2b95a8ffa4570e70eccb3b023b0ccfef5aa
Thanks in advance ...

In your catch block you need to re-throw the exception.
catch (Exception e) {
println('Exception Caught ' + e)
throw e;
}
The problem as i see it, is that the exception never escapes the method.

Related

Spring boot #Transactional not calling my catch block

Why is the ConstraintViolationException catch block not executing below when I use #Transactional. It does when I remove #Transactional. I want it to execute so I can do some custom error handling. I do understand that Spring wraps my code in a proxy which does the transaction management and that checked exceptions are not rolledback by default. But I still can't connect the dots here:
#Transactional
public PropertyInserterResult insertOrUpdateProperty(ScalerProperty property,
MultipartFile logo, ScalerUser loggedInUser) {
ScalerLogo scalerLogo = null;
int success = 0, error = 0;
try {
logger.info("Logo size:"+logo.getSize());
if(logo.getSize() > 0) {
scalerLogo = new ScalerLogo(logo.getBytes());
scalerLogoRepository.save(scalerLogo);
}
scalerPropertyRepository.save(property.prepareToSave(loggedInUser, scalerLogo));
logger.info("Prop {}:{} inserted for {}",property.getKey(), property.getValue(), property.getCreatedBy().getUsername());
++success;
} catch (org.hibernate.exception.ConstraintViolationException cve) {
logger.error("Dupe key");
handleDuplicate(property);
++error;
}
return new PropertyInserterResult(success, error);
}

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.

Spring #Transactional behavior calling both Transactional and Non-Transactional Methods

I'm looking at some existing code and wanted to know what happen's in the following scenario with Spring's #Transactional annotation? Consider the following example:
A POST request hits a #Controller annotated with #Transactional:
#ResponseBody
#Transactional
#RequestMapping(value="/send", method=RequestMethod.POST)
public void send(#RequestBody Response response) {
try {
DBItem updatedDbItem = repository.updateResponse(response);
if (updatedDbItem == null){
//some logging
}
} catch(Exception ex) {
//some logging
}
}
The controller calls a non #transactional repository method which sets a value and in turns calls a another #Transactional method:
#Override
public DBItem updateResponse(Response response) {
try {
DBItem dBItem = findResponseById(response.getKey());
if (dBItem != null){
dBItem.setSomeField(response.getValue());
return updateDataBaseItem(response);
}
} catch (Exception ex) {
//some logging
}
return null;
}
The following updateDataBaseItem() method is common and called from other non transactional methods as well as the above method:
#Transactional
#Override
public DBItem updateDataBaseItem(Response response){
try {
DBItem dBItem = em.merge(response);
return dBItem;
} catch (Exception ex) {
//some logging
}
return null;
}
send() => spring detect #transaction with default parameters
actually Propagation setting is REQUIRED and the spring join the exist transaction or create new if none.
repository.updateResponse(..) => No transactions params the method execute within the same transaction already exist
updateDataBaseItem(..) => calling the method in same repository , spring will not recognize the #Transaction annotation because the use of proxy mode, so this method will be executed within the same transaction
a method within the target object calling another method of the target
object, will not lead to an actual transaction at runtime even if the
invoked method is marked with #Transactional

Test case using SpringJunitRunner

I want to write junit test case for the below code with springJunitRunner.
the below piece of code is one service in a class.
#Component
#Path(/techStack)
public class TechStackResource {
#Autowired
private transient TechStackService techStackService;
#GET
#Path("/{id}")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getTechStackById(final #PathParam("id") Integer technicalstackid) {
final TechStackResponse response = new TechStackResponse();
int statusCode = Constants.HTTP_STATUS_OK_200;
try {
TechStackModel techStackModel = techStackService.findObjectById(technicalstackid);
response.setGetTechStackDetails(GetTechStackDetails.newBuilder().technicalStack(techStackModel).build());
if (techStackModel == null) {
statusCode = Constants.HTTP_STATUS_ERROR_404;
}
} catch (EmptyResultDataAccessException erde) {
} catch (Exception e) {
LOGGER.error("Exception occured in TechStackResource.getTechStackById(technicalstackid) ", e);
throw new APMRestException(
"Exception while executing TechStackResource.getTechStackById(technicalstackid) ",
Constants.UNKNOW_ERROR, e);
}
return Response.status(statusCode).entity(response).build();
}
}
the configuration in web.xml for servlet is
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
Since you are using Jersey as well as Spring, you can use the SpringJunitRunner only to wire-up TechStackResource with its dependency TechStackService.
In order to test your REST handler method getTestStackById, you could go the POJO approach and invoke it directly. Alternatively, you can use Jersey's own MockWeb environment. To find out more about this, I recommend looking at the Jersey example sources, e.g. HelloWorld.

CMT rollback : how to roll back the transaction

The following code does not help in roll back even if I throw null pointer exception at update() method. Everytime it inserts values into the database if I run the code. Please help me how can I roll back the transaction if null pointer is thrown at update() method. Am I missing something in the code?
#TransactionManagement(value = TransactionManagementType.CONTAINER)
public class Bean implements RemoteIF {
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertIntoDb() {
insert();
update();
}
private Integer update() {
val=0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val1 = st.executeUpdate("INSERT INTO tab VALUES('ab')");
st.close();
throw new NullPointerException();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
private Integer insert() {
int val = 0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val = st.executeUpdate("INSERT INTO tab VALUES('bnm')");
st.close();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
}
Couple things that stick out at me as suspect.
No #Stateless, #Stateful or #Singleton annotation on the Bean class. Unless you've declared the bean in an ejb-jar.xml file, then this is not getting recognized as an EJB. Definitely double check that.
The DbConn.getConnection() looks suspiciously like you might be trying to manage database connections yourself. If you have any code that uses the DriverManager or does new FooDataSource(), then that is definitely the problem. If you want transaction management to work you have to get all resources from the container via either
Injection via a #Resource DataSource datasource field in the EJB class
JNDI lookup of java:comp/env/yourDataSource, where yourDataSource is the name of a datasource you configured in the ejb-jar.xml or declared on the bean class via using #Resource(type=DataSource.class, name="youDataSource") -- that annotation goes on the class itself rather than a method or field.
See also these answers for some insight as to how transaction management works:
How does UserTransaction propagate?
Programming BMT - UserTransaction

Resources