1. Spring MVC application-context.xml
<tx:annotation-driven/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
<property name="url" value="jdbc:mysql://localhost:13306/test"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2. Service impl class
#Override
#Transactional
public void deleteCommentAndFiles(int commentId) {
int deletedCommentCount = commentDAO.deleteComment(commentId);
int deletedFileCount = fileDAO.deleteFiles(commentId);
if (deletedCommentCount != 1) {
throw new IncorrectUpdateSemanticsDataAccessException("Deleted comment not 1 [deletedCount : " + deletedCommentCount);
}
if (deletedFileCount != 1) {
throw new IncorrectUpdateSemanticsDataAccessException("Deleted file not 1 [deletedCount : " + deletedCommentCount);
}
}
3. Test Case
#Test
public void rollbackT() {
boolean hasException = false;
int sizeBeforDelete = commentDAO.selectCountByArticle(1);
try {
commentService.deleteCommentAndFiles(1);
} catch (RuntimeException e) {
hasException = true;
}
Assert.assertTrue(hasException);
Assert.assertEquals(sizeBeforDelete, commentDAO.selectCountByArticle(1));
}
in Test case
first Assert.assertTrue(hasException); is passed but
Assert.assertEquals(sizeBeforDelete, commentDAO.selectCountByArticle(1)) this case fail Expected : 1 but Actual : 0
this second TC fail mean Exception is occur but doesn't rollback delete comment
deleteCommentAndFiles method throw exception but doesn't rollback
Im trying to use #Transactional(propagation=Propagation.REQUIRED, rollbackFor={IncorrectUpdateSemanticsDataAccessException.class})
but same doesn't work
why #Transactional annotaion doesn't work?
I've also had the same issue. I moved the #transactional to the controller in order to works.
#EnableTransactionManagement and only looks for #Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for #Transactional beans in your controllers, and not your services. See Section 21.2, “The DispatcherServlet” for more information.
The database has to support transactions. In case of MySQL you need to create a table with type of InnoDB, BDB or Genini.
Per default myisam does not support transactions.
I didn't see your main test class, but i assume you use the default spring configuration for junit :
By default, a test is launched on his own implicit transaction. when you call your service, it start a "sub" logical transaction (which is a part of the test transaction, because you didn't use require_new for the propagation). when this transaction failed, the main transaction is marked for rollback, but the rollback is not done until the main transaction has finished, when the test end
If you want to test a rollback, you shouldn't use this implicit transaction, launched by the test framework, or you can use TestTransaction.end() before your assertions to force this transaction to be committed or rollback.
Related
I am new to spring and working on a sample program using Spring jdbc. this is to check how spring #Trsactional working and rolling back the changes to the Db if there is an exception.
But I am not able to achieve this. Through I am raising an exception in one of the DB update, still it's inserting the data to DB and not rolling back.
I know somewhere I am making mistake but not able to figure it out. Not sure if this is a correct approach.
What I am doing :-
in main methis I am calling load methos of Global class (which has jdbcTemplate as satic member as I will this jdbcTemplate to all other classes)
Global class load methos will initiate the bean using ApplicationContext.
Creating Dbclass instance in main method and sending the jdbcTemplate as parameter.
4.creating some sample data and calling executeDb method.
5.execute DB method will create the instance of other Dbclasss and setting the jdbcTemplate which earlier I initialized using bean in main method (I have separate class for each operation - like createuser, UpdataBalance etc)
then it will call the db opration method to insert data (I am using batchupdate)
EDIT - Removed all try-catch
DB opration code:-
#Transactional(rollbackFor={Exception.class})
public void executeDB(int count) throws Exception
{
CreateAccount newacc = new CreateAccount(jdbcTemplate);
CreateUser newusr = new CreateUser(jdbcTemplate);
//BalanceUpdate newbal = new BalanceUpdate(jdbcTemplate);
newacc.addList(acclist);
newusr.addToList(usrlist);
//newbal.addList(ballist);
newusr.execute(); // insert data to db
newacc.addAccount(); // insert data to db
//newbal.addBalance(); // insert data to db
newacc.getAccList().clear();
newusr.getUserList().clear();
//newbal.getBalanceList().clear();
if(count == 5000)
{
Thread.sleep(1000);
throw new Exception("Rollback");
}
count += 1000;
//throw new Exception();
}
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-xml -->
<context:component-scan base-package="com.example"></context:component-scan>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#localhost:1521:xe"/>
<property name="username" value="system"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="startit" class="com.example.springtransaction.GlobalClass">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="dbupdate" class="com.example.springtransaction.DbUpdate">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
You need to throw exception from your method not silently log it in catch block.
And for checked exceptions you need to use #Transactional(rollbackFor = {Exception.class}).
http://www.logicbig.com/tutorials/spring-framework/spring-data-access-with-jdbc/transactional-roll-back/
https://www.catalysts.cc/en/wissenswertes/spring-transactional-rollback-on-checked-exceptions/
You should remove the try - catch and define that the method throws an Exception. Something like that
#Transactional(rollbackFor={Exception.class})
public void executeDB() throws Exception
{
if(usrlist.size() >= 5)
{
CreateAccount newacc = new CreateAccount(jdbcTemplate);
CreateUser newusr = new CreateUser(jdbcTemplate);
BalanceUpdate newbal = new BalanceUpdate(jdbcTemplate);
newacc.addList(acclist);
newusr.addToList(usrlist);
newbal.addList(ballist);
newusr.execute(); // insert data to db
newacc.addAccount(); // insert data to db
newbal.addBalance(); // insert data to db - raise exception here
}
}
Update
The class that contain the executeDB() method should be a #Component and inject that component in the main class.
Not create a new Dbclass() instance by your own.
In high-level the reason is that the Spring creates proxy classes upon injection for classes that declare #Transactional.
You could read more about Aspect-Oriented Programming here.
Assuming I have the next code:
#Autowired
private IManager1 manager1;
#Autowired
private IManager2 manager2;
#Autowired
private IManager3 manager3;
#Transactional
public void run() {
manager1.doStuff();
manager2.registerStuffDone();
manager3.doStuff();
manager2.registerStuffDone();
manager1.doMoreStuff();
manager2.registerStuffDone();
}
If any exception is launched I want to rollback everything done by the "doStuff()" methods, but I don't want to rollback the data recorded by the "registerStuffDone()" method.
I've been reading the propagation options for #Transactional annotation, but I don't understand how to use them properly.
Every manager internally uses hiberante to commit the changes:
#Autowired
private IManager1Dao manager1Dao;
#Transactional
public void doStuff() {
manager1Dao.doStuff();
}
Where the dao looks like this:
#PersistenceContext
protected EntityManager entityManager;
public void doStuff() {
MyObject whatever = doThings();
entityManager.merge(whatever);
}
This is my applicationContext configuration:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSourcePool" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
</bean>
<bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<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"/>
Ideas?
You need 2 transactions, one for the stuff to be committed and one for the stuff to be rolled back.
#Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor={Exception1.class, Exception2.class})
public void registerStuffDone()() {
//code
}
Your run method will then use the first transaction and that will be rolled back, but the registerStuffDone method will start a second transaction which will be commited.
You are using declarative transaction and want to control like program sense. For this reason, you need more practice and deep understanding about Spring transaction definition such as PROPAGATION, ISOLATION etc...
Programmatic transaction management: This means that you have manage the transaction with the help of programming. That gives you extreme flexibility, but it is difficult to maintain.
VsDeclarative transaction management: This means you separate transaction management from the business code. You only use annotations or XML based configuration to manage the transactions.
Perhaps, alternative way for your questions by Programmatic transaction management.
/** DataSourceTransactionManager */
#Autowired
private PlatformTransactionManager txManager;
public void run() {
try {
// Start a manual transaction.
TransactionStatus status = getTransactionStatus();
manager1.doStuff();
manager2.registerStuffDone();
manager3.doStuff();
manager2.registerStuffDone();
manager1.doMoreStuff();
manager2.registerStuffDone();
//your condition
txManager.commit(status);
//your condition
txManager.rollback(status);
} catch (YourException e) {
}
}
/**
* getTransactionStatus
*
* #return TransactionStatus
*/
private TransactionStatus getTransactionStatus() {
DefaultTransactionDefinition dtd = new DefaultTransactionDefinition();
dtd.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
dtd.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
dtd.setReadOnly(false);
return txManager.getTransaction(dtd);
}
Note: It does not mean you need to always use one approach like Programmatic transaction management. I prefer mixed approach. Please use easy way like Declarative transaction for simple database services, otherwise, just control with Programmatic transaction in your services will save your logic easily.
I just have a new project to maintain, and using Hibernate+Spring. I wrote a DeliveryInfoServiceImpl which have a method to query for some entity but not have any update or save operation, but I have an error:
Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
Unless I add #Transactional on the method or the class.
My questions are :
Why I have to add #Transactional though I am only executing select query?
Does adding #Transactional means enable transaction support ,which may have more unnecessary overhead when I am only using "select" query.
Below is my code snippet:
#Override
public List<UnavailableRestaurantBean> getUnavailableRestaurantBean(String custAddress, List<Long> dishesId) {
List<Dish> dishes = getDishByIds(dishesId);//exception here
....
}
private List<Dish> getDishByIds(List<Long> ids){
return deliveryInfoDao.findByIds(Dish.class,ids);
}
And I have below transaction manager config:
<tx:annotation-driven transaction-manager="myTxManager" />
<bean id="myTxManager" name="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory">
</property>
</bean>
I am using Spring 3.2.2 version,
I have a issue when I am using #Transactional with jdbc template,
My XML configuration is below:
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />
<bean id="reportTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="reportDataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" order="0"/>
<tx:annotation-driven transaction-manager="reportTransactionManager" order="1"/>
Code :
#Transactional(value = "reportTransactionManager", rollbackFor = Exception.class)
public ReportResponse deleteAndSave(input) throws ReportServiceException {
service1.saveData(input);
service2.saveData(input);
service3.saveData(input);
service4.saveData(input);
service5.saveData(input);
service6.saveData(input);
service7.saveData(input);
} catch (Exception exception) {
_LOGGER.debug("Caught exception all KPI changes should be reverted back for scenario id {}", scenarioId);
_LOGGER.error(ExceptionUtils.getFullStackTrace(exception));
throw new ReportServiceException(exception.getMessage(), exception);
}
return reportResponse;
}
So issues is whenever any service fails everthing should roll back but in my case if suppose service7 failed all services are roll backed except one service i.e service4 only,
I thought may be transaction is not propagating to service4 api, then I tested using
TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.isSynchronizationActive();
TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.isActualTransactionActive();
Its returning me the transaction and its details properly, but still not rollbacking
I surprised that why only that service api is failed to rollback , Can any one please suggest me on this.
Note : Apart from deleteAndSave api in root method, no other services/ repository classes does not have any #transaction becoz by default spring will propagate.
I dont have anyissue with hibernate transactionManager bcoz these services involved with reportTransactionManager manager only.
I am using Spring and Hibernate in my application and using Spring Transaction.
So I have a service layer with annotation #Transaction on methods and DAO layer having methods for database query.
#Transactional(readOnly = false)
public void get(){
}
The issue is when I want to save an object in the database,then I have to use session.flush() at the end of DAO layer method. Why?
I think if I have annotated #Transaction, then Spring should automatically commit the transaction on completion of the service method.
DAO layer :
public BaseEntity saveEntity(BaseEntity entity) throws Exception {
try {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(entity);
session.flush();
} catch (HibernateException he) {
throw new Exception("Failed to save entity " + entity);
}
return entity;
}
Service layer :
#Transactional(readOnly = false)
public BaseEntity saveEntity(BaseEntity entity) throws Exception {
return dao.saveEntity(entity);
}
spring config :
<context:property-placeholder properties-ref="deployProperties" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activate Spring Data JPA repository support -->
<jpa:repositories base-package="com" />
<!-- Declare a datasource that has pooling capabilities-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${app.jdbc.driverClassName}"
p:jdbcUrl="${app.jdbc.url}"
p:user="${app.jdbc.username}"
p:password="${app.jdbc.password}"
p:acquireIncrement="5"
p:idleConnectionTestPeriod="60"
p:maxPoolSize="100"
p:maxStatements="50"
p:minPoolSize="10" />
<!-- Declare a JPA entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceXmlLocation="classpath*:META-INF/persistence.xml"
p:persistenceUnitName="hibernatePersistenceUnit"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="hibernateVendor"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource" p:configLocation="${hibernate.config}"
p:packagesToScan="com" />
<!-- Specify our ORM vendor -->
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:showSql="false"/>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
Yes, if you have #Transactional for your DAO method then you need not flush the session manually, hibernate will take care of flushing the session as part of committing the transaction if the operations in the method are successful.
Check this link to know on how #Transactional works - Spring - #Transactional - What happens in background?
By default, hibernate stacks its queries so they can be optimized when they are finally executed onto the database.
The whole point of flush is to flush this stack and execute it in your transaction onto the database. Your leaving the "safe" house of the JVM and execute your query on a big strange database.
This is why you can't select something you've just saved without a flush. It's simply not in the database yet.
The meaning of commit is to end the transaction and make changes of the database visible for others. Once commit has been executed there's no return possible anymore.
Frankly I'm not exactly sure if it is a best practice but for normal CRUD operations you should be able to add flush into your DAO layer.
This way you don't need to worry about it into the service layer.
If you want java to optimize your transaction then you'll have to add it into your service layer. But remember that you don't need to solve performance issues when there aren't any! Flushes all over your code into the service layer is not good for the code readability. Keep it simple and stupid ;)