I have a problem with rollback transaction. Below I wrote some of the configuration of beans. I do 2 SQL-queries: delete and update. And when UPDATE generates exception (constraint of foreign ket), the first query (DELETE) does not rollback. Could anyone please tell me where the problem is? I wrote only some of the configuration for sake of clarity, so if it's needed more information please let me know. Thanks in adnvance!
CONTEXT:
I have DAO layer with method removeUser:
public void removeUser(final Long id) {
getHibernateTemplate().execute(new HibernateCallback() {
#Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
executeUpdate("delete from table1 where user_id = ?", session, id);
executeUpdate("update table2 set user_id = null where user_id = ?", session, id);
return null;
}
private void executeUpdate(String queryString, Session session, Long... params) {
SQLQuery query = session.createSQLQuery(queryString);
for (int paramIndex = 0; paramIndex < params.length; paramIndex++) {
Long param = params[paramIndex];
query.setParameter(paramIndex, param);
}
query.executeUpdate();
}
});
}
This method is called from within service:
public void removeUser(Long id) {
userDao.removeUser(id);
}
This service is configured via spring:
<bean name="adminUserService" parent="txProxyServiceTemplate">
... setting properties ...
</bean>
<bean id="txProxyServiceTemplate" abstract="true"
class="com.xalmiento.desknet.ui.server.service.transaction.GWTTransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="remove*">PROPAGATION_NESTED</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="nestedTransactionAllowed" value="true"/>
</bean>
Try with
<prop key="remove*">PROPAGATION_REQUIRED</prop>
I don't think all the database server does support the transaction.
Related
In my spring project i am using hibernate entitymanager. I have done transaction management in the project like below.
applicationContext.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#xxxx:1527:selva" />
<property name="username" value="xxx" />
<property name="password" value="yyy" />
</bean>
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.selva.entity</value>
</list>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="ORACLE" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
TestTranService.java
#Transactional
public String checkTranWork() {
try {
testTranDao.method1();
int i=0;
if(i==0)
throw new ValidationException();
testTranDao.method2();
testTranDao.method3();
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
TestTranDaoImpl.java
public class TestTranDaoImpl implements TestTranDao {
public static final Logger LOGGER = Logger
.getLogger(TestTranDaoImpl.class);
#PersistenceContext
private EntityManager entityManager;
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public void method1() {
try {
String sql = "INSERT INTO CUST(MOBNUM,CUST_NAME) values ('1','dd')";
Query query = entityManager.createNativeQuery(sql);
query.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void method2() {
try {
String sql = "INSERT INTO CUST(MOBNUM,CUST_NAME) values ('1','dd')";
Query query = entityManager.createNativeQuery(sql);
query.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
}
}
}
In my code after method 1 executing the insert statement i am throwing the exception so the first statement should be rolled back.But in my case rollback is not happened.Is their any other configuration required for rollback.
Any help will be greatly appreciated!!!
You Catch the Exceptions to log them, but then, you drop them. Don't do that!
An Exception has a meaning in a software, more than for logging, but also for all the framework around it.
Remove the try catch blocks so the #Transactional Aspect around your method can detect there's an error and rollback your transaction.
As explained by Gillaume for #Transactional to work, the method has to throw an exception then only the framework will know that rollback has to be performed.
One solution already suggested is to remove the try/catch and if there is a need to catch the exception and log in application error log file then make sure the exception is propagated not consumed.
So the updated code would look like:
#Transactional
public String checkTranWork() {
try {
testTranDao.method1();
int i=0;
if(i==0)
throw new ValidationException();
testTranDao.method2();
testTranDao.method3();
} catch(Exception e) {
e.printStackTrace();
throw e;
}
return null;
}
I have a method in one of the DAO class that is inserting records in 2 different Oracle tables. I would like the records to be either inserted in both the table or none.
In order to achieve this I am using both #Transactional and cn.setAutoCommit(false) code snippet.
In order to test it, I intentionally put in a wrong column name in the SQL so that data insertion in the second table fails. The expectation, from me, here is that the data will not get inserted in the first table since the insertion in second table failed because of incorrect query. But that didn't happen for some reason. The record still got inserted in first table and the second table did not have the record inserted.
It looks like the implementation is not incorrect here. Not sure what I am missing here.
EventLogDao.java
#Transactional
public long saveEventData(EventLog eventLog, String userId) throws SQLException {
Connection cn = this.dataSource.getConnection();
cn.setAutoCommit(false);
//(FIRST TABLE INSERTION - Table Name: EVENT_LOG)
//save data in event log table
long eventId = getNextEventIdSequence();
saveEventLogData(eventId, eventLog);
//(SECOND TABLE INSERTION - Table Name: EVENT_LOG_MESSAGE)
//save data in event log message table
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
cn.commit();
return eventId;
}
private void saveEventLogData(long eventId, EventLog eventLog) {
Object[] parameters = {eventId, eventLog.getRouteId(), eventLog.getEventType().getEventTypeId(),
eventLog.getOrderId(), eventLog.getIncomingEventTimestamp(), eventLog.getOutgoingEventTimestamp()};
int[] types = {Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.TIMESTAMP, Types.TIMESTAMP};
int rowsAffected = jdbcTemplate.update(INSERT_EVENT_LOG_SQL2, parameters, types);
System.out.println("rowsAffected (eventlog) = " + rowsAffected);
}
private int saveEventLogMessageData(long eventId, EventLogMessage eventLogMessage) {
Object[] parameters = {eventId, eventLogMessage.getIncomingEventMessage(), eventLogMessage.getOutgoingEventMessage()};
int[] types = {Types.INTEGER, Types.VARCHAR, Types.VARCHAR};
int rowsAffected = jdbcTemplate.update(INSERT_EVENT_LOG_MESSAGE_SQL2, parameters, types);
System.out.println("rowsAffected (eventLogMessage) = " + rowsAffected);
return rowsAffected;
}
applicationContext.xml
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</constructor-arg>
<property name="propagationBehavior">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.PROPAGATION_REQUIRED"/>
</property>
<property name="isolationLevel">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.ISOLATION_READ_COMMITTED"/>
</property>
</bean>
<bean id="eventLogDao" class="com.ebayenterprise.publicapi.events.dao.EventLogDao">
<constructor-arg ref="dataSource" />
</bean>
Please guide.
I suppose that you should use transactionTemplate with your jdbcTemplate together:
public long saveEventData(EventLog eventLog, String userId) throws SQLException {
return transactionTemplate.execute(new TransactionCallback<Long>(){
#Override
public Long doInTransaction(TransactionStatus transactionStatus) {
try {
Connection cn = this.dataSource.getConnection();
cn.setAutoCommit(false);
//(FIRST TABLE INSERTION - Table Name: EVENT_LOG)
//save data in event log table
long eventId = getNextEventIdSequence();
saveEventLogData(eventId, eventLog);
//(SECOND TABLE INSERTION - Table Name: EVENT_LOG_MESSAGE)
//save data in event log message table
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
cn.commit();
return eventId;
} catch (Exception e) {
transactionStatus.setRollbackOnly();
}
return 0;
}
});
You probably has not configuration for JTA and your #Transactional has no effect.
Got it working by using TransactionManager. The changes I make to my code base are mentioned below.
applicationContext.xml (updated)
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg ref="transactionManager"/>
<property name="propagationBehavior">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.PROPAGATION_REQUIRED"/>
</property>
<property name="isolationLevel">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.ISOLATION_READ_COMMITTED"/>
</property>
</bean>
<bean id="eventLogDao" class="com.ebayenterprise.publicapi.events.dao.EventLogDao">
<constructor-arg index="0" ref="dataSource" />
<constructor-arg index="1" ref="transactionManager"/>
</bean>
EventLogDao.java (updated)
public EventLogDao(DataSource dataSource, PlatformTransactionManager transactionManager) {
super(dataSource, transactionManager);
}
public long save(EventLog eventLog, String userId) throws Exception {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
long eventId = 0L;
try {
eventId = getNextEventIdSequence();
System.out.println("eventId = " + eventId);
saveEventLogData(eventId, eventLog);
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
userId = StringUtils.defaultIfBlank(userId, DEFAULT_USER_ID);
saveEventLogAuditData(eventId, userId, eventLog.getOutgoingEventTimestamp());
transactionManager.commit(txStatus);
} catch (TransactionException ex) {
transactionManager.rollback(txStatus);
throw new RuntimeException("Error occurred during tx management in event log tables...", ex);
} finally {
return eventId;
}
}
#Repository
public class Init {
public static void main(String[] args) {
Init init = new Init();
init.addUser(init.getSessionFactory());
}
private SessionFactory getSessionFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "Spring_Hibernate.xml" });
SessionFactory sf = (SessionFactory) context.getBean("sessionFactory");
return sf;
}
#Transactional
private void addUser(SessionFactory sf) {
Session session = sf.getCurrentSession();
User user = new User();
user.setName("123");
session.save(user);
session.close();
sf.close();
}
}
xml:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.warmmer.bean" />
<property name="hibernateProperties">
<!-- <value> hibernate.dialect=org.hibernate.dialect.HSQLDialect </value> -->
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
err:
INFO: Using DataSource [org.apache.commons.dbcp.BasicDataSource#6098b14d] of Hibernate SessionFactory for HibernateTransactionManager
Exception in thread "main" org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
If:
hibernate.current_session_context_class set 'thread'
then :save is not valid without active transaction
what should I do please?
You are not creating your "Init" object within the spring context, so spring would never get a chance to wrap a proxy around the method with the annotation that will manage the transaction
Try changing your class to...
package my.pkg;
// Imports etc
#Repository
public class Init {
#Autowired
private SessionFactory sessionFactory;
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "Spring_Hibernate.xml" });
Init init = context.getBean(Init.class);
init.addUser();
}
#Transactional
private void addUser() {
Session session = sessionFactory.getCurrentSession();
User user = new User();
user.setName("123");
session.save(user);
// session.close(); DON'T NEED THESE!
// sf.close();
}
}
Now you might need to add the following to your beans file so that it finds your repository...
<context:component-scan base-package="my.pkg"/>
In my app I've been passing my SessionFatory in my method parameters when I need to access my database within those methods. It's instantiated in my Controller using:
#Autowired
private SessionFactory sessionFactory;
And when I need to access my database I use lines like this:
sessionFactory.getCurrentSession().save()
// or
List products = sessionFactory.getCurrentSession().createSQLQuery("SELECT * FROM PRODUCTS").list();
Should I be creating new sessionFactories to get my current session or should is it okay to pass it as a parameter?
edit [added for clarity]:
From HomeController.java:
#Controller
public class HomeController {
#Autowired
private SessionFactory sessionFactory;
//Correlations Insert Handler
//DOCUMENT CREATE
#RequestMapping(value="/documents", method = RequestMethod.PUT)
public #ResponseBody String insertDocument(HttpServletRequest request, HttpServletResponse response){
DocumentControl documentControl = new DocumentControl();
documentControl.insertDocument(request, response, sessionFactory);
return "went through just find";
}
//DOCUMENT READ
#RequestMapping(value="/documents", method = RequestMethod.GET)
public #ResponseBody List<Documents> getAllDocuments(){
//List documents = sessionFactory.getCurrentSession().createQuery("from Documents").list();
DocumentControl documentControl = new DocumentControl();
List documents = documentControl.getAllDocuments(sessionFactory);
return documents;
}
From DocumentControl.java:
public class DocumentControl {
private static Logger logger = Logger.getLogger(DocumentControl.class.getName());
public DocumentControl(){};
//CREATE
public void insertDocument(HttpServletRequest request, HttpServletResponse response, SessionFactory sessionFactory){
Documents document = new Documents(request)
sessionFactory.getCurrentSession().save(document);
}
//READ
public List<DocumentReturn> getAllDocuments(SessionFactory sessionFactory){
List documents = sessionFactory.getCurrentSession().createQuery("from Documents").list();
return documents;
}
private boolean ifProductExists (String productCode, SessionFactory sessionFactory) {
boolean exists = false;
List<Products> productList = sessionFactory.getCurrentSession().createCriteria(Products.class).add( Restrictions.eq("productCode", productCode)).list();
if ( !productList.isEmpty() && productList.size() > 0 ) {
exists = true;
}
return exists;
}
private boolean ifStandardExists (String standardCode, SessionFactory sessionFactory) {
boolean exists = false;
List<Standards> standardList = sessionFactory.getCurrentSession().createCriteria(Standards.class).add( Restrictions.eq("standardCode", standardCode)).list();
if ( !standardList.isEmpty() && standardList.size() > 0 ) {
exists = true;
}
return exists;
}
}
hibernate.cfg.xml:
hibernate-configuration>
<session-factory>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">100</property>
<property name="hibernate.c3p0.max_size">10</property>
<property name="hibernate.c3p0.max_statements">10</property>
<property name="hibernate.c3p0.min_size">10</property>
<property name="hibernate.c3p0.timeout">100</property>
<mapping class="***.*****.********.model.Documents"/>
</session-factory>
</hibernate-configuration>
persistence-context.xml:
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<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>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key=" hibernate.use_sql_comments">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<
property name="sessionFactory" ref="sessionFactory" />
</bean>
SessionFactory is a Singleton. Therefore, you should get it from the ApplicationContext (i.e. using the #Autowired annotation). Passing it as a parameter will only complicate your API.
I think that you should leave creation of SessionFactory to the framework you are using and inject it as a resource just as you are doing by now. Besides you are not creating it in your code at all. Good practice is to create separate session for every chunk of data manipulation, if it is not done by a framework ofc. Session should be your main unit of work.
<prop key="hibernate.hbm2ddl.auto">create</prop> creates a new database schema and <prop key="hibernate.hbm2ddl.auto">update</prop> create if it is not exists and update existing database schema. If I want to check whether database schema exists or not and depending on that a database schema will be created, how can I achieve this. Currently the configuration of my applicationContext.xml is:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="annotatedClasses">
<list>
<value>info.ems.models.User</value>
<value>info.ems.models.Role</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
<bean id="dao" class="info.ems.hibernate.HibernateEMSDao" init-method="createSchema">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
And the HibernateEMSDao.java:
public class HibernateEMSDao implements EMSDao {
private final Logger logger = LoggerFactory.getLogger(getClass());
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public void saveUser(User user) {
hibernateTemplate.saveOrUpdate(user);
}
public List<User> listUser() {
return hibernateTemplate.find("from User");
}
public void createSchema() {
logger.info("inserting default admin user into database");
User admin = new User();
admin.setUsername("admin");
admin.setName("Admin");
admin.setEmail("admin");
admin.setPassword("21232f297a57a5a743894a0e4a801fc3");
saveUser(admin);
logger.info("Admin inserted into database");
try {
System.out.println(listUser().get(0).getId());
} catch (Exception e) {
logger.error("===================Error================");
}
}
}
It is working. What configuration will help me to gain this?
Something like:
Check an user with id=1 exists
If not create the schema
Thanks and regards.
You could disable the hibernate.hbm2ddl.auto option, check the conditions (probably using plain JDBC) and call (or don't) the create method of the SchemaExport class. This would be done in your application's initialization code (a ServletContextListener in case you are working with a web app).
An example on how to use the SchemaExport class:
AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(info.ems.models.User.class);
config.addAnnotatedClass(info.ems.models.Role.class);
config.configure();
new SchemaExport(config).create(true, true);