OpentransactionPerView instead #Transactional - performance

I have a Java EE Application with Spring 3.1.1 and Hibernate 4.1. Now I wanted to speed up things and saw that the bottleneck is the opening + closing of multiple transactions in one request.
Now I removed all #Transactional annotations and created my own OpenSessionInViewFilter, which opens and closes one transaction.
package utils.spring;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class CustomHibernateSessionViewFilter extends OpenSessionInViewFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
// Do not modify the Session: just set the participate flag.
participate = true;
} else {
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = openSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
//BEGIN TRANSACTION
session.beginTransaction();
}
try {
filterChain.doFilter(request, response);
}
finally {
if (!participate) {
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
// COMMIT
sessionHolder.getSession().getTransaction().commit();
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
}
}
}
Is that a good idea? It seems to work and speeded up things.
Here is my log for the transactions:
http-bio-8080-exec-3 01/03/2013 11:25:20,947 | DEBUG | org.springframework.orm.hibernate4.HibernateTransactionManager | doGetTransaction | Found thread-bound Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] for Hibernate transaction
http-bio-8080-exec-3 01/03/2013 11:25:20,948 | DEBUG | org.springframework.orm.hibernate4.HibernateTransactionManager | getTransaction | Creating new transaction with name [by2.server.service.UserService.loadUserByUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
http-bio-8080-exec-3 01/03/2013 11:25:20,948 | DEBUG | org.springframework.orm.hibernate4.HibernateTransactionManager | doBegin | Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
http-bio-8080-exec-3 01/03/2013 11:25:21,172 | DEBUG | org.springframework.orm.hibernate4.HibernateTransactionManager | doBegin | Exposing Hibernate transaction as JDBC transaction [org.hibernate.engine.jdbc.internal.proxy.ConnectionProxyHandler#1e7b64f4[valid=true]]
http-bio-8080-exec-3 01/03/2013 11:25:21,188 | DEBUG | org.hibernate.SQL | logStatement | select userentity_.userID as userID5_ from users userentity_ where userentity_.username=?
connection pool
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>4.1.1.Final</version>
</dependency>
<property name="hibernateProperties">
<value>
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.bytecode.use_reflection_optimizer=false
hibernate.max_fetch_depth=0
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=300
hibernate.c3p0.max_statements=50
hibernate.c3p0.idle_test_period=3000
</value>
</property>
but the open session in view filter seems to close the sessions
finally {
if (!participate) {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
}
But even when I remove the filter, Hibernate doesn't seem to use the pool.

I would say no.
For example, you'll save some product or whatever in the database, and show a success page or redirect, thinking everything has been saved. But the transaction won't be committed yet and could still rollback after you have displayed the success message.
And with Hibernate, the probability of this happening is even bigger, because nothing will be written to the database until flush time, which will happen just before the commit.
Moreover the transaction will live longer than necessary, preventing other transactions to procees in case it has put a lock on rows or tables in the database, resulting in poorer performance and scalability.
What's wrong with the default Spring OpenSessionInViewFilter, which lets the session open, but still uses service-level, short transactions? Why do your requests open multiple transactions? My quess is that you're doing too many calls from the UI layer to the service layer in a single request.

Finally i configured my pool the right way... the solution was not to add some c3p0 properties to my hibernate config, i just had to replace my datasource-bean
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- Connection properties -->
<property name="driverClass" value="org.postgresql.Driver" />
<property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/DBNAME" />
<property name="user" value="xxx" />
<property name="password" value="xxx" />
<!-- Pool properties -->
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxStatements" value="50" />
<property name="idleConnectionTestPeriod" value="3000" />
<property name="loginTimeout" value="300" />
</bean>
runs like a charm now

Related

Hibernate Transaction Manager not committing data changes

I'm using Hibernate 4 to write data to an H2 embedded in-memory database and there seems to be a problem with transactions. The application already uses Oracle and H2 has been added with a separate DataSource, SessionFactory, and TransactionManager. The original TransactionManager is marked as default and the H2 TransactionManager has the qualifier memTransactions
The following code - specifically the load function - correctly populates the memEvents variable at the end with the written data.
#Repository
#Transactional(transactionManager = "memTransactions")
public class EventMemDaoHibernate implements EventMemDao {
#Autowired
#Qualifier(value = "memSessionFactory")
private SessionFactory memSessionFactory;
#Override
public List<EventMem> getEvents() {
return memSessionFactory.getCurrentSession().createCriteria(EventMem.class).list();
}
#Override
public void load(List<Event> allEvents) {
Session session = memSessionFactory.getCurrentSession();
for (Event e : allEvents) {
EventMem memEvent = new EventMem(e);
session.save(memEvent);
}
List<EventMem> memEvents = getEvents(); // correct
}
}
However the following code produces an empty memEvents list
#Autowired
private EventMemDao eventMemDao;
List<Event> allEvents = eventDao.getAllEvents();
eventMemDao.load(allEvents); // calls the load function shown above
List<EventMem> memEvents = eventMemDao.getEvents(); // empty
I assume this is related to transaction management (e.g.: data is not auto-committed after the call to .save()). However when I tried explicitly beginning and committing a transaction within EventMemDaoHibernate#load, I receive this error:
nested transactions not supported
So, from what I can tell the TransactionManager is working.
My TransactionManager and related bean definitions are shown below.
<bean
id="memTransactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="memSessionFactory" />
<qualifier value="memTransactions"/>
</bean>
<bean id="hDataSource" class="org.h2.jdbcx.JdbcDataSource">
<property name="url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:scripts/init-h2.sql'" />
<property name="user" value="sa" />
<property name="password" value="" />
</bean>
<bean
id="memSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="hDataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
</props>
</property>
</bean>
This was due to my configuration error (of course). I didn't fully grasp that the connection URL was evaluated every time a session was opened against H2 and that means init-h2.sql was executed repeatedly. init-h2.sql included a truncate followed by an insert so it was dropping and recreating data every time Hibernate opened a session.

Hibernate saveorupdateall not doing save not update

I have a spring integration hibernate application. My application picks up files and checks their entries against the database. For this i am using Hibernate.
I want to disable certain rows in the database. I am retrieving the rows using criteria. Editing one field and using saveorupdateall method. This is not changing my db rows at all!!
Following are the code snippets
<bean id="dmsdbDetectionJob" class="com.polling.scheduler.job.DMSDBDetectionJob">
</bean>
<bean id="dmsdbDetectionJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="dmsdbDetectionJob" />
<property name="targetMethod" value="deleteDBMaster" />
<property name="concurrent" value="false" />
</bean>
<bean id="simpleTriggerDB" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="dmsdbDetectionJobDetail" />
<!-- <property name="cronExpression" value="1 * * * * ?" /> -->
<!-- <property name="cronExpression" value="0 0/1 * * * ?" /> -->
<property name="cronExpression" value="0 52 11 ? * *" />
</bean>
This fires at given time properly. deleteDBMaster method calls this code:
//get Document files not having any links
List<DocumentFile> strayFiles = documentDaoImpl.getStrayDocumentFiles();
try
{
if (strayFiles.size() > 0)
{
//delete
strayFiles = documentDaoImpl.softDelete(strayFiles, DocumentFile.class);
documentDaoImpl.saveOrUpdateAll(strayFiles);}
}...
The softDelete method uses generics
#Override
public <T extends GenericEntity> List<T> softDelete(List<T> stray,
Class<T> clazz)
{
for (T tObj : stray)
{
clazz.cast(tObj).setActive(Constants.INACTIVE.getVal());
}
return stray;
}
When I debug I see the value changed in the active property of the objects in the strayFiles list, inside saveOrUpdateAll() method. The saveOrUpdateAll method is:
#Override
public void saveOrUpdateAll(Collection<?> objects) throws DMSException {
try {
for (Object object : objects) {
getCrntSession().saveOrUpdate(object);
}
} catch (final HibernateException e) {
throw new DMSException("Exception while save or update All ", ErrorCode.BASE_DB_ERROR, e);
}
}
When the saveorupdate is called no query is logged by hibernate. Nothing changes in the db!!
Since this is in spring integration I have just added a #Transactional to the method. I am presuming that spring must do the flush or commit. Is that wrong? And even then I should see the query right??
Please let me know what is wrong in the code...
Thanks
EDIT::
Change my softdelete code to the following. Thought maybe the generics are causing this.
#Override
#Transactional
public void softDeleteDocumentFile(
List<DocumentFile> stray)
{
for (DocumentFile tObj : stray)
{
tObj.setActive(Constants.INACTIVE.getVal());
saveEntity(tObj);
}
}
Still no change. The rows are not getting updated. Hibernate is not printing the update query on the console!!
Please let me know if anyone finds any mistake in the code..
EDIT::
This is the configuration
<mvc:annotation-driven />
<context:component-scan base-package="com.polling" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<context:property-placeholder location="classpath:config/jdbc.properties,classpath:config/config.properties,classpath:config/ftp.properties"/>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- Configure Hibernate 4 Session Factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.polling.entity</value>
</list>
</property>
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
EDIT::
Tried using a hardcoded id. Used update instead of save. But still same result, no update query and hence no change in row!!
EDIT::
This is the log at trace level
14:31:12.644 T|TransactionInterceptor |Completing transaction for [com.polling.service.DocumentServiceImpl.deActivateStrays]
14:31:12.644 T|TransactionInterceptor |Completing transaction for [com.polling.scheduler.job.DMSDBDetectionJob.deleteDBMaster]
14:31:12.675 T|HibernateTransactionManager |Triggering beforeCommit synchronization
14:31:12.675 T|HibernateTransactionManager |Triggering beforeCompletion synchronization
14:31:12.675 D|HibernateTransactionManager |Initiating transaction commit
14:31:12.675 D|HibernateTransactionManager |Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[com.polling.entity.DocumentFile#273], EntityKey[com.polling.entity.DocumentGroup#107], EntityKey[com.polling.entity.DocumentFile#275]],collectionKeys=[CollectionKey[com.polling.entity.DocumentFile.docgroups#275], CollectionKey[com.polling.entity.DocumentGroup.files#107], CollectionKey[com.polling.entity.DocumentFile.docgroups#273]]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#2a6f7180 updates=org.hibernate.engine.spi.ExecutableList#7a84a043 deletions=org.hibernate.engine.spi.ExecutableList#1935cd8c orphanRemovals=org.hibernate.engine.spi.ExecutableList#1b49af42 collectionCreations=org.hibernate.engine.spi.ExecutableList#291240d collectionRemovals=org.hibernate.engine.spi.ExecutableList#6d5d2cc collectionUpdates=org.hibernate.engine.spi.ExecutableList#40025295 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#587bd507 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
14:31:12.675 D|AbstractTransactionImpl |committing
14:31:12.675 T|SessionImpl |Automatically flushing session
14:31:12.675 T|AbstractFlushingEventListener |Flushing session
14:31:12.675 D|AbstractFlushingEventListener |Processing flush-time cascades
14:31:12.675 T|Cascade |Processing cascade ACTION_SAVE_UPDATE for: com.polling.entity.DocumentFile
14:31:12.675 T|Cascade |Done processing cascade ACTION_SAVE_UPDATE for: com.polling.entity.DocumentFile
14:31:12.675 T|Cascade |Processing cascade ACTION_SAVE_UPDATE for: com.polling.entity.DocumentFile
14:31:12.675 T|Cascade |Done processing cascade ACTION_SAVE_UPDATE for: com.polling.entity.DocumentFile
14:31:12.675 D|AbstractFlushingEventListener |Dirty checking collections
14:31:12.675 T|AbstractFlushingEventListener |Flushing entities and processing referenced collections
14:31:12.675 D|Collections |Collection found: [com.polling.entity.DocumentFile.docgroups#273], was: [com.polling.entity.DocumentFile.docgroups#273] (uninitialized)
14:31:12.691 D|Collections |Collection found: [com.polling.entity.DocumentFile.docgroups#275], was: [com.polling.entity.DocumentFile.docgroups#275] (uninitialized)
14:31:12.691 D|Collections |Collection found: [com.polling.entity.DocumentGroup.files#107], was: [com.polling.entity.DocumentGroup.files#107] (uninitialized)
14:31:12.691 T|AbstractFlushingEventListener |Processing unreferenced collections
14:31:12.691 T|AbstractFlushingEventListener |Scheduling collection removes/(re)creates/updates
14:31:12.691 D|AbstractFlushingEventListener |Flushed: 0 insertions, 0 updates, 0 deletions to 3 objects
14:31:12.691 D|AbstractFlushingEventListener |Flushed: 0 (re)creations, 0 updates, 0 removals to 3 collections
14:31:12.691 D|EntityPrinter |Listing entities:
14:31:12.691 D|EntityPrinter |com.polling.entity.DocumentFile{encodingKey=yyy, docgroups=<uninitialized>, contactNumber=12121212, documentType=com.polling.entity.DocumentType#5, totalLinks=1, modifiedBy=com.polling.entity.UserApplicationGroup#3, documentFileName=f11, documentDescription=null, totalDownloads=0, uploaderCompletePath=somepath/somepath, modifiedDate=2014-12-16 14:36:57.707, size=0, DMSPath=c:/DMSFinalRoot\dir0\File0, id=273, createdBy=com.polling.entity.UserApplicationGroup#3, documentFormat=com.polling.entity.DocumentFormat#1, uploadDateTime=2014-12-16 14:36:56.86, keyWords=null, active=3, uploadStage=1, uploaderIP=0:0:0:0:0:0:0:1, createdDate=2014-12-16 14:36:57.707, contactPerson=some name, uploaderName=scmsname}
14:31:12.691 D|EntityPrinter |com.polling.entity.DocumentGroup{files=<uninitialized>, expiryPresent=false, modifiedBy=com.polling.entity.UserApplicationGroup#3, uploadStatus=0, documentDescription=null, modifiedDate=2014-12-22 14:28:09.027, id=107, nodeId=206, totalFiles=0, createdBy=com.polling.entity.UserApplicationGroup#3, documentFormat=null, objectId=101, active=3, documentName=doc1, nodeType=document, objectType=demand, createdDate=2014-12-22 14:28:09.027, expiryAt=null}
14:31:12.691 D|EntityPrinter |com.polling.entity.DocumentFile{encodingKey=azaz, docgroups=<uninitialized>, contactNumber=12121212, documentType=com.polling.entity.DocumentType#5, totalLinks=1, modifiedBy=com.polling.entity.UserApplicationGroup#3, documentFileName=f11, documentDescription=null, totalDownloads=0, uploaderCompletePath=somepath/somepath, modifiedDate=2014-12-17 16:52:23.163, size=0, DMSPath=c:/DMSFinalRoot\dir2\File0, id=275, createdBy=com.polling.entity.UserApplicationGroup#3, documentFormat=com.polling.entity.DocumentFormat#1, uploadDateTime=2014-12-17 16:52:22.127, keyWords=null, active=9, uploadStage=1, uploaderIP=0:0:0:0:0:0:0:1, createdDate=2014-12-17 16:52:23.163, contactPerson=some name, uploaderName=scmsname}
14:31:12.691 T|AbstractFlushingEventListener |Executing flush
14:31:12.691 T|JdbcCoordinatorImpl |Starting after statement execution processing [ON_CLOSE]
14:31:12.691 T|AbstractFlushingEventListener |Post flush
14:31:12.691 T|SessionImpl |before transaction completion
14:31:12.691 D|JdbcTransaction |committed JDBC Connection
14:31:12.691 D|JdbcTransaction |re-enabling autocommit
14:31:12.691 T|TransactionCoordinatorImpl |after transaction completion
14:31:12.707 T|SessionImpl |after transaction completion
14:31:12.707 T|HibernateTransactionManager |Triggering afterCommit synchronization
14:31:12.707 T|HibernateTransactionManager |Triggering afterCompletion synchronization
14:31:12.707 T|TransactionSynchronizationManager |Clearing transaction synchronization
14:31:12.707 T|TransactionSynchronizationManager |Removed value [org.springframework.orm.hibernate4.SessionHolder#711fbb66] for key [org.hibernate.internal.SessionFactoryImpl#5e2ed6a9] from thread [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-7]
14:31:12.707 T|TransactionSynchronizationManager |Removed value [org.springframework.jdbc.datasource.ConnectionHolder#67914078] for key [org.springframework.jdbc.datasource.DriverManagerDataSource#7bc247d0] from thread [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-7]
14:31:12.707 D|HibernateTransactionManager |Closing Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[com.polling.entity.DocumentFile#273], EntityKey[com.polling.entity.DocumentGroup#107], EntityKey[com.polling.entity.DocumentFile#275]],collectionKeys=[CollectionKey[com.polling.entity.DocumentFile.docgroups#275], CollectionKey[com.polling.entity.DocumentGroup.files#107], CollectionKey[com.polling.entity.DocumentFile.docgroups#273]]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList#2a6f7180 updates=org.hibernate.engine.spi.ExecutableList#7a84a043 deletions=org.hibernate.engine.spi.ExecutableList#1935cd8c orphanRemovals=org.hibernate.engine.spi.ExecutableList#1b49af42 collectionCreations=org.hibernate.engine.spi.ExecutableList#291240d collectionRemovals=org.hibernate.engine.spi.ExecutableList#6d5d2cc collectionUpdates=org.hibernate.engine.spi.ExecutableList#40025295 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList#587bd507 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] after transaction
14:31:12.707 T|SessionImpl |Closing session
14:31:12.707 I|StatisticalLoggingSessionEventListener |Session Metrics {
36322379 nanoseconds spent acquiring 1 JDBC connections;
0 nanoseconds spent releasing 0 JDBC connections;
16153296 nanoseconds spent preparing 2 JDBC statements;
20467177 nanoseconds spent executing 2 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
7830765 nanoseconds spent executing 1 flushes (flushing a total of 3 entities and 3 collections);
85953666 nanoseconds spent executing 2 partial-flushes (flushing a total of 2 entities and 2 collections)
}
Have found the crux of my problem. Wasnt related to transactions at all. By mistake I had added updatable=false to my entity class property(active) get accessor. Sorry for the trouble. Really a very elementary mistake!!! Apologies to all that have spent time on this..
session.saveOrUpdate(Object) method only converts any transient object into persistence object, means object associates with current hibernate session. When current hibernate session will be flushed or hibernate transaction will be committed then only actual query will run to store object data into the database.
Hibernate saveOrUpdate(Object) works like above comment. I don't know much about Spring.
I hope above description will work for you.
Example code : Hibernate
Session session = sessionFactory.getCurrentSession();
session.setFlush(FlushMode.COMMIT);// Session will be flushed when transation.commit will be called
Transaction tx = session.beginTransaction();
session.saveOrUpdate(Object obj);
tx.commit();// this line will run query to map your object on database
OR
Session session = sessionFactory.getCurrentSession();
session.setFlush(FlushMode.MANUAL);// Session will be only ever flushed when session.flush() will be explicitely called by aaplication
Transaction tx = session.beginTransaction();
session.saveOrUpdate(Object obj);
session.flush();// this line will run query to map your object on database
tx.commit();
Thanks
What you've written so far seems that your Spring TransactionManagement configuration is wrong.
A simple solution if you don't want to configure aspects is to use a programmatic approach using the Spring Framework TransactionTemplate
public class DMSDBDetectionJob {
private final TransactionTemplate transactionTemplate;
public DMSDBDetectionJob(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
this.transactionTemplate = new TransactionTemplate(transactionManager);
// optional configuration of the Transaction like timeout
// this.transactionTemplate.setTimeout(30); // 30 seconds
}
public void deleteDBMaster() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
deleteDBMasterImpl();
}
});
}
private void deleteDBMasterImpl() {
List<DocumentFile> strayFiles = documentDaoImpl.getStrayDocumentFiles();
try
{
if (strayFiles.size() > 0)
{
//delete
strayFiles = documentDaoImpl.softDelete(strayFiles, DocumentFile.class);
documentDaoImpl.saveOrUpdateAll(strayFiles);
...
}
} catch (...) {...}
}
Your Spring Configuration should have at least two entries like:
<!-- PlatformTransactionManager which are going to drive transaction-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- Instance of transaction template -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>

Spring JPA transaction over multiple methods

I'm using Spring 3.2 with JPA and Hibernate 4 in a web application running in Tomcat 7. The application is divided into controller, service an DAO classes. The service classes have an annotated transaction configuration at class and method level. The DAOs are plain JPA with entity manager injected by #PersistenceContext annotation.
#Service("galleryService")
#Transactional(propagation=Propagation.SUPPORTS, readOnly=true)
public class GalleryServiceImpl implements GalleryService {
#Override
public Picture getPicture(Long pictureId) {
return pictureDao.find(pictureId);
}
#Override
public List<PictureComment> getComments(Picture picture) {
List<PictureComment> comments = commentDao.findVisibleByPicture(picture);
Collections.sort(comments, new Comment.ByCreatedOnComparator(Comment.ByCreatedOnComparator.SORT_DESCENDING));
return comments;
}
...
}
#Controller
#RequestMapping("/gallery/displayPicture.html")
public class DisplayPictureController extends AbstractGalleryController {
#RequestMapping(method = RequestMethod.GET)
public String doGet(ModelMap model, #RequestParam(REQUEST_PARAM_PICTURE_ID) Long pictureId) {
Picture picture = galleryService.getPicture(pictureId);
if (picture != null) {
model.addAttribute("picture", picture);
// Add comments
model.addAttribute("comments", galleryService.getComments(picture));
} else {
LOGGER.warn(MessageFormat.format("Picture {0} not found.", pictureId));
return ViewConstants.CONTENT_NOT_FOUND;
}
return ViewConstants.GALLERY_DISPLAY_PICTURE;
}
...
}
I switched on debug logging for org.springframework.transaction and noticed, that "Creating new transaction", "Opened new EntityManager", "Getting...", "Closing..." and "Committing transaction" is done for every call of a method in my service class. Even if those methods where called by one single method in my controller class. Here is an example of my log output:
2014-12-03 10:53:00,448 org.springframework.transaction.support.AbstractPlatformTransactionManager getTransaction
DEBUG: Creating new transaction with name [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2014-12-03 10:53:00,448 org.springframework.orm.jpa.JpaTransactionManager doBegin
DEBUG: Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl#6133a72f] for JPA transaction
2014-12-03 10:53:00,468 org.springframework.orm.jpa.JpaTransactionManager doBegin
DEBUG: Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#5182c1b7]
2014-12-03 10:53:00,468 org.springframework.transaction.interceptor.TransactionAspectSupport prepareTransactionInfo
TRACE: Getting transaction for [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]
2014-12-03 10:53:00,489 org.springframework.transaction.interceptor.TransactionAspectSupport commitTransactionAfterReturning
TRACE: Completing transaction for [de.domain.webapp.gallery.service.GalleryServiceImpl.getPicture]
2014-12-03 10:53:00,489 org.springframework.transaction.support.AbstractPlatformTransactionManager processCommit
DEBUG: Initiating transaction commit
2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCommit
DEBUG: Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl#6133a72f]
2014-12-03 10:53:00,489 org.springframework.orm.jpa.JpaTransactionManager doCleanupAfterCompletion
DEBUG: Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl#6133a72f] after transaction
2014-12-03 10:53:00,489 org.springframework.orm.jpa.EntityManagerFactoryUtils closeEntityManager
DEBUG: Closing JPA EntityManager
I know that I can use an OpenSessionInView to hold the hibernate session for a complete request but some people said, OSIV is an antipattern. Instead I'm using SpringOpenEntityManagerInViewFilter. But with no success. How can I achieve, Spring uses a single transaction for multiple service layer method calls of my controller? Or did I missunderstand anything?
Here a part of my Spring configuration:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL" />
<property name="showSql" value="false" />
</bean>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/tikron" />
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="jpaTemplate" class="org.springframework.orm.jpa.JpaTemplate">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<context:component-scan base-package="de.domain.webapp">
<context:include-filter type="regex" expression=".*Service"/>
</context:component-scan>
My persistence unit:
<persistence-unit name="tikron-data" transaction-type="RESOURCE_LOCAL">
<!-- Entities located in external project tikron-data -->
<jar-file>/WEB-INF/lib/tikron-data-2.0.1-SNAPSHOT.jar</jar-file>
<!-- Enable JPA 2 second level cache -->
<shared-cache-mode>ALL</shared-cache-mode>
<properties>
<property name="hibernate.hbm2ddl.auto" value="validate" />
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
<property name="hibernate.generate_statistics" value="false" />
</properties>
</persistence-unit>
Some more log output from application startup:
2014-12-03 10:46:48,428 org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean createNativeEntityManagerFactory
INFO: Building JPA container EntityManagerFactory for persistence unit 'tikron-data'
2014-12-03 10:46:48,428 org.hibernate.ejb.HibernatePersistence logDeprecation
WARN: HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; use [org.hibernate.jpa.HibernatePersistenceProvider] instead.
2014-12-03 10:46:48,448 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
name: tikron-data
...]
...
2014-12-03 10:46:51,101 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory#6cccf90d: defining beans [propertyConfigurer,messageSource,entityManagerFactory,dataSource]; root of factory hierarchy
2014-12-03 10:46:51,111 org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 3374 ms
Thanks in advance.
You need to play your PROPAGATION_LEVEL and structure your service bean calls properly. If you're using #Transactional on your Service classes, what you described is normal, since the transaction demarcation happens on public methods level. So depending on the propagation level when entering the public method of the service bean, the transaction will either start, join an existing transaction, throw an exception or execute non-transactionally.
To have service methods execute in one transaction, its enough to have the propagation level set to support as you do in your GalleryService (providing that you don't override it on a method level), and call these methods from a single method of another service which is annotated #Transactional(propagation=Propagation.REQUIRED). Its important to have you're calls pass through a bean e.g. (galleryService.getPicture instead of local call getPicture), 'cause aspects that inject the transaction semantics work against a proxy that wraps the bean
#Service("exampleService")
#Transactional(propagation=Propagation.REQUIRED)
public class ExampleServiceImpl implements ExampleService {
#Autowired
private GalleryService galleryService;
#Override
public void singleTransaction() {
galleryService.getPicture
galleryService.getComments
}
...
}
a brief PROPAGATION_LEVEL glossary
MANDATORY
Support a current transaction, throw an exception if none exists.
NESTED
Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else.
NEVER
Execute non-transactionally, throw an exception if a transaction exists.
NOT_SUPPORTED
Execute non-transactionally, suspend the current transaction if one exists.
REQUIRED
Support a current transaction, create a new one if none exists.
REQUIRES_NEW
Create a new transaction, suspend the current transaction if one exists.
SUPPORTS
Support a current transaction, execute non-transactionally if none exists.
UPDATE with respect to the comment
But is combining service method calls into one service method the only way to handle those calls in one single transaction?
No, but in my opinion its your best option. Consider the article http://www.ibm.com/developerworks/java/library/j-ts2/index.html. What I'm describing is whats referred to in the article as API layer strategy. Its defined as
The API Layer transaction strategy is used when you have
coarse-grained methods that act as primary entry points to back-end
functionality. (Call them services if you would like.) In this
scenario, clients (be they Web-based, Web services based,
message-based, or even desktop) make a single call to the back end to
perform a particular request.
Now in the standard three-layer architecture you have the presentation, a business and a persistence layer. In simple words you can annotate your controllers, services or DAOs. Services are the ones holding the logical unit of work. If you annotate your controllers, they are part of your presentation layer, if your transactional semantics is there, and you decide to switch or add a non-http client (e.g. Swing client), you're bound to either migrate or duplicate your transaction logic. DAO layers should also not be the owners of transaction, 'the granularity of the DAO methods is much less than what is a business logical unit. I'm restraining from points like best-practice etc. but, if you're uncertain, choose your business (service) as your API transaction layer :)
You have numerous posts discussing this topic in all directions
why use #transactional with #service insted of with #controller
Where should "#Transactional" be place Service Layer or DAO
very good and fun reading, many opinions and very context-dependant

transactions not working with Spring 3.1 – H2 – junit 4– hibernate 3.2

I have the following test..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/schedule-agents-config-context.xml"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional
public class H2TransactionNotWorkingTest extends SubmitAgentIntegratedTestBase {
private static final int FIVE_SUBMISSIONS = 5;
#Autowired
private ApplicationSubmissionInfoDao submissionDao;
private FakeApplicationSubmissionInfoRepository fakeRepo;
#Before
public void setUp() {
fakeRepo = fakeRepoThatNeverFails(submissionDao, null);
submitApplication(FIVE_SUBMISSIONS, fakeRepo);
}
#Test
#Rollback(true)
public void shouldSaveSubmissionInfoWhenFailureInDatabase() {
assertThat(fakeRepo.retrieveAll(), hasSize(FIVE_SUBMISSIONS));
}
#Test
#Rollback(true)
public void shouldSaveSubmissionInfoWhenFailureInXmlService() {
assertThat(fakeRepo.retrieveAll().size(), equalTo(FIVE_SUBMISSIONS));
}
}
...and the following config...
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:/db/h2-schema.sql" />
</jdbc:embedded-database>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionalSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
</props>
</property>
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy"/>
</property>
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
<property name="packagesToScan" value="au.com.mycomp.life.snapp"/>
</bean>
<bean id="regionDependentProperties" class="org.springframework.core.io.ClassPathResource">
<constructor-arg value="region-dependent-service-test.properties"/>
</bean
>
I have also set auto commit to false in the sql script
SET AUTOCOMMIT FALSE;
There are not REQUIRES_NEW in the code.
Why is the rollback not working in the test?
Cheers
Prabin
I have faced the same problem but I have finally solved it albeit I don't use Hibernate (shouldn't really matter).
The key item making it work was to extend the proper Spring unit test class, i.e. AbstractTransactionalJUnit4SpringContextTests. Note the "Transactional" in the class name. Thus the skeleton of a working transactional unit test class looks like:
#ContextConfiguration(locations = {"classpath:/com/.../testContext.xml"})
public class Test extends AbstractTransactionalJUnit4SpringContextTests {
#Test
#Transactional
public void test() {
}
}
The associated XML context file has the following items contained:
<jdbc:embedded-database id="dataSource" type="H2" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
Using this setup the modifications by each test method is properly rolled back.
Regards, Ola
I'm experiencing similar problems, I'm also using TestNG + Spring test support and Hibernate. What happens is that Hibernate disables autocommit on the connection before the transaction begins and it remembers the original autocommit setting:
org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:
#Override
protected void doBegin() {
try {
if ( managedConnection != null ) {
throw new TransactionException( "Already have an associated managed connection" );
}
managedConnection = transactionCoordinator().getJdbcCoordinator().getLogicalConnection().getConnection();
wasInitiallyAutoCommit = managedConnection.getAutoCommit();
LOG.debugv( "initial autocommit status: {0}", wasInitiallyAutoCommit );
if ( wasInitiallyAutoCommit ) {
LOG.debug( "disabling autocommit" );
managedConnection.setAutoCommit( false );
}
}
catch( SQLException e ) {
throw new TransactionException( "JDBC begin transaction failed: ", e );
}
isDriver = transactionCoordinator().takeOwnership();
}
Later on, after rolling back the transaction, it will release the connection. Doing so hibernate will also restore the original autocommit setting on the connection (so that others who might be handed out the same connection start with the original setting). However, setting the autocommit during a transaction triggers an explicit commit, see JavaDoc
In the code below you can see this happening. The rollback is issued and finally the connection is released in releaseManagedConnection. Here the autocommit will be re-set which triggers a commit:
org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:
#Override
protected void doRollback() throws TransactionException {
try {
managedConnection.rollback();
LOG.debug( "rolled JDBC Connection" );
}
catch( SQLException e ) {
throw new TransactionException( "unable to rollback against JDBC connection", e );
}
finally {
releaseManagedConnection();
}
}
private void releaseManagedConnection() {
try {
if ( wasInitiallyAutoCommit ) {
LOG.debug( "re-enabling autocommit" );
managedConnection.setAutoCommit( true );
}
managedConnection = null;
}
catch ( Exception e ) {
LOG.debug( "Could not toggle autocommit", e );
}
}
This should not be a problem normally, because afaik the transaction should have ended after the rollback. But even more, if I issue a commit after a rollback it should not be committing any changes if there were no changes between the rollback and the commit, from the javadoc on commit:
Makes all changes made since the previous commit/rollback permanent
and releases any database locks currently held by this Connection
object. This method should be used only when auto-commit mode has been
disabled.
In this case there were no changes between rollback and commit, since the commit (triggered indirectly by re-setting autocommit) happens only a few statements later.
A work around seems to be to disable autocommit. This will avoid restoring autocommit (since it was not enabled in the first place) and thus prevent the commit from happening. You can do this by manipulating the id for the embedded datasource bean. The id is not only used for the identification of the datasource, but also for the databasename:
<jdbc:embedded-database id="dataSource;AUTOCOMMIT=OFF" type="H2"/>
This will create a database with name "dataSource". The extra parameter will be interpreted by H2. Spring will also create a bean with name "dataSource;AUTOCOMMIT=OFF"". If you depend on the bean names for injection, you can create an alias to make it cleaner:
<alias name="dataSource;AUTOCOMMIT=OFF" alias="dataSource"/>
(there isn't a cleaner way to manipulate the embedded-database namespace config, I wish Spring team would have made this a bit better configurable)
Note: disabling the autocommit via the script (<jdbc:script location="...") might not work, since there is no guarantee that the same connection will be re-used for your test.
Note: this is not a real fix but merely a workaround. There is still something wrong that cause the data to be committed after a rollback occured.
----EDIT----
After searching I found out the real problem. If you are using HibernateTransactionManager (as I was doing) and you use your database via the SessionFactory (Hibernate) and directly via the DataSource (plain JDBC), you should pass both the SessionFactory and the DataSource to the HibernateTransactionManager. From the Javadoc:
Note: To be able to register a DataSource's Connection for plain JDBC code, this instance >needs to be aware of the DataSource (setDataSource(javax.sql.DataSource)). The given >DataSource should obviously match the one used by the given SessionFactory.
So eventually I did this:
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
<property name="dataSource" ref="dataSource" />
</bean>
And everything worked for me.
Note: the same goes for JpaTransactionManager! If you both use the EntityManager and perform raw JDBC access using the DataSource, you should supply the DataSource separately next to he EMF. Also don't forget to use DataSourecUtils to obtain a connection (or JDBCTemplate which uses DataSourceUtils internally to obtain the connection)
----EDIT----
Aight, while the above did solve my problem, it is not the real cause after all :)
In normal cases when using Spring's LocalSessionFactoryBean, setting the datasource will have no effect since it's done for you.
If the SessionFactory was configured with LocalDataSourceConnectionProvider, i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource", the DataSource will be auto-detected: You can still explicitly specify the DataSource, but you don't need to in this case.
In my case the problem was that we created a caching factory bean that extended LocalSessionFactoryBean. We only use this during testing to avoid booting the SessionFactory multiple times. As told before, Spring test support does boot multiple application contexts if the resource key is different. This caching mechanism mitigates the overhead completely and ensures only 1 SF is loaded.
This means that the same SessionFactory is returned for different booted application contexts. Also, the datasource passed to the SF will be the datasource from the first context that booted the SF. This is all fine, but the DataSource itself is a new "object" for each new application context. This creates a discrepancy:
The transaction is started by the HibernateTransactionManager. The datasource used for transaction synchronization is obtained from the SessionFactory (so again: the cached SessionFactory with the DataSource instance from the application context the SessionFactory was initially loaded from). When using the DataSource in your test (or production code) directly, you'll be using the instance belonging to the app context active at that point. This instance does not match the instance used for the transaction synchronization (extracted from the SF). This result into problems as the connection obtained will not be properly participating in the transaction.
By explicitly setting the datasource on the transactionmanager this appeared to be solved since the post initialization will not obtain the datasource from the SF but use the injected one instead. The appropriate way for me was to adjust the caching mechanism and replace the datasource in the cached SF with the one from the current appcontext each time the SF was returned from cache.
Conclusion: you can ignore my post:) as long as you're using HibernateTransactionManager or JtaTransactionManager in combination with some kind of Spring support factory bean for the SF or EM you should be fine, even when mixing vanilla JDBC with Hibernate. In the latter case don't forget to obtain connections via DataSourceUtils (or use JDBCTemplate).
Try this:
remove the org.springframework.jdbc.datasource.DataSourceTransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
and replace it with the org.springframework.orm.jpa.JpaTransactionManager
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
or you inject an EntityManagerFactory instead ...
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
you need an EntityManagerFactory then, like the following
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
</bean>
</property>
</bean>
You haven't shown all the pieces to the puzzle. My guess at this point would be that your ApplicationSubmissionInfoDao is transactional and is committing on its own, though I'd think that would conflict with the test transactions if everything were configured properly. To get more of an answer, ask a more complete question. The best thing would be to post an SSCCE.
Thanks Ryan
The test code is something like this.
#Test
#Rollback(true)
public void shouldHave5ApplicationSubmissionInfo() {
for (int i = 0; i < 5; i++) {
hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
.with(NOT_PROCESSED)
.build());
}
assertThat(repo.retrieveAll(), hasSize(5));
}
#Test
#Rollback(true)
public void shouldHave5ApplicationSubmissionInfoAgainButHas10() {
for (int i = 0; i < 5; i++) {
hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
.with(NOT_PROCESSED)
.build());
}
assertThat(repo.retrieveAll(), hasSize(5));
}
I figure out that embedded DB define using jdbc:embedded-database don't have transaction support. When I used commons DBCP to define the datasource and set default auto commit to false, it worked.
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" scope="singleton">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/snappDb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="defaultAutoCommit" value="false" />
<property name="connectionInitSqls" value=""/>
</bean>
None of the above worked for me!
However, the stack i am using is [spring-test 4.2.6.RELEASE, spring-core 4.2.6.RELEASE, spring-data 1.10.1.RELEASE]
The thing is, using any unit test class annotated with [SpringJUnit4ClassRunner.class] will result in an auto-rollback functionality by spring library design
check ***org.springframework.test.context.transaction.TransactionalTestExecutionListener*** >> ***isDefaultRollback***
To overcome this behavior just annotate the unit test class with
#Rollback(value = false)

Spring 3.1 Hibernate 4 not rolling back JDBC transaction

I am having trouble setting up a JUnit test that will rollback. I've read a few of these posts here on SO before deciding to write a question, as none seems to be the answer to my problem.
The second test case is failing because data is inserted and persisted in DB from the first test case.
Here's the JUnit test:
package com.company.group.spring.dao;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import com.company.group.spring.model.BusObj;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"classpath:NAME_OF_CONFIG_FILE.xml"})
#Transactional
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
#TestExecutionListeners({TransactionalTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class})
public class PerPartsWCDAOTest {
#Autowired
#Qualifier("myBusObjDAO")
private BusObjDAO busObj_dao;
#Transactional
#Test
public void testInsertRollback() {
try {
BusObj busObj = new BusObj("some data...");
assertNotNull(busObj_dao);
assertNotNull(busObj);
busObj_dao.insert(perPartWC);
}
catch (NullPointerException e) {
e.printStackTrace();
}
}
#Transactional
#Rollback(false)
#Test
public void testInsert() {
BusObj busObj2 = new BusObj("some data...");
busObj_dao.insert(perPartWC2);
}
}
Here's the DAOImpl class (the class I'm trying to test):
package com.company.group.spring.dao;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.company.group.spring.model.BusObj;
#Repository("myBusObjDAO")
public class myBusObjDAOImpl implements myBusObjDAO {
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
#Override
public void insert(BusObj someObj) {
String sql = "insert into TABLE (args...) " +
"VALUES (:named_params....)";
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(someObj);
jdbcTemplate.update(sql, paramSource);
}
}
Part of the relevant config:
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#SERVER:PORT:DB"/>
<property name="username" value="USER"/>
<property name="password" value="XXXXXX"/>
</bean>
Edit: +debug print
2012-12-18 08:50:42,177 [main] DEBUG [org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction()] Creating new transaction with name [com.company.group.spring.service.MyClassService.create]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2012-12-18 08:50:42,342 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] for Hibernate transaction
2012-12-18 08:50:42,344 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2012-12-18 08:50:42,361 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Exposing Hibernate transaction as JDBC transaction [org.hibernate.engine.jdbc.internal.proxy.ConnectionProxyHandler#128647a[valid=true]]
2012-12-18 08:50:42,390 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate.update()] Executing prepared SQL update
2012-12-18 08:50:42,391 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate.execute()] Executing prepared SQL statement [insert into TABLE (args...) VALUES (?, ?, SYSDATE,?, ?, SYSDATE, ?,?, ?)]
2012-12-18 08:50:42,391 [main] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection()] Fetching JDBC Connection from DataSource
2012-12-18 08:50:42,657 [main] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection()] Registering transaction synchronization for JDBC Connection
2012-12-18 08:50:42,671 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement()] SQL update affected 1 rows
2012-12-18 08:50:42,678 [main] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection()] Returning JDBC Connection to DataSource
2012-12-18 08:50:42,688 [main] DEBUG [org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback()] Initiating transaction rollback
2012-12-18 08:50:42,688 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doRollback()] Rolling back Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2012-12-18 08:50:42,693 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doCleanupAfterCompletion()] Closing Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] after transaction
Update:
I noticed in the log, it is trying to rollback a Hibernate transaction (but this is JDBC). Then noticed the class for "txManager" is currently org.springframework.orm.hibernate4.HibernateTransactionManager, so I experimented with org.springframework.jdbc.datasource.DataSourceTransactionManager and voila, rollback worked. So I think my problem is more specifically how I'm configuring to use Hibernate and JDBC... I still don't know what is wrong.
Your JUnit and DAO classes looks fine.
The problem might be that the connection is auto-committing before the transaction is rolled back. Can you change the dataSource bean to include a defaultAutoCommit property
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#SERVER:PORT:DB"/>
<property name="username" value="USER"/>
<property name="password" value="XXXXXX"/>
<property name="defaultAutoCommit" value="false" />
</bean>
If this is not working can you paste your console logs.
The solution to my problem was specifying the dataSource for the HibernateTransactionManager. Here's the documentation on HibernateTransactionManager.
So I guess it was a mistake not posting how I defined the txManager in the original post.
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
Changed to:
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
<property name="dataSource" ref="dataSource" />
</bean>
I also added <property name="dataSource"><ref local="dataSource"/></property> to the SessionFactory definition, but it doesn't seem necessary if I'm only worried about JdbcTemplate. Suspect it's needed though if HQL is intermixed in there.
Here's what the debug print looks like when it is working. The only difference I saw was this line Returning JDBC Connection to DataSource was absent before initiate rollback
2012-12-19 08:14:29,455 [main] DEBUG [org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction()] Creating new transaction with name [com.company.group.spring.service.MyClassService.create]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2012-12-19 08:14:29,512 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] for Hibernate transaction
2012-12-19 08:14:29,514 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2012-12-19 08:14:29,539 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin()] Exposing Hibernate transaction as JDBC transaction [org.hibernate.engine.jdbc.internal.proxy.ConnectionProxyHandler#1289e48[valid=true]]
2012-12-19 08:14:29,558 [main] DEBUG [com.company.group.spring.dao.MyClassDAOImpl.insert()] Session Factory is: org.hibernate.internal.SessionFactoryImpl#1285252
2012-12-19 08:14:29,569 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate.update()] Executing prepared SQL update
2012-12-19 08:14:29,570 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate.execute()] Executing prepared SQL statement [insert into MY_CLASS (COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9) VALUES (?, ?, SYSDATE,?, ?, SYSDATE, ?,?, ?)]
2012-12-19 08:14:29,608 [main] DEBUG [org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement()] SQL update affected 1 rows
2012-12-19 08:14:29,611 [main] DEBUG [org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback()] Initiating transaction rollback
2012-12-19 08:14:29,612 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doRollback()] Rolling back Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
2012-12-19 08:14:29,680 [main] DEBUG [org.springframework.orm.hibernate4.HibernateTransactionManager.doCleanupAfterCompletion()] Closing Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] after transaction

Resources