Spring Hibernate Transaction Rollback Not Working - java

I am calling 2 different dao method from service layer.
The second method throw Null Pointer Exception (Column 'address' cannot be null), but the task done from first method couldn't rollback.
Service Layer
package com.yetistep.dboy.business.impl;
#Service
#Transactional
public class TestServiceImpl implements TestService {
#Autowired
TestDaoService testDao;
#Autowired
CustomerDaoService customerService;
#Override
#Transactional //I supposed propagation control from aop config
public Boolean saveMulTransactions(User user, Customer customer) throws Exception {
testDao.saveUser(user);
customerService.saveCustomer(customer);
return true;
}
}
//Dao Layer
public class TestDaoServiceImpl implements TestDaoService {
#Autowired
SessionFactory sessionFactory;
Session session = null;
Transaction tx = null;
#Override
public boolean saveUser(User user) throws Exception {
session = sessionFactory.openSession();
tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();
return false;
}
}
public class CustomerDaoServiceImpl implements CustomerDaoService{
#Autowired
SessionFactory sessionFactory;
Session session = null;
Transaction tx = null;
#Override
public boolean saveCustomer(Customer customer) throws Exception {
session = sessionFactory.openSession();
tx = session.beginTransaction();
session.save(customer);
tx.commit();
session.close();
return false;
}
}
//Controller
public #ResponseBody
ServiceResponse createAdmin(#RequestBody User user) throws Exception{
log.debug("Saving User to Database");
ServiceResponse serviceResponse = null;
try {
Customer customer = new Customer();
customer.setName("Jeka");
customer.setContact("87897898788978979");
customer.setContact("Ktm");
testService.saveMulTransactions(user, customer);
serviceResponse = new ServiceResponse("User Added Successfully!!!");
} catch (Exception e) {
e.printStackTrace();
}
return serviceResponse;
}
/Transaction Configuration in xml
//data source and hiibernate config here.....
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- MUST have transaction manager, using aop and aspects -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" propagation="REQUIRED" rollback-for="Throwable"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="userServicePointCut"
expression="execution(* com.yetistep.dboy.business.impl.*ServiceImpl.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="userServicePointCut" />
</aop:config>
I supposed that, If any method of impl package throws error then rollback should be performed
What is the problem please suggest me by answering?

And why should it roll back... You are opening new sessions yourself and managing transactions yourself also. How should Spring know that.
Your daos are wrong in multiple ways.
Never store the session/transaction in a instance variable
Never use openSession when using Spring to manage your transactions
Never manage your own transactions if you want container managed transactions.
So in short fix your daos.
public class CustomerDaoServiceImpl implements CustomerDaoService{
#Autowired
SessionFactory sessionFactory;
#Override
public boolean saveCustomer(Customer customer) throws Exception {
Session session = sessionFactory.getCurrentSession();
session.save(customer);
return false;
}
}
Is all you need (of course that goes for all your daos).
In addition your config is also wrong, you only need the <tx:annotation-driven /> you don't need the <tx:advice /> and <aop:config /> remove those.
All of this is explained in the reference guide.

Related

Spring and hibernate: query.list() does not get executed more than twice

I have a class in java with two more methods similar to the one here.
public Object noOfEmployees() {
List<Employee> emp = null;
String u = user.getUserName();
if ("user1".equals(u)) {
Query query = getHibernateTemplate().getSessionFactory().openSession()
.createSQLQuery("select * from employee where job='System Analyst'")
.addEntity(EMPLOYEE.class);
emp = query.list();
getHibernateTemplate().getSessionFactory().openSession().close();
} else if ("user2".equals(u)) {
Query query = getHibernateTemplate().getSessionFactory().openSession()
.createSQLQuery("select * from employee where job='DBA'")
.addEntity(EMPLOYEE.class);
emp = query.list();
getHibernateTemplate().getSessionFactory().openSession().close();
}
return emp.size();
}
When I ran the application, this is how I got the output:
Logged in as 'user2'
Hibernate: select * from employee where job='DBA'
Hibernate: select * from employee where job='DBA' and rank='2'
Hibernate: select * from employee where present='yes'
Logged in as 'user1'
Hibernate: select * from employee where job='System Analyst'
Hibernate: select * from employee where job='System Analyst' and rank='3'
Hibernate: select * from employee where present='yes'
Again, logged in as 'user2', first two methods get executed.
Hibernate: select * from employee where job='DBA'
Hibernate: select * from employee where job='DBA' and rank='2'
When I logged in as any user, this time even the first method did not get executed.
I have noticed that the code gets stuck when query.list() is encountered. I know that using hibernateTemplate is not recommended but the entire application is written using it. I have only started to learn about spring and hibernate. I will start making changes once I get comfortable with those.
Any suggestions related to the performance of query.list() and ways to improve the code are more than welcome.
Thank you!
Your code is flawed on many levels... For starters you shouldn't be using HibernateTemplate unless you are in a very old application, else use a plain SessionFactory. See http://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html#orm-hibernate-straight for more information.
Second when using Spring use Spring to manage your resources, i.e. Session in this case, or if you want to do it yourself at least manage it properly!.
So in short use a SessionFactory and use getCurrentSession instead of openSession. And use a proper query.
#Autowired
private SessionFactory sf;
#Transactional
public Long noOfEmployees() {
final String query = "select count(*) from employee where job=:job";
Query q = sf.getCurrentSession().createSQLQuery(query);
if ("user1".equals(u)) {
q.setParameter("job", "System Analyst");
} else if ("user2".equals(u) ) {
q.setParameter("job", "DBA");
}
return (Long) query.uniqueResult();
}
The #Transactional will make spring manage the resources, assuming you have <tx:annotation-driven /> in your context and properly added the HibernateTransactionManager.
The problem, obviously, with this code
Query query = getHibernateTemplate().getSessionFactory().openSession().
...
getHibernateTemplate().getSessionFactory().openSession().close();
With this getHibernateTemplate().getSessionFactory().openSession().close() you get a new session and close it!
You should use HQL
Query query = session.createQuery("from Employee where job='System Analyst'");
List<Employee> emp = query.list();
And the way you use Hibernate Template is entirely incorrect.
Refer how to use it
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/classic-spring.html
An example
public class ProductDaoImpl {
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public Collection loadProductsByCategory(final String category) throws DataAccessException {
return this.hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) {
Criteria criteria = session.createCriteria(Product.class);
criteria.add(Expression.eq("category", category));
criteria.setMaxResults(6);
return criteria.list();
}
};
}
}
I have tried to change my flawed-on-many-levels code to some extent. All thanks to #M.Deinum and #v.ladynev for their swift responses.
Given below are only snippets where changes to move from HibernateTemplate to SessionFactory are mentioned:
//IndexService
#Transactional
public class IndexService {
User user;
SessionFactory sf;
public IndexService(User user, SessionFactory sf) {
this.user = user;
this.sf = sf;
}
//This method is used to get the number of employees based on users.
public Object noOfEmployees() {
String u = user.getUserName();
final String query = "select count(*) from employee where job=:job";
Query q = sf.getCurrentSession().createSQLQuery(query);
if ("user1".equals(u)) {
q.setParameter("job", "System Analyst");
} else if ("user2".equals(u) ) {
q.setParameter("job", "DBA");
}
return q.uniqueResult();
}
--------------------------------------------------------------------------------------
//Index
#Controller
#Transactional
public class Index {
#Autowired
User user;
#Autowired
SessionFactory sf;
#RequestMapping("/index")
public ModelAndView getIndex() {
System.out.println("user.getUserName() In Index = " + user.getUserName());
ModelAndView modelAndView = new ModelAndView("index");
IndexService indexService = new IndexService(user, sf);
modelAndView.addObject("noOfEmployees", indexService.noOfEmployees());
return modelAndView;
}
}
--------------------------------------------------------------------------------------
//spring-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="basicDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url"
value="jdbc:mysql://localhost/database_name"></property>
<property name="username" value="user"></property>
<property name="password" value="password"></property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="basicDataSource"></property>
<property name="mappingResources" value="myBeans.hbm.xml" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
Issues that I came across while changing from HibernateTemplate to SessionFactory:
1.NullPointerException for SessionFactory
In index class,
#Autowired
SessionFactory sf;
and passed it as an argument.
IndexService indexService = new IndexService(user, sf);
2.No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
I put #Transactional above Index also. It is the controller in my application.
Thank you once again #M.Deinum and #v.ladynev!

TransactionException: Transaction not successfully started

I have following dao:
#Repository("userDao")
public class UserDaoImpl implements UserDao {
#Autowired
private SessionFactory sessionFactory;
#Transactional
public void add(User user) {
Session session = sessionFactory.getCurrentSession();
session.save(user);
session.getTransaction().commit();
}
}
it is invokes from
#Controller
public class HomeController {
#Autowired
private UserDao userDao;
#RequestMapping(value = "/test")
public ModelAndView test() {
User user = new User();
user.setName("34r");
userDao.add(user);
ModelAndView model = new ModelAndView("home");
model.addObject("userList", null);
return model;
}
}
in browser I try to access to this link
And finally I get following stacktrace:
SEVERE: Servlet.service() for servlet [appServlet] in context with path [/SpringMvcHibernateXML] threw exception [Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction; nested exception is org.hibernate.TransactionException: Transaction not successfully started] with root cause
org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:172)
I have following configuration:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
How to fix this problem?
You should not do session.getTransaction().commit(); this, the #transactional will take care of it. Remove it, you should be fine.
where you begin the transaction. i can't see this line
session.beginTrainsaction();
once you begin the transaction then only you can commit and rollback

No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here without using #contoller

I'm newbie to spring framework.I have integrated spring + hibernate to create RESTFUI API web service.
public interface IGenericDao {
long persists(T clazz) throws ResourceFailureException;
List select(T clazz)throws ResourceFailureException;
}
#Repository(value="genericDao")
public class GenericDao implements IGenericDao {
#Autowired
SessionFactory sessionFactory;
#Override
public long persists(T clazz) throws ResourceFailureException {
long generatedID;
generatedID = (Long) getCurrentSession().save(clazz);
getCurrentSession().getTransaction().commit();
return generatedID;
}
#Override
public List select(T clazz) throws ResourceFailureException {
String queryStr = " FROM "+clazz.getClass().getName()+" as table WHERE (table.isDelete is null OR"
+ " table.isDelete = false) ";
return this.select(queryStr);
}
protected final Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
//Implement GenericDAO class
#Repository(value="roleCapabilityDao")
public class RoleCapabilityDAO extends GenericDao{
public final void persistRole(final Role role) throws ResourceFailureException {
persists(role);
}
public final List getRoles(final String whereString)
throws ResourceFailureException {
String queryStr = "FROM Role " + whereString;
return select(queryStr);
}
public final Role getRoleById(final int roleId) throws ResourceFailureException {
String whereString = "WHERE id=" + roleId;
List roles = getRoles(whereString);
if (roles != null && !roles.isEmpty()) {
return roles.get(0);
}
return null;
}
}
//Servlet-context class.
#Transactional
public class HibernateUtility {
private static Logger logger =Logger.getLogger(HibernateUtility.class.getName());
public HibernateUtility() {
}
#Autowired(required=true)
RoleCapabilityDAO roleCapabilityDao;
#Autowired(required=true)
UserDAO userDao;
public void createDefaultUser(final ServletContext context) {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, context);
Users user = new Users();
Role role = new Role();
.....
try {
role = roleCapabilityDao.getRoleByName("SystemAdmin");
}catch (ResourceFailureException re) {
logger.error("Resource not found" +re.getMessage());
}
}
applicationContext.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
<context:annotation-config />
<context:component-scan base-package="com.base" use-default-filters="false"/>
<bean id="propertyConfigurer"
.....
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseurl}" p:username="${jdbc.username}" p:password="${jdbc.password}">
<property name="testOnBorrow" value="true"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>HibernateUtility</servlet-name>
<servlet-class>com.base.hibernate.HibernateUtilityServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
//RESTFUL API calls
#Path("/roles")
#Service
#Transactional
public class RoleCapabilityResource {
public RoleCapabilityResource(){
super();
}
#Autowired
UserDAO userDao;
#Autowired
RoleCapabilityDAO roleCapabilityDao;
private static Logger roleLogger=Logger.getLogger(RoleCapabilityResource.class.getName());
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/getAllRoles")
#Loggable(value = LogLevel.DEBUG)
#CapabilityCode(value = "C_User_R")
public Response getAllRoles(#Context final HttpServletRequest request) {
HttpSession session = request.getSession();
try {
String loggedUser = session.getAttribute("userName").toString();
Users user = userDao.getCurrentUser(loggedUser.trim());
if(user == null){
return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity("Current User not found").build();
}
List<Role> roleList = roleCapabilityDao.getValidRolesForUserRole(user.getRole().getName(), false);
JSONObject allRolesJson = getRoleJSON(roleList);
return Response.status(Response.Status.OK).type(MediaType.APPLICATION_JSON).entity(allRolesJson).build();
} catch (ResourceFailureException re) {
roleLogger.error("Error in resource"+re.getMessage());
return Response.status(Response.Status.BAD_REQUEST).type("text/plain").entity(re.toString()).build();
}
}
}
//HibernateUtilizyServlet
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Hibernate Utility Servlet is invoked only once during JBoss Startup.
*/
#Service
#Transactional
public class HibernateUtilityServlet extends HttpServlet {
/**
* Default serial Version ID.
*/
private static final long serialVersionUID = 1L;
#Override
public void init(ServletConfig config) {
try {
super.init(config);
ServletContext context = getServletContext();
HibernateUtility hibernateUtil = new HibernateUtility();
hibernateUtil.createDefaultUser(context);
} catch (ServletException e) {
e.printStackTrace();
}
}
}
If i run the application it throws below error message
StandardWrapper.Throwable: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63) [:3.1.0.RELEASE]
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:685) [:3.6.0.Final]
at com.base.dao.GenericDao.getCurrentSession(GenericDao.java:186) [:]
at com.base.dao.GenericDao.select(GenericDao.java:80) [:]
at com.base.dao.RoleCapabilityDAO.getRoles(RoleCapabilityDAO.java:29) [:]
at com.base.dao.RoleCapabilityDAO.getRoleByName(RoleCapabilityDAO.java:40) [:]
at com.base.hibernate.HibernateUtility.createDefaultUser(HibernateUtility.java:180) [:]
at com.base.hibernate.HibernateUtilityServlet.init(HibernateUtilityServlet.java:41) [:]
I have tried some links as mentioned below,
1.Spring MVC + Hibernate: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.
2.I am receiving HibernateException "No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here"
but I have not resolved yet.The only difference is here I didn't use #controller annotation.
How to resolve this Error?
How to set sessions handle by spring?
HibernateUtilityServlet is no Spring Bean! It is just some simple Http Servlet created by your Servlet container (not by Spring).
Therefore
your Spring annotations at HibernateUtilityServlet are ignored (they are only taken in account if your Object is an Spring Bean (created by Spring))
you can not inject something in HibernateUtilityServlet
Futuremore: when you create an instance with new (like you did HibernateUtility hibernateUtil = new HibernateUtility();), then this Object will be no Spring Bean. Therefore
(you already know it):
your Spring annotations at this class are ignored
you can not inject something in this object
I don't really know what you want do do with this HibernateUtilityServlet, but it looks like you try to setup the database when the application starts. -- A Much more easyer way would be using the spring default functionality for this:
#Component
public class OnStartupAction implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(final ContextStartedEvent event) {
// do whatever you need here
}
}
More details in this answer of mine.

Alter Session to set Date Format in MyBatis

How can I do this in the same transactional session?
alter session set nls_date_format = 'yyyy/mm/dd hh24:mi:ss'
There are some procedures and insertes that I need to execute this before them.
I've tried to make another method, in the same one, but it still doesn't work.
I have MyBatis integrated with Spring. I don't know if it makes any difference.
Can anyone help me?
Thanks.
SOLUTION:
I'v managed this to work by changing the way Spring and MyBatis integrates through SqlSession.
Here are two possible solutions.
Extend SqlSessionFactory
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.apache.log4j.Logger;
public class CustomSqlSessionFactory extends DefaultSqlSessionFactory
{
private static Logger msLogger = Logger.getLogger(CustomSqlSessionFactory.class);
public CustomSqlSessionFactory(Configuration configuration)
{
super(configuration);
}
#Override
public SqlSession openSession()
{
SqlSession session = super.openSession();
alterSession(session);
return session;
}
protected void alterSession(SqlSession session)
{
try
{
Statement statement = session.getConnection().createStatement();
statement.addBatch("alter session set nls_date_format = 'yyyy/mm/dd hh24:mi:ss'");
statement.addBatch("ALTER SESSION SET NLS_COMP = LINGUISTIC");
statement.addBatch("ALTER SESSION SET NLS_SORT = XTURKISH_AI");
statement.executeBatch();
msLogger.debug("Altered newly created session parameters.");
statement.close();
}
catch (SQLException e)
{
msLogger.error("Alter session failed!", e);
}
}
#Override
public SqlSession openSession(boolean autoCommit)
{
SqlSession session = super.openSession(autoCommit);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(Connection connection)
{
SqlSession session = super.openSession(connection);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(ExecutorType execType)
{
SqlSession session = super.openSession(execType);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit)
{
SqlSession session = super.openSession(execType, autoCommit);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(ExecutorType execType, Connection connection)
{
SqlSession session = super.openSession(execType, connection);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
{
SqlSession session = super.openSession(execType, level);
alterSession(session);
return session;
}
#Override
public SqlSession openSession(TransactionIsolationLevel level)
{
SqlSession session = super.openSession(level);
alterSession(session);
return session;
}
}
If you are using spring also create a CustomSqlSessionFactoryBuilder
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class CustomSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder
{
#Override
public SqlSessionFactory build(Configuration config)
{
return new CustomSqlSessionFactory(config);
}
}
and attach CustomSqlSessionFactoryBuilder by modifying the SqlSessionFactoryBean configuration
<bean id="mySqlSessionFactoryBuilder" class="your.package.CustomSqlSessionFactoryBuilder" />
<bean id="mySessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="sqlSessionFactoryBuilder" ref="mySqlSessionFactoryBuilder" />
</bean>
Session will be altered on each borrow operation. However, if you are using pooled connection, this approach will degrade the execution performance. Hence on every checkout of the connection from the pool, openSession will be called.
If you are using a pooled data source then handling session alter operations on data source level will be much faster. Second solution modifies C3P0 pooled data source for altering session.
Modify Pooled Data Source (C3P0 and Spring)
Create a connection customizer class [1]
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.log4j.Logger;
import com.mchange.v2.c3p0.AbstractConnectionCustomizer;
public class ConnectionCustomizer extends AbstractConnectionCustomizer
{
private static Logger msLogger = Logger.getLogger(ConnectionCustomizer.class);
public void onAcquire(Connection c, String pdsIdt)
{
try
{
Statement statement = c.createStatement();
statement.addBatch("alter session set nls_date_format = 'yyyy/mm/dd hh24:mi:ss'");
statement.addBatch("ALTER SESSION SET NLS_COMP = LINGUISTIC");
statement.addBatch("ALTER SESSION SET NLS_SORT = XTURKISH_AI");
statement.executeBatch();
msLogger.debug("Altered newly created session parameters.");
statement.close();
}
catch (SQLException e)
{
msLogger.error("Alter session failed!", e);
}
}
}
Modify data source configuration bean and add created class as connectionCustomizerClassName
<!--
driverClass : Driver class that will be used to connect to database.
jdbcUrl : jdbc url defining the database connection string.
user : username of the database user.
password : password of the database user.
acquireIncrement : how many connections will be created at a time when there will be a shortage of connections.
idleConnectionTestPeriod : after how much delay a connection will be closed if it is no longer in use.
maxPoolSize : Max number of connections that can be created.
maxStatements : Max number of SQL statements to be executed on a connection.
minPoolSize : Minimum number of connections to be created.
connectionCustomizerClassName : Custom connection customizer to enable session alterations and jobs on acquiring/closing - checking in/out physical connections
-->
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
<property name="jdbcUrl" value="${app.jdbc.url}" />
<property name="user" value="${app.jdbc.username}" />
<property name="password" value="${app.jdbc.password}" />
<property name="acquireIncrement" value="3" />
<property name="maxPoolSize" value="50" />
<property name="maxStatements" value="50" />
<property name="minPoolSize" value="5" />
<property name="idleConnectionTestPeriod" value="60" />
<property name="preferredTestQuery" value="SELECT 1 FROM DUAL" />
<property name="testConnectionOnCheckout" value="true" />
<property name="connectionCustomizerClassName" value="your.package.name.ConnectionCustomizer" />
</bean>
[1]: http://www.mchange.com/projects/c3p0/

Spring doesn't close hibernate session after transaction rollback

I'm trying to catch a ConstraintViolationException in my service layer and rethrowing a user defined checked exception. I'm catching the exception in my controller and adding an error object to my BindingResult. I'm using declarative transaction management I've tried to make my DAO a Repository and added a PersistenceExceptionTranslationPostProcessor to catch a spring translated exception. I've also added a txAdvice to rollback on all throwables. My exception does get caught but I'm getting an error 500 with:
Hibernate: insert into user (email, password, first_name, last_name, userType) values (?, ?, ?, ?, 1)
[acme]: [WARN ] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): SQL Error: 1062, SQLState: 23000
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): Duplicate entry 'admin' for key 'email_unique'
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:processCommit(): Initiating transaction commit
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doCommit(): Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - AssertionFailure:<init>(): HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs)
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doRollbackOnCommitException(): Initiating transaction rollback after commit exception
org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1213)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:402)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:468)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
My Controller:
#RequestMapping (method = RequestMethod.POST)
#Transactional
public String registerAdmin(#Valid #ModelAttribute("user") AdminUser user, BindingResult bindingResult, ModelMap model) {
if (bindingResult.hasErrors()) {
return "admin/admins/form";
}
else if (!user.getPassword().equals(user.getConfirmPassword())) {
bindingResult.addError(new ObjectError("user.confirmPassword", "Passwords don't match"));
return "admin/admins/form";
}
else {
user.setPassword(passwordEncoder.encodePassword(user.getPassword(), null));
try {
userService.save(user);
return "redirect:/admin/admins";
} catch(ApplicationException ce) {
bindingResult.addError(new ObjectError("user.email", "Email already registered"));
return "admin/admins/form";
}
}
}
Part of my Spring config:
<context:component-scan base-package="com.test.dao, com.test.service" />
<context:property-placeholder location="/WEB-INF/spring.properties"/>
<import resource="springapp-security.xml"/>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb?zeroDateTimeBehavior=convertToNull"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingLocations" value="classpath*:com/test/model/hbm/**/*.hbm.xml" />
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
</value>
</property>
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" />
</tx:attributes>
</tx:advice>
Service layer:
public class UserServiceImpl implements UserDetailsService, UserService {
private UserDAO dao;
#Override
public void save(User c) throws ApplicationException {
try {
dao.save(c);
} catch(DataIntegrityViolationException cve) {
throw new ApplicationException("email already registered");
}
}
If I don't catch the runtime exception I don't get the hibernate exception (don't flush the session..)
You may want to remove the transaction annotation from your controller and add it to the service layer.
The service layer would look like below. If your service layer is throwing a checked exception you can add that to your annotation so that the insert is not even attempted to be committed.
public class UserServiceImpl implements UserDetailsService, UserService {
private UserDAO dao;
#Override
#Transactional(rollbackFor=ApplicationException.class)
public void save(User c) throws ApplicationException {
try {
dao.save(c);
} catch(DataIntegrityViolationException cve) {
throw new ApplicationException("email already registered");
}
}
What is happening currently in your code is that the transaction is not being rolled back but has to rollback because it actually tried to commit the data but because a database constraint the transaction had to be rolled back. By forcing the rollback with the #Transactional(rollbackFor=ApplicationException.class) it will not allow the transaction to perform a commit but it will rollback and your app will still add the error to the BindingResult.

Categories