Need Proxy?

BotProxy: Rotating Proxies Made for professionals. Really fast connection. Built-in IP rotation. Fresh IPs every day.

Find out more


Spring @Transactional rollbackFor not working for checked Exception for method called outside proxy object

Question

I am having a problem with the rollback of Hibernate updates in combination with Spring.

I have the following class:

@Service
@Transactional(readOnly = true)
public class DocumentServiceImpl extends AbstractGenericService<Document, IDocumentDao> implements DocumentService {

    @Override
    @Transactional(readOnly=false, rollbackFor = DocumentServiceException.class)
    public void saveDocument(final DocumentForm form, final BindingResult result, final CustomUserContext userContext) throws DocumentServiceException {
        Document document = locateDocument(form, userContext);
        if (!result.hasErrors()) {
            try {
                updateDocumentCategories(form, document);
                storeDocument(document, form.getDocumentId(), form.getFile());
                solrService.addDocument(document);
            } catch (IOException e) {
                result.reject("error.uploading.file");
                throw new DocumentServiceException("Error trying to copy the uploaded file to its final destination", e);
            } catch (SolrServerException e) {
                result.reject("error.uploading.file.solr");
                throw new DocumentServiceException("Solr had an error parsing your uploaded file", e);
            }
        }
    }

    @Override
    @Transactional(readOnly = false, rollbackFor = IOException.class)
    public void storeDocument(Document document, String documentId, CommonsMultipartFile uploadedFile) throws IOException {
        getDao().saveOrUpdate(document);
        if (StringUtils.isBlank(documentId)) {
            File newFile = documentLocator.createFile(document);
            uploadedFile.transferTo(newFile);
            // Todo: TEST FOR ROLLBACK ON FILE I/O EXCEPTION
            throw new IOException("this is a test");
        }
    }

The interface is not tagged with any @Transactional annotations. The saveDocument() method is called directly from my Controller, so I expect that the @Transactional configuration of that method is used, particularly the rollbackFor parameter. However, when the DocumentServiceException is thrown, nothing is rolled back (ie the getDao().saveOrUpdate(document) is persisted). For testing purposes I added an "throw new IOException" in the storeDocument method. Hope anybody can help me out how to get this working, it would be greatly appreciated.

Answer

  1. The @Transactional annotation is properly placed. You don;t have to set it at interface level, because it's not automatically inherited (what if your concrete class implements two interfaces with a conflicting transactional setting).

  2. When you say the method are called directly, I assume you have the interface being @Autowired and not the concrete implementation.

  3. Place a break point in your service method and check if your have a TransactionInterceptor entry in your stack trace. If you don't have it, then your transaction management configuration is wrong and you are not using Spring transaction management at all.

Update from Dennis

  1. One more thing that could help other people perhaps:

    I had the tx:annotation-driven in my applicationContext. The applicationContext contained a component-scan for all beans (no filters).

    However, the dispatcherServlet context also contained a component-scan for all beans (legacy code, don't shoot the messenger). So basically I had a copy of all my beans because they were scanned in both contexts.

    And because the beans created in the dispatcherServlet context did not contain the tx:annotation-driven element, the services beans in the dispatcherServlet context were not transactional.

  2. I had to change the component-scan in the dispatcherServlet context into:

    <context:component-scan base-package="your/base/package" use-default-filters="false">    
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
    </context:component-scan>
    

    So, it will only instantiate the controllers in the dispatcher servlet context (and no autowired dependencies, like its services), and the services/daos in the applicationContext.

The services from the applicationContext are then transactionalized.

cc by-sa 3.0