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
Related
Good day!
My problem is when i transferred configuration for another DB from xml to #Bean my transactions is lost.... dont rollback and not work.
I see this when in DB after first insert created row (!), but in this method(transaction) start second insert i take Exception and row after first inset stay on DB!
This xml
<bean name="sqlSessionFactoryYarus" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="/WEB-INF/MapperConfigYarus.xml" />
<property name="dataSource" ref="dataSourceYarus" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="ru.project.crm.mapper_yarus"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryYarus" />
</bean>
<bean id="transactionManagerYarus" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceYarus" />
<qualifier value="yarus"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManagerYarus" />
this code (dataSource there is no this not to waste space)
#Component
#Scope("singleton")
#DependsOn("springApplicationContextHolder")
public class YarusConnectionConfig {
#Bean
public SqlSessionFactory sqlSessionFactoryYarus() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSourceYarus());
sqlSessionFactory.setConfigLocation(new ClassPathResource("../MapperConfigYarus.xml"));
return sqlSessionFactory.getObject();
}
#Bean
public MapperScannerConfigurer yarusMapper() throws Exception {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setSqlSessionFactoryBeanName("sqlSessionFactoryYarus");
msc.setBasePackage("ru.project.crm.mapper_yarus");
return msc;
}
#Bean
public DataSourceTransactionManager transactionManagerYarus() throws Exception {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSourceYarus());
return dataSourceTransactionManager;
}
}
And
All paces when i want to Transactional annotate #Transactional(value = "transactionManagerYarus")
And if i build project with xml Transactional works fine
BUT if build with #Bean its dont work...
Plesae Help me!
I use
1) Spring 4.3
2) MyBatis
3) Postgesql
4) Java 8
Also we find solution.
Problem was in dataSource
#Bean(destroyMethod = "close", name = "dataSourceYarus")
public ComboPooledDataSource dataSourceYarus() {
ComboPooledDataSource cpds = new ComboPooledDataSource();
//config connection
}
It is my bean, and i call this bean like method, for example
new DataSourceTransactionManager(dataSourceYarus());
I did not attach any importance to this, because in all example this is true.
BUT in xml configuration caused this like "Bean" on his name, i replace call in java-config to
new DataSourceTransactionManager(context.getBean("dataSourceYarus"))
and.... this work for me!
Because if call this method, every time creating new pool then transaction was over
I have a Java web application running on Tomcat 7 - jdk1.7
This application uses Spring 4.1.5.RELEASE and Hibernate 4.2.2.Final
My problem is a OutOfMemoryException of the Heap space on building section factory
This is my static method that opens a SessionFactory
public class GenericDAO {
public static SessionFactory sessionFactory = null;
public static ServiceRegistry serviceRegistry = null;
Transaction tx = null;
public static SessionFactory createSessionFactory() {
Configuration configuration = new Configuration();
configuration.configure();
serviceRegistry = new ServiceRegistryBuilder().applySettings(
configuration.getProperties()). buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
return sessionFactory;
}
}
And this is an example of DAO
public class SpecificDAO extends GenericDAO {
public int save(MyObject item) {
Session session = createSessionFactory().openSession();
try {
tx = session.beginTransaction();
session.save(item);
tx.commit();
return item.getId();
} catch (HibernateException e) {
if (tx != null) tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return -1;
}
}
The error occurs at the line containing
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
The problem doesn't occur immediately at the deploy, but after 2 o 3 days of usage
This is my Hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:sqlserver://192.168.XX.XXX:1433;databaseName=DatabaseName</property>
<property name="connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
<property name="connection.username">username</property>
<property name="connection.password">password</property>
<mapping class="it.company.client.project.hibernate.MyObject"/>
<!-- DB schema will be updated if needed -->
<!-- <property name="hbm2ddl.auto">update</property> -->
</session-factory>
</hibernate-configuration>
You have to create the session factory only once as it is a heavy weight object, refer to the hibernate documentation for its details.
Here is the sample code from the doc on how it should be created:
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory(
new StandardServiceRegistryBuilder().build() );
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
It is better idea to flush and clear the session after used, you can use both
session.flush();
session.clear();
for more information link1 and link2
You are creating a SessionFactory object for every save() call.
i.e you are creating a new SessionFactory repeatedly for every save() call but not closing the existing SessionFactory objects in memory.
How many times save() is called ? the same no of SessionFactory will be in memory, which causes the memory leak.
SessionFactory are heavy weight objects, so you'd create at application initialization. You can create a SingleTon to instantiate SessionFactory.
Avoid instantiation of SessionFactory object on every DAO action. It is very slow and causes memory leaks. Better explained in this answer
If you're using Spring anyway, better to delegate to Spring work with SessionFactory, transactions and handling SQL exceptions. For example, your save() method will reduce to one line of code sessionFactory.getCurrentSession().save(item); Manual transaction open/commit/rollback should be replaced with #Transactional attribute. Also, usually better place transactions on whole service method, not on every single DAO method, but it depends of business logic.
Here example how to configure spring context for work with Hibernate (just first article for google)
I slightly adopted this example for current question
#Repository
public class SpecificDAO {
private SessionFactory sessionFactory;
#Autowired
public SpecificDAO(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
#Transactional(propagation=Propagation.REQUIRED)
public int save(MyObject item) {
try{
sessionFactory.getCurrentSession().save(item);
}catch (HibernateException e) {
return -1;
}
}
}
Spring configuration
<context:annotation-config/>
<context:component-scan base-package="it.company.client.project"/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="url" value="jdbc:sqlserver://192.168.XX.XXX:1433;databaseName=DatabaseName"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>it.company.client.project.hibernate.MyObject</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
<prop key="hibernate.connection.provider_class">org.hibernate.connection.DatasourceConnectionProvider</prop>
<prop key="hibernate.show_sql">false</prop>
<!--prop key="hibernate.hbm2ddl.auto">update</prop-->
</props>
</property>
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
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.
I am using spring 3.2 with hibernate 4. I want to use spring to control the transactions.
However with the configuration mentioned below I get the
'Servlet.service() for servlet spring threw exception: org.hibernate.HibernateException: No Session found for current thread'
exception:
<aop:config>
<aop:pointcut id="serviceMethods"
expression="execution(* com.locator.service.impl.ServiceTypeService.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="hbTransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- Hibernate session factory -->
<bean id="hbSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>../spring/model/ServiceType.hbm.xml</value>
</list>
</property>
</bean>
<bean id="hbTransactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="hbSessionFactory" />
</bean>
<bean id="serviceTypeService" class="com.locator.service.impl.ServiceTypeService">
<property name="serviceTypeDao" ref="serviceTypeDao"></property>
</bean>
<bean id="serviceTypeDao" class="com.locator.dao.impl.ServiceTypeDao">
<property name="sessionFactory" ref="hbSessionFactory"></property>
</bean>
The code for the Dao layer and the Service is as follows:
public class ServiceTypeDao implements IServiceTypeDao{
private static final Log log = LogFactory.getLog(ServiceTypeDao.class);
private SessionFactory sessionFactory;
public SessionFactory getSessionFactory(){
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
public ServiceType findById(int id) {
log.debug("getting ServiceType instance with id: " + id);
try {
Session session = getSessionFactory().getCurrentSession();
ServiceType instance = (ServiceType) session.get("com.locator.model.ServiceType", id);
if (instance == null) {
log.debug("get successful, no instance found");
} else {
log.debug("get successful, instance found");
}
instance.setName(instance.getName()+"0");
session.saveOrUpdate(instance);
return instance;
}catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
}
public class ServiceTypeService implements IServiceTypeService{
private ServiceTypeDao serviceTypeDao;
public void setServiceTypeDao(ServiceTypeDao serviceTypeDao){
this.serviceTypeDao = serviceTypeDao;
}
public ServiceType getServiceTypeById(int id){
return serviceTypeDao.findById(id);
}
}
Replacing getSessionFactory().getCurrentSession() with getSessionFactory().openSession() will resolve the above issue however, it will mean that the developer will then be responsible for the session open/close rather than spring. Therefore, please advise how this can be resolved using spring.
I was able to resolve the issue. It was occurring due to the following problems:
The Service class had not been Auto wired into the controller i.e. the #Autowired annotation was missing.
The configuration for the web.xml had to be modified with the listener class 'org.springframework.web.context.ContextLoaderListener' and the context-param was added.
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.