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.
Related
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="DataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
<prop key="org.hibernate.cache.ehcache.configurationResourceName">classpath:hibernate-ehcache.xml</prop>
<prop key="cache.provider_class">${cache.provider_class}</prop>
<prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
<prop key="hibernate.enable_lazy_load_no_trans">true</prop>
</props>
</property>
</bean>
<!-- Hibernate template for hibernateSessionFactory -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- Transaction Manager for hibernateSessionFactory -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="dataSource" ref="DataSource" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Above is the Hibernate config for the spring boot application. We have addition added the following c3po config. But still the connection pool is getting maxed out.
<bean id="DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"></property name>
<property name="jdbcUrl" value="${ondot.jdbc.url}"></property name>
<property name="user" value="${jdbc.user}"></property name>
<property name="password" value="${jdbc.password}"></property name>
<property name="initialPoolSize" value="${initialPoolSize}" /></property name>
<property name="minPoolSize" value="${minPoolSize}"></property name>
<property name="maxPoolSize" value="${maxPoolSize}"></property name>
<property name="acquireIncrement" value="${acquireIncrement}"></property name>
<property name="maxStatements" value="${maxStatements}"></property name>
<property name="acquireRetryAttempts" value="${acquireRetryAttempts}"></property name>
<property name="acquireRetryDelay" value="${acquireRetryDelay}"> </property name>
<property name="breakAfterAcquireFailure"
value="${breakAfterAcquireFailure}"></property name>
<property name="maxIdleTime" value="${maxIdleTime}"> </property name>
and following is the property file:
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.generate_statistics=true
cache.provider_class=org.hibernate.cache.EhCacheProvider
initialPoolSize=5
minPoolSize=100
maxPoolSize=250
acquireIncrement=5
maxStatements=100
acquireRetryAttempts=10
acquireRetryDelay=1000
breakAfterAcquireFailure=false
maxIdleTime=1800
The session factory is autowired and a simple code with Open and close session is written.
public class BasicAunthenticationImpl {
private static SessionFactory sessionFactory;
public static SessionFactory getSessionFactory()
{
return sessionFactory;
}
public static void setSessionFactory(SessionFactory sessionFactory)
{
BasicAunthenticationImpl.sessionFactory = sessionFactory;
}
}
public class Module1
{
#Autowired
private SessionFactory sessionFactory;
private Session session = null;
#Override
public void process(List<RoutableModuleData> routableProcessObjects)
throws OndotException
{
try
{
session = sessionFactory.openSession();
session.flush();
canonicalMessage =
lookupprocesser.processInput(canonicalMessage,
Constants.L1_LOOKUP_CACHE, session);
}
catch (Exception Ex)
{
}
finally
{
if (session != null && session.isOpen()){
session.close();
}
}
}
}
public class Lookupprocesser{
public CanonicalMessage processInput(CanonicalMessage canonicalMessage,
String cacheLookup, Session session) throws DataInsightsCommonException
{
lookupSearchForL1(canonicalMessage, session, cacheLookup);
return canonicalMessage;
}
}
public class Query{
public List<TerminalData> searchByMatchKey1AndMatchKey2(Session
session, String matchKey1, String matchKey2) throws
DataInsightsCommonException
{
Query query;
List<Data> t = null;
query = session.createQuery("from Data as odt where odt.Key1 is
null and odt.Key2 = :Key2");
query.setParameter(Constants.KEY2, Key2);
query.setCacheable(true);
terminals = (List<Data>) query.list();
return (null != t && !t.isEmpty()) ? t : null;
}
}
The Session object is shared among all classes and object, I edited the code below , BasicAuth class creates the SessionFactory
The Module1 will call LookupProcessor and which will call the query class. We are closing the session on the Module1.
Introduction
In order to extract some data from a database, I am trying to setup a basic hibernate and spring batch project. The goal is to provide one query (HQL) and based on this query the spring batch application extracts all the data to a flat file.
One of the requirements of the application is that the user should not have to configure the mappings of the columns. As such I am trying to create a DynamicRecordProcessor that evaluates the input and passes the input (a table for example Address) to the writer in such a way that the flat file item writer can use a PassThroughFieldExtractor.
Below the reader-processor-writer xml configuration:
<!-- Standard Spring Hibernate Reader -->
<bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader">
<property name="sessionFactory" ref="sessionFactory" />
<property name="queryString" value="from Address" />
</bean>
<!-- Custom Processor -->
<bean id="dynamicRecordProcessor" class="nl.sander.mieras.processor.DynamicRecordProcessor"/>
<!-- Standard Spring Writer -->
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:target/extract/output.txt" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value="|"/>
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor"/>
</property>
</bean>
</property>
</bean>
EDIT:
And the job configuration:
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"/>
<property name="cacheableMappingLocations" value="classpath*:META-INF/mappings/*.hbm.xml"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager" lazy-init="true">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Problem
My processor looks as following:
public class DynamicRecordProcessor<Input,Output> implements ItemProcessor<Input,Output> {
private static final String DELIMITER = "|";
private boolean areNamesSetup = false;
private List<String> names = new ArrayList<String>();
private Input item;
#SuppressWarnings("unchecked")
#Override
public Output process(Input item) throws Exception {
this.item = item;
initMapping();
return (Output) extract();
}
private void initMapping() {
if (!areNamesSetup) {
mapColumns();
}
areNamesSetup = true;
}
private void mapColumns() {
Field[] allFields = item.getClass().getDeclaredFields();
for (Field field : allFields) {
if (!field.getType().equals(Set.class) && Modifier.isPrivate(field.getModifiers())) {
names.add(field.getName());
}
}
}
private Object extract() {
List<Object> values = new ArrayList<Object>();
BeanWrapper bw = new BeanWrapperImpl(item);
for (String propertyName : this.names) {
values.add(bw.getPropertyValue(propertyName));
}
return StringUtils.collectionToDelimitedString(values, DELIMITER);
}
}
The table Address has the following field:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="city_id", nullable=false)
public City getCity() {
return this.city;
}
And the corresponding column in city:
#Column(name="city_id", unique=true, nullable=false)
public Short getCityId() {
return this.cityId;
}
When using values.add(bw.getPropertyValue(propertyName)); with propertyName being "city" the following exception occurs:
org.hibernate.SessionException: proxies cannot be fetched by a stateless session
at org.hibernate.internal.StatelessSessionImpl.immediateLoad(StatelessSessionImpl.java:292)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:156)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:260)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at nl.sander.mieras.localhost.sakila.City_$$_jvstc2c_d.toString(City_$$_jvstc2c_d.java)
at java.lang.String.valueOf(String.java:2982)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1132)
at org.springframework.util.StringUtils.collectionToDelimitedString(StringUtils.java:1148)
at nl.sander.mieras.processor.DynamicRecordProcessor.extract(DynamicRecordProcessor.java:52)
at nl.sander.mieras.processor.DynamicRecordProcessor.process(DynamicRecordProcessor.java:27)
The value, however, is availabe as shown in the screenshot below.
Concrete question: How can I get the value 300?
I have tried getting the value using reflection API, but I couldn't reach the actual value I want to get...
Reproduce
I have setup a public repo. However, you still need a local database to be able to reproduce exactly the issue. https://github.com/Weirdfishees/hibernate-batch-example. Any suggestions how isolate this issue further are more then welcome.
Just flip your reader to be state-full instead of stateless with the useStatelessSession property:
<!-- Standard Spring Hibernate Reader -->
<bean id="hibernateItemReader" class="org.springframework.batch.item.database.HibernateCursorItemReader">
<property name="sessionFactory" ref="sessionFactory" />
<property name="queryString" value="from Address" />
<property name="useStatelessSession" value="false" />
</bean>
Want to use Spring to automatically work with Hibernate 4 sessions
Also I like to extends DAO, for not create many DAO for many entities
But there is problem:
SessionDAO.java
public abstract class SessionDAO extends HibernateDaoSupport{
public void startSession() {
getSession().beginTransaction();
}
public void closeSession() {
getSession().getTransaction().commit();
}
public void addObject() {
startSession();
getSession().save(this);
closeSession();
}
public Session getSession()
{
return getHibernateTemplate().getSessionFactory().getCurrentSession();
}
}
Values.java
#Entity
#Table
public class Values extends SessionDAO {
private int valuesId;
private double amount;
private Date date;
//...getters, setters, etc
}
dispatcher-servlet.xml
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>entity.Crypto</value>
<value>entity.Values</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="dataSource"
class="org.apache.tomcat.dbcp.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#my.****.us-east-1.rds.amazonaws.com:1521:ORCL" />
<property name="username" value="****" />
<property name="password" value="****" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="persistenceExceptionTranslationPostProcessor"
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
When start server, receive:
INFO: HHH000206: hibernate.properties not found
When try to load page receive:
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException
on line
return getHibernateTemplate().getSessionFactory().getCurrentSession();
What's wrong?
Don't get the session by extend HibernateDaoSupport, just inject SessionFactory in DAO.
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
All. Attached herewith all the files. If I give the same name for both bean property name and reference, it returns the object. But it returns null, in case both are different. Please help. Thank you so much.
The AuthenticateAction.java file has got the detailed comments inside it.....
below are my files list:
WIRAdminBaseAction.java ---> my base action
AuthenticateAction.java ---> my java file that calls the bean here
applicationContext_MNC.xml
The below way of calling works...... This is not encouraged, as we
should not use applicationContext always
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletRequest().getSession().getServletConte xt());
ICacheDelegate cacheAction = (ICacheDelegate)applicationContext.getBean("BMOCacheDelegate");
The below way of calling does NOT work .... returns null value....
Please help... I assume that, since I have extended the
WIRAdminBaseAction, i should be able to call the getCacheDelegate
directly and it should return my cacheDelegate object ... Again,
Please note.....if I change my applicationContext_MNC.xml as below,
the below way of calling works fine. But, i don't want to change my
applicationContext_MNC.xml as below, due to some necessity.
<bean id="BMOWIRAdminBaseAction" class="com.symcor.wiradmin.web.action.WIRAdminBase Action">
<property name="cacheDelegate">
<ref bean="cacheDelegate" />
</property>
</bean>
<bean id="cacheDelegate" class="com.symcor.wiradmin.delegate.CacheDelegate" >
<property name="statusDBDAO"><ref bean="BMOStatusDBDAO" /></property>
</bean>
Is it that the name and bean should have the same value.... ??? No
Need to be.....Am i right ? Please advise.
getCacheDelegate().getActorAction(1); // this way of calling doesn't work and returns null value. please help.
MY CODE Follows below.
AuthenticateAction.java
package com.symcor.wiradmin.web.action;
import java.net.UnknownHostException;
import java.sql.SQLException;
import org.bouncycastle.crypto.CryptoException;
import org.springframework.context.ApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.web.context.support.WebApplica tionContextUtils;
import com.symcor.wiradmin.delegate.ICacheDelegate;
public class AuthenticateAction extends WIRAdminBaseAction {
private static final long serialVersionUID = 1L;
public String authenticate() throws UnknownHostException, CryptoException,
DataAccessException, SQLException{
/** This way of calling works...... This is not encouraged, as we should not use applicationContext always **/
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContex t(getServletRequest().getSession().getServletConte xt());
ICacheDelegate cacheAction = (ICacheDelegate) applicationContext.getBean("BMOCacheDelegate");
/** The below way of calling does NOT work .... returns null value.... Please help...
* I assume that, since I have extended the WIRAdminBaseAction, i should be able to call the getCacheDelegate directly
* and it should return my cacheDelegate object ...
* Again, Please note.....if I change my applicationContext_MNC.xml as below, the below way of calling works fine...
* but, i don't want to change my applicationContext_MNC.xml as below, due to some necessity.
<bean id="BMOWIRAdminBaseAction" class="com.symcor.wiradmin.web.action.WIRAdminBase Action">
<property name="cacheDelegate">
<ref bean="cacheDelegate" />
</property>
</bean>
<bean id="cacheDelegate" class="com.symcor.wiradmin.delegate.CacheDelegate" >
<property name="statusDBDAO"><ref bean="BMOStatusDBDAO" /></property>
</bean>
... is it that the name and bean should have the same value.... ??? No Need to be.....Am i right ? Please advise.
*
* **/
getCacheDelegate().getActorAction(1); // this way of calling doesn't work and returns null value. please help.
return "success";
}
}
WIRAdminBaseAction.java
package com.symcor.wiradmin.web.action;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ParameterAware;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.Preparable;
import com.opensymphony.xwork2.config.entities.Parameteri zable;
import com.symcor.wiradmin.delegate.ICacheDelegate;
public class WIRAdminBaseAction extends ActionSupport implements Preparable, ParameterAware, Parameterizable, SessionAware,RequestAware {
private HttpServletRequest request;
private static final long serialVersionUID = 1L;
private HttpServletResponse response;
private ICacheDelegate cacheDelegate;
private Map session;
private Map<String, String> params;
private Map parameters;
public void prepare() throws Exception {
}
public String execute() throws Exception {
return SUCCESS;
}
public void setServletRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletRequest getServletRequest() {
return this.request;
}
public void setServletResponse(HttpServletResponse response) {
this.response = response;
}
public HttpServletResponse getServletResponse() {
return this.response;
}
public ICacheDelegate getCacheDelegate() {
return cacheDelegate;
}
public void setCacheDelegate(ICacheDelegate cacheDelegate) {
this.cacheDelegate = cacheDelegate;
}
public void addParam(final String key, final String value) {
this.params.put(key, value);
}
public Map getParams() {
return params;
}
public void setParams(final Map<String, String> params) {
this.params = params;
}
public Map getSession() {
return this.session;
}
public void setSession(final Map session) {
this.session = session;
}
public void setParameters(final Map param) {
this.parameters = param;
}
}
applicationContext_MNC.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- database configuration from property file -->
<bean id="BMOAdminDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close" lazy-init="default" autowire="default" dependency-check="default">
<property name="driverClass" value="${jdbc.driver}" ></property>
<property name="jdbcUrl" value="${admin.jdbc.url}" ></property>
<property name="user" value="${admin.jdbc.user}" ></property>
<property name="password" value="${admin.jdbc.password}" ></property>
<property name="initialPoolSize" value="3" ></property>
<property name="minPoolSize" value="3" ></property>
<property name="maxPoolSize" value="25" ></property>
<property name="acquireIncrement" value="1" ></property>
<property name="acquireRetryDelay" value="1000" ></property>
<property name="debugUnreturnedConnectionStackTraces" value="true" ></property>
<property name="maxIdleTime" value="300" ></property>
<property name="unreturnedConnectionTimeout" value="300000" ></property>
<property name="preferredTestQuery" value="SELECT COUNT(*) FROM LOCALE_CODE" ></property>
<property name="checkoutTimeout" value="300000" ></property>
<property name="idleConnectionTestPeriod" value="600000" ></property>
</bean>
<bean id="BMOWIRAdminBaseAction" class="com.symcor.wiradmin.web.action.WIRAdminBase Action">
<property name="cacheDelegate">
<ref bean="BMOCacheDelegate" />
</property>
</bean>
<bean id="BMOCacheDelegate" class="com.symcor.wiradmin.delegate.CacheDelegate" >
<property name="statusDBDAO"><ref bean="BMOStatusDBDAO" /></property>
</bean>
<bean id="BMOStatusDBDAO" class="com.symcor.wiradmin.dao.StatusDBDAO">
<property name="dataSource">
<ref bean="BMOAdminDataSource" />
</property>
</bean>
<!-- this bean is set to map the constants which needs to be configured as per
the environment to the java constants file -->
<bean id="envConstantsConfigbean" class="com.symcor.wiradmin.util.constants.Environm entConstantsSetter">
<property name="loginUrl" value="${login.url}"/>
<property name="logoutIR" value="${logout.from.image.retrieval}"/>
<property name="adminModuleUrl" value="${admin.url}"/>
<property name="adminUrlSym" value="${admin.url.sym}"/>
<property name="envProperty" value="${env.property}"/>
</bean>
</beans>
So, I try to use Spring for transaction management. Finally my application had worked, but deleting do not commits. Before using Spring I managed transactions in DAO, and I thought that after I can remove this code. But now deleting do not commit to DB.
My DAO:
protected Session getSession() {
Session session = sessionFactory.openSession();
ThreadLocalSessionContext.bind(session);
return session;
}
public void delete(T t) {
Session session = getSession();
// **this approach I used before**
// session.beginTransaction();
// try {
// session.delete(t);
// session.getTransaction().commit();
// } catch (Exception e) {
// session.getTransaction().rollback();
// }
session.delete(t);
}
My applicationContext:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://ngs-java-srv.synapse.com:3306/mybase" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- <property name="configLocation" value="hibernate.cfg.xml" /> -->
<property name="packagesToScan" value="todolist.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- dao beans -->
<bean id="userDao"
class="todolist.dao.hibernate.UserDaoImpl">
<constructor-arg>
<value>todolist.entity.User</value>
</constructor-arg>
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="itemDao"
class="todolist.dao.hibernate.ItemDaoImpl">
<constructor-arg>
<value>todolist.entity.Item</value>
</constructor-arg>
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- service bean -->
<bean id="userService" class="todolist.service.UserServiceImpl">
<property name="userDao" ref="userDao" />
<property name="itemDao" ref="itemDao" />
</bean>
<!-- transaction manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>
<!-- <tx:annotation-driven/> -->
<!-- <aop:config>
<aop:pointcut id="serviceMethods"
expression="execution(* todolist.service.UserService.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRES_NEW" />
<tx:method name="deleteItem" no-rollback-for="Exception" />
</tx:attributes>
</tx:advice> -->
<!-- backbeans -->
<bean id="userLogin" class="todolist.jsf.UserLogin"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="userLogged" class="todolist.jsf.UserLogged"
scope="session">
<aop:scoped-proxy />
</bean>
<bean id="userRegister" class="todolist.jsf.UserRegister"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="createItem" class="todolist.jsf.CreateItem"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="todoList" class="todolist.jsf.TodoList"
scope="request">
<property name="userService" ref="userService" />
</bean>
</beans>
UserServiceImpl class
package todolist.service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import todolist.exception.AuthorizationError;
import todolist.exception.DuplicateLoginsException;
import todolist.service.StringToHashTool;
import todolist.dao.ItemDao;
import todolist.dao.UserDao;
import todolist.entity.Item;
import todolist.entity.User;
public class UserServiceImpl implements UserService {
//private static final Logger log = Logger.getLogger(UserServiceImpl.class);
private UserDao userDao;
private ItemDao itemDao;
public void setItemDao(ItemDao itemDao) {
this.itemDao = itemDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isUserExists(String login) {
return (userDao.getUserByLogin(login) != null);
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isPasswordCorrect(String login, String password) {
if (isUserExists(login)) {
return userDao.getUserByLogin(login).getPassword()
.equals(StringToHashTool.getHash(password));
} else {
return false;
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User login(String login, String password) {
if (isPasswordCorrect(login, password)) {
return userDao.getUserByLogin(login);
} else {
throw new AuthorizationError("Incorrect password");
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean register(String login, String password) {
if (isUserExists(login)) {
throw new DuplicateLoginsException("Login " + login + " is already used.");
} else {
User user = new User();
user.setLogin(login);
user.setPassword(StringToHashTool.getHash(password));
userDao.save(user);
return true;
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void logout() {
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Item createItem(Long creator_id, Long performer_id,
String description, Date dueDate) {
Item item = new Item();
User user = userDao.getById(creator_id);
item.setCreator(user);
user = userDao.getById(performer_id);
item.setPerformer(user);
item.setDescription(description);
item.setStartDate(new Date());
item.setDueDate(dueDate);
itemDao.save(item);
return item;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteItem(Long item_id) {
Item item = itemDao.getById(item_id);
itemDao.delete(item);
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public List<Item> getUserItems(String login) {
User user = userDao.getUserByLogin(login);
return itemDao.getItemsByPerformer(user.getId());
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public List<User> getUsers() {
return userDao.getUsers();
}
}
<aop:pointcut
id="serviceMethods"
expression="execution(*todolist.service.UserService.*(..))"
/>
First Step:
Change this to:
<aop:pointcut
id="serviceMethods"
expression="todolist.service.UserServiceImpl.delete()"
/>
See if delete's start committing. I just want to make sure you flush out any typos and what have you. If this explicit config fails then there must be something wrong with the config.
I have had success using the Spring HibernateTransactionManager object and annotation driven configuration. In my application-context, I simply declare the following two beans:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="session_factory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Then in my service layer, your UserServiceImpl for example:
public class UserServiceImpl implements UserService {
private final UserDao userDao;
private final ItemDao itemDao;
public UserServiceImpl(UserDao userDao, ItemDao itemDao) {
this.userDao = userDao;
this.itemDao = itemDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isUserExists(String login) {
return (userDao.getUserByLogin(login) != null);
}
...
It's important to move you transaction annotations as close to the view (in MVC) as possible. The reason is 1) performance and 2) atomic transactions. If the call calling the UserService calls the isUserExists, isPasswordCorrect and login methods in a single function call, you would want those to execute in a single transaction. To do so, remove the #Transactional annotation from the UserServiceImpl and move it to the calling class. This ensures that a single transaction is used for all three method calls. For atomic execution, this also works. Think of the classic bank money transfer example. If you had two separate transactions on the UserService, the first two method call can succeed and the last one fail. The firt two transactions would be committed because they are in separate transaction blocks which would leave you database in an inconsistent state. Of course, atomic transactions are mostly relevant for transactions that write data.
Also, don't add the transactional annotations to the interfaces, keep them on the implementations.
Then in my DAO, I simply do a sessionFactory.getCurrentSession() and go from there. getCurrentSession() is thread safe but keep it private to your DAOs.
Use getCurrentSession instead of openSession in your DAO, so that the getSession method looks like:
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
You're creating a new session that uses a different database connection so the transaction set up for the service doesn't apply.
Also as you have them configured the DAOs will have a default scope of singleton; passing an entity as a constructor-argument doesn't make sense. A singleton shouldn't have instance state specific to some individual transaction.