I have my own DAO which extends NamedJdbcDaoSupport. This DAO is created using the Spring framework.
I have written a small test application which accesses the DAO multiple times from the main thread without wait.
In this scenario everything works fine.
But if I access the DAO from different threads I receive the following NPE:
java.lang.NullPointerException
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.createPreparedStatement(PreparedStatementCreatorFactory.java:238)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:627)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:684)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:711)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:761)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query(NamedParameterJdbcTemplate.java:192)
at test.DAO.loadData(DAO.java:29)
at test.Application$1.run(Application.java:19)
at java.lang.Thread.run(Unknown Source)
In the description of the NamedJdbcDaoSupport is the following note:
"NOTE: An instance of this class is thread-safe once configured."
So why am I receiving this NPE? If I go into the Spring source I can see that the DBConnection seems to be null. But why? Normally Spring returns with a SQLException if it is not able to get a connection from the connection pool. So an NPE should never be possible.
Can someone help me understand why the NamedJdbcDaoSupport is not thread-safe despite the comments suggesting otherwise. And what I can do?
This is my Application:
public class Application {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("ServerContext.xml");
DAO bean = (DAO)classPathXmlApplicationContext.getBean("dao");
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
#Override
public void run() {
for (int j = 0; j < 100; j++) {
bean.loadData();
}
}
}).start();
}
}
}
This is my gradle script:
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.springframework', name: 'spring-context', version: '4.3.11.RELEASE'
compile 'org.springframework:spring-jdbc:4.3.11.RELEASE'
compile 'commons-dbcp:commons-dbcp:1.4'
compile files('lib/ojdbc6.zip')
}
This is my DAO:
public class DAO extends NamedParameterJdbcDaoSupport {
public List<String> loadData() {
Date fromDate = null;
Date toDate = null;
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("fromDate", fromDate);
namedParameters.addValue("toDate", toDate);
String sql = "SELECT * FROM myTable";
return getNamedParameterJdbcTemplate().query(sql, namedParameters, new RowMapper<String>() {
#Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return null;
}
});
}
}
And this is my Spring context:
<!-- Database -->
<bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
<property name="URL" value="jdbc:oracle:thin:#localhost:1521:orcl"/>
<property name="user" value="user"/>
<property name="password" value="passwort"/>
<property name="connectionCachingEnabled" value="true" />
<property name="connectionCacheName" value="my_connection_pool" />
<property name="connectionCacheProperties">
<value>
MinLimit: 10
MaxLimit: 10
InitialLimit: 10
ValidateConnection: true
</value>
</property>
</bean>
<bean id="dao" class="test.DAO">
<property name="dataSource" ref="dataSource"/>
</bean>
Related
In my spring project i am using hibernate entitymanager. I have done transaction management in the project like below.
applicationContext.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#xxxx:1527:selva" />
<property name="username" value="xxx" />
<property name="password" value="yyy" />
</bean>
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.selva.entity</value>
</list>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="ORACLE" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
TestTranService.java
#Transactional
public String checkTranWork() {
try {
testTranDao.method1();
int i=0;
if(i==0)
throw new ValidationException();
testTranDao.method2();
testTranDao.method3();
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
TestTranDaoImpl.java
public class TestTranDaoImpl implements TestTranDao {
public static final Logger LOGGER = Logger
.getLogger(TestTranDaoImpl.class);
#PersistenceContext
private EntityManager entityManager;
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public void method1() {
try {
String sql = "INSERT INTO CUST(MOBNUM,CUST_NAME) values ('1','dd')";
Query query = entityManager.createNativeQuery(sql);
query.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void method2() {
try {
String sql = "INSERT INTO CUST(MOBNUM,CUST_NAME) values ('1','dd')";
Query query = entityManager.createNativeQuery(sql);
query.executeUpdate();
} catch(Exception e) {
e.printStackTrace();
}
}
}
In my code after method 1 executing the insert statement i am throwing the exception so the first statement should be rolled back.But in my case rollback is not happened.Is their any other configuration required for rollback.
Any help will be greatly appreciated!!!
You Catch the Exceptions to log them, but then, you drop them. Don't do that!
An Exception has a meaning in a software, more than for logging, but also for all the framework around it.
Remove the try catch blocks so the #Transactional Aspect around your method can detect there's an error and rollback your transaction.
As explained by Gillaume for #Transactional to work, the method has to throw an exception then only the framework will know that rollback has to be performed.
One solution already suggested is to remove the try/catch and if there is a need to catch the exception and log in application error log file then make sure the exception is propagated not consumed.
So the updated code would look like:
#Transactional
public String checkTranWork() {
try {
testTranDao.method1();
int i=0;
if(i==0)
throw new ValidationException();
testTranDao.method2();
testTranDao.method3();
} catch(Exception e) {
e.printStackTrace();
throw e;
}
return null;
}
I have a method in one of the DAO class that is inserting records in 2 different Oracle tables. I would like the records to be either inserted in both the table or none.
In order to achieve this I am using both #Transactional and cn.setAutoCommit(false) code snippet.
In order to test it, I intentionally put in a wrong column name in the SQL so that data insertion in the second table fails. The expectation, from me, here is that the data will not get inserted in the first table since the insertion in second table failed because of incorrect query. But that didn't happen for some reason. The record still got inserted in first table and the second table did not have the record inserted.
It looks like the implementation is not incorrect here. Not sure what I am missing here.
EventLogDao.java
#Transactional
public long saveEventData(EventLog eventLog, String userId) throws SQLException {
Connection cn = this.dataSource.getConnection();
cn.setAutoCommit(false);
//(FIRST TABLE INSERTION - Table Name: EVENT_LOG)
//save data in event log table
long eventId = getNextEventIdSequence();
saveEventLogData(eventId, eventLog);
//(SECOND TABLE INSERTION - Table Name: EVENT_LOG_MESSAGE)
//save data in event log message table
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
cn.commit();
return eventId;
}
private void saveEventLogData(long eventId, EventLog eventLog) {
Object[] parameters = {eventId, eventLog.getRouteId(), eventLog.getEventType().getEventTypeId(),
eventLog.getOrderId(), eventLog.getIncomingEventTimestamp(), eventLog.getOutgoingEventTimestamp()};
int[] types = {Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.TIMESTAMP, Types.TIMESTAMP};
int rowsAffected = jdbcTemplate.update(INSERT_EVENT_LOG_SQL2, parameters, types);
System.out.println("rowsAffected (eventlog) = " + rowsAffected);
}
private int saveEventLogMessageData(long eventId, EventLogMessage eventLogMessage) {
Object[] parameters = {eventId, eventLogMessage.getIncomingEventMessage(), eventLogMessage.getOutgoingEventMessage()};
int[] types = {Types.INTEGER, Types.VARCHAR, Types.VARCHAR};
int rowsAffected = jdbcTemplate.update(INSERT_EVENT_LOG_MESSAGE_SQL2, parameters, types);
System.out.println("rowsAffected (eventLogMessage) = " + rowsAffected);
return rowsAffected;
}
applicationContext.xml
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</constructor-arg>
<property name="propagationBehavior">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.PROPAGATION_REQUIRED"/>
</property>
<property name="isolationLevel">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.ISOLATION_READ_COMMITTED"/>
</property>
</bean>
<bean id="eventLogDao" class="com.ebayenterprise.publicapi.events.dao.EventLogDao">
<constructor-arg ref="dataSource" />
</bean>
Please guide.
I suppose that you should use transactionTemplate with your jdbcTemplate together:
public long saveEventData(EventLog eventLog, String userId) throws SQLException {
return transactionTemplate.execute(new TransactionCallback<Long>(){
#Override
public Long doInTransaction(TransactionStatus transactionStatus) {
try {
Connection cn = this.dataSource.getConnection();
cn.setAutoCommit(false);
//(FIRST TABLE INSERTION - Table Name: EVENT_LOG)
//save data in event log table
long eventId = getNextEventIdSequence();
saveEventLogData(eventId, eventLog);
//(SECOND TABLE INSERTION - Table Name: EVENT_LOG_MESSAGE)
//save data in event log message table
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
cn.commit();
return eventId;
} catch (Exception e) {
transactionStatus.setRollbackOnly();
}
return 0;
}
});
You probably has not configuration for JTA and your #Transactional has no effect.
Got it working by using TransactionManager. The changes I make to my code base are mentioned below.
applicationContext.xml (updated)
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<constructor-arg ref="transactionManager"/>
<property name="propagationBehavior">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.PROPAGATION_REQUIRED"/>
</property>
<property name="isolationLevel">
<util:constant static-field="org.springframework.transaction.support.DefaultTransactionDefinition.ISOLATION_READ_COMMITTED"/>
</property>
</bean>
<bean id="eventLogDao" class="com.ebayenterprise.publicapi.events.dao.EventLogDao">
<constructor-arg index="0" ref="dataSource" />
<constructor-arg index="1" ref="transactionManager"/>
</bean>
EventLogDao.java (updated)
public EventLogDao(DataSource dataSource, PlatformTransactionManager transactionManager) {
super(dataSource, transactionManager);
}
public long save(EventLog eventLog, String userId) throws Exception {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
long eventId = 0L;
try {
eventId = getNextEventIdSequence();
System.out.println("eventId = " + eventId);
saveEventLogData(eventId, eventLog);
saveEventLogMessageData(eventId, eventLog.getEventLogMessage());
userId = StringUtils.defaultIfBlank(userId, DEFAULT_USER_ID);
saveEventLogAuditData(eventId, userId, eventLog.getOutgoingEventTimestamp());
transactionManager.commit(txStatus);
} catch (TransactionException ex) {
transactionManager.rollback(txStatus);
throw new RuntimeException("Error occurred during tx management in event log tables...", ex);
} finally {
return eventId;
}
}
I need to convert dbcp2 java setup code into spring beans
The below code works as expected:
protected void setupDriver(String connectURI, String username, String password) throws ClassNotFoundException, SQLException{
//
// First, we'll create a ConnectionFactory that the
// pool will use to create Connections.
// We'll use the DriverManagerConnectionFactory,
// using the connect string passed in the command line
// arguments.
//
ConnectionFactory connectionFactory =
new DriverManagerConnectionFactory(connectURI, username, password);
//
// Next we'll create the PoolableConnectionFactory, which wraps
// the "real" Connections created by the ConnectionFactory with
// the classes that implement the pooling functionality.
//
poolableConnectionFactory =
new PoolableConnectionFactory(connectionFactory, null);
logger.info("poolableConnectionFactory created");
//
// Now we'll need a ObjectPool that serves as the
// actual pool of connections.
//
// We'll use a GenericObjectPool instance, although
// any ObjectPool implementation will suffice.
//
connectionPool =
new GenericObjectPool<PoolableConnection>(poolableConnectionFactory,getPoolConfig());
logger.info("connectionPool created");
// Set the factory's pool property to the owning pool
poolableConnectionFactory.setPool(connectionPool);
logger.info("connectionPool is set to poolableConnectionFactory");
//
// Finally, we create the PoolingDriver itself...
//
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
logger.info("dbcp2 driver is created");
//
// ...and register our pool with it.
//
driver.registerPool(poolName,connectionPool);
logger.info("driver is registered");
//
// Now, we create the PoolingDriver itself,
// passing in the object pool we created.
//
dataSource = new PoolingDataSource<PoolableConnection>(connectionPool);
logger.info("dataSource is created");
//
//Finally we create the JdbcTemplate for sql
//operations in DbDAO class
//
jdbcTemplate = new JdbcTemplate(dataSource);
logger.info("jdbcTemplate is setup");
logger.info("Finally dbcp2 driver setup is completed!");
}
//Pool initial setup values
private GenericObjectPoolConfig getPoolConfig(){
logger.info("Let's create the pool config values");
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(Integer.parseInt(config.getMaxtotal())); // set number of max connections i.e 25
poolConfig.setMaxWaitMillis(Long.parseLong(config.getMaxwaitmillis())); //ie. wait for a minute = 60000
poolConfig.setMaxIdle(Integer.parseInt(config.getMaxidle())); // set max number of idle connections
/*poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);*/
//poolConfig.setTestWhileIdle(true);
//poolConfig.setTimeBetweenEvictionRunsMillis(10000L);
//poolConfig.setNumTestsPerEvictionRun(5);
//poolConfig.setMinEvictableIdleTimeMillis(5000L);
return poolConfig;
}
And this is the beans.xml
<!-- ============ Trauma Database Connection Pooling Beans Settings ================== -->
<bean id="connectionFactory" class="org.apache.commons.dbcp2.DriverManagerConnectionFactory">
<constructor-arg index="0" value="${tir.jdbc.url}" />
<constructor-arg index="1" value="${tir.jdbc.username}" />
<constructor-arg index="2" value="${tir.jdbc.password}" />
</bean>
<!-- Connection Factory -->
<bean id="poolableConnectionFactory" class="org.apache.commons.dbcp2.PoolableConnectionFactory">
<constructor-arg index="0" ref="connectionFactory"/>
<constructor-arg index="1" > <null/> </constructor-arg>
</bean>
<!-- Pool Configs -->
<bean id="poolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
<property name="maxTotal" value="${pool.maxtotal}"/>
<property name="maxIdle" value="${pool.maxidle}"/>
<property name="minIdle" value="${pool.minidle}"/>
<property name="maxWaitMillis" value="${pool.maxwaitmillis}"/>
</bean>
<!-- Connection Pool -->
<bean id="connectionPool" class="org.apache.commons.pool2.impl.GenericObjectPool">
<constructor-arg index="0" ref="poolableConnectionFactory"/>
<constructor-arg index="1" ref="poolConfig"/>
</bean>
<!-- Datasource gets connection pool -->
<bean id="dataSource" class="org.apache.commons.dbcp2.PoolingDataSource">
<constructor-arg ref="connectionPool"/>
</bean>
<!-- JdbcTemplate bean gets the datasource -->
<bean id="jdbcTemplateTIR" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- Finally, we create our Database object bean -->
<bean id="dbdao" class="edu.uams.dao.impl.DBDAO">
<property name="jdbcTemplate" ref="jdbcTemplateTIR" />
</bean>
<!-- ============= END OF Trauma Database Connection Pooling Settings =========== -->
In both situations I can use the jDBCTemplate objects, however it gives the following warning:
"WARN 0[main] - org.apache.commons.dbcp2.PoolingDataSource.(PoolingDataSource.java:65) PoolableConnectionFactory not linked to pool. Calling setPool() to fix the configuration"
The reason is obvious: In my java code, I setup
poolableConnectionFactory.setPool(connectionPool);
How can I call setPool method from beans?
And how can I also setup this java code in my beans? The constructor of DriverManagerConnectionFactory does not get any constructor of DriverClassName?
// Finally, we create the PoolingDriver itself...
//
Class.forName("org.apache.commons.dbcp2.PoolingDriver");
driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
logger.info("dbcp2 driver is created");
//
// ...and register our pool with it.
//
driver.registerPool(poolName,connectionPool);
Wouldn't it be easier if you just used org.apache.commons.dbcp2.BasicDataSource? According to the documentation it provides a "one stop shopping" solution for basic connection pooling.
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.jdbcurl}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="initialSize" value="3"/>
</bean>
I was searching for a solution to exactly this problem given in the question with these specific versions of the libraries. I used the Spring configuration you gave and tweaked it a little bit, and I was able to get it working. Here is how I am using it...
<bean id="poolingDataSourceBean" class="org.apache.commons.dbcp2.PoolingDataSource">
<constructor-arg>
<bean id="genericObjectPoolBean" class="org.apache.commons.pool2.impl.GenericObjectPool">
<constructor-arg>
<bean id="poolableConnectionFactoryBean" class="org.apache.commons.dbcp2.PoolableConnectionFactory">
<constructor-arg index="0">
<bean id="dataSourceConnectionFactoryBean" class="org.apache.commons.dbcp2.DataSourceConnectionFactory">
<constructor-arg>
<bean id="dataSourceBean" class="net.sourceforge.jtds.jdbcx.JtdsDataSource">
<property name="serverName" value="${database.server}"/>
<property name="portNumber" value="${database.port}"/>
<property name="databaseName" value="${database.name}"/>
<property name="user" value="${database.user}"/>
<property name="password" value="${database.password}"/>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
<constructor-arg index="1"><null/></constructor-arg>
</bean>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
You are getting PoolableConnectionFactory not linked to pool. Calling setPool() to fix the configuration" warning because PoolingDataSource has pooling defined while PoolableConnectionFactory does not. With spring you run into circular dependency: PoolableConnectionFactory needs GenericObjectPool that needs PoolableConnectionFactory in constructor.
One way to get around it is to use MethodInvokingFactoryBean (or MethodInvokingBean in spring 4.0+):
<bean id="connectionPoolSetter" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" >
<property name="targetObject" ref="poolableConnectionFactory" />
<property name="targetMethod" value="setPool"/>
<property name="arguments" ref="connectionPool" />
</bean>
then add depends-on attribute to dataSource to make sure connectionPoolSetter is initialized:
<bean id="dataSource" class="org.apache.commons.dbcp2.PoolingDataSource" depends-on="connectionPoolSetter">
<constructor-arg ref="connectionPool"/>
</bean>
Check Pooling Data Source example code:
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/doc/PoolingDataSourceExample.java?view=markup
By the way, I also have a DBPoolManager which I can manage the pool itself, even printing the stats.. even I haven't find any solution yet, I hope this code will be useful for you..
import java.sql.Connection;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
public class DBPoolManager {
private static final Logger logger = LoggerFactory.getLogger(DBPoolManager.class);
private static DBPoolManager dbPoolManager = null;
private static final String POOLNAME = "dbpool";
private DBPoolDriver poolDriver = null;
private String url = null;
private String username = null;
private String password = null;
private static DriverStats stats = null;
protected DBPoolManager(){}
public void init(String url, String username, String password ){
this.url = url;
this.username = username;
this.password = password;
try{
this.poolDriver = new DBPoolDriver(POOLNAME);
}catch(Exception ex){
logger.error(ex.getMessage());
}
}
public static DBPoolManager getInstance(){
if(dbPoolManager == null){
synchronized(DBPoolManager.class){
if(dbPoolManager == null){
dbPoolManager = new DBPoolManager();
logger.info("Created a new singleton of DBPoolManager: "+dbPoolManager);
}
}
}
else{
logger.info("Created the same singleton of DBPoolManager: "+dbPoolManager);
}
return dbPoolManager;
}
public JdbcTemplate getJdbcTemplate(){
return poolDriver.getJdbcTemplate();
}
public Connection getConnection() throws Exception{
return poolDriver.getConnection();
}
public void setupPool() throws ClassNotFoundException, SQLException {
try{
poolDriver.setupDriver(url, username, password);
}
catch(Exception e){
logger.error(e.getMessage());
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMaxTotal() {
return Integer.parseInt(poolDriver.getConfig().getMaxtotal());
}
public void setMaxTotal(int maxTotal) {
poolDriver.getConfig().setMaxtotal(String.valueOf(maxTotal));
}
protected long getMaxWaitMillis() {
return Long.parseLong(poolDriver.getConfig().getMaxwaitmillis());
}
public void setMaxWaitMillis(long maxWaitMillis) {
poolDriver.getConfig().setMaxwaitmillis(String.valueOf(maxWaitMillis));
}
public int getMaxIdle() {
return Integer.parseInt(poolDriver.getConfig().getMaxidle());
}
public void setMaxIdle(int maxIdle) {
poolDriver.getConfig().setMaxidle(String.valueOf(maxIdle));
}
public int getNumActive() throws SQLException{
return poolDriver.getConnectionPool().getNumActive();
}
public int getNumIdle() throws SQLException{
return poolDriver.getConnectionPool().getNumIdle();
}
public void shutDownPool() throws SQLException{
poolDriver.close();
}
public void printDriverStats() throws Exception {
logger.info(Thread.currentThread().getName()+ " NumActive: " + dbPoolManager.getNumActive());
logger.info(Thread.currentThread().getName()+ " NumIdle: " + dbPoolManager.getNumIdle());
}
I have a problem with rollback transaction. Below I wrote some of the configuration of beans. I do 2 SQL-queries: delete and update. And when UPDATE generates exception (constraint of foreign ket), the first query (DELETE) does not rollback. Could anyone please tell me where the problem is? I wrote only some of the configuration for sake of clarity, so if it's needed more information please let me know. Thanks in adnvance!
CONTEXT:
I have DAO layer with method removeUser:
public void removeUser(final Long id) {
getHibernateTemplate().execute(new HibernateCallback() {
#Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
executeUpdate("delete from table1 where user_id = ?", session, id);
executeUpdate("update table2 set user_id = null where user_id = ?", session, id);
return null;
}
private void executeUpdate(String queryString, Session session, Long... params) {
SQLQuery query = session.createSQLQuery(queryString);
for (int paramIndex = 0; paramIndex < params.length; paramIndex++) {
Long param = params[paramIndex];
query.setParameter(paramIndex, param);
}
query.executeUpdate();
}
});
}
This method is called from within service:
public void removeUser(Long id) {
userDao.removeUser(id);
}
This service is configured via spring:
<bean name="adminUserService" parent="txProxyServiceTemplate">
... setting properties ...
</bean>
<bean id="txProxyServiceTemplate" abstract="true"
class="com.xalmiento.desknet.ui.server.service.transaction.GWTTransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="remove*">PROPAGATION_NESTED</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="nestedTransactionAllowed" value="true"/>
</bean>
Try with
<prop key="remove*">PROPAGATION_REQUIRED</prop>
I don't think all the database server does support the transaction.
I'm trying to invoke method based on some interval time, here are some beans inside applicationContext.xml
<bean id="MngtTarget"
class="com.management.engine.Implementation"
abstract="false" lazy-init="true" autowire="default" dependency-check="default">
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="MngtTarget" />
<property name="targetMethod" value="findItemByPIdEndDate"/>
</bean>
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<!-- 10 seconds -->
<property name="startDelay" value="10000" />
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="20000" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger" />
</list>
</property>
</bean>
Here is the method I'm trying to invoke :
public List<Long> I need findItemByPIdEndDate() throws Exception {
List<Long> list = null;
try{
Session session = sessionFactory.getCurrentSession();
Query query = session.getNamedQuery("endDateChecker");
list = query.list();
for(int i=0; i<list.size(); i++)
{
System.out.println(list.get(i));
}
System.out.println("Total " + list.size());
}catch (HibernateException e){
throw new DataAccessException(e.getMessage());
}
return list;
}
Here is the exception message that I get :
Invocation of method 'findItemByPIdEndDate' on target class [class com.management.engine.Implementation] failed; nested exception is No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
I've spent time googling alot so far also I've tried to modify my method like this :
public List<Long> I need findItemByPIdEndDate() throws Exception {
List<Long> list = null;
try{
Session session = sessionFactory.openSession();
Query query = session.getNamedQuery("endDateChecker");
list = query.list();
for(int i=0; i<list.size(); i++)
{
System.out.println(list.get(i));
}
System.out.println("Total " + list.size());
session.close();
}catch (HibernateException e){
throw new DataAccessException(e.getMessage());
}
return list;
}
And I get different error msg, I get : Invocation of method 'findItemByPIdEndDate' on target class [class com.management.engine.Implementation] failed; nested exception is could not execute query] , anyone knows what is this all about, any suggestions ? thank you
Also my queries.hbm.xml
<hibernate-mapping>
<sql-query name="endDateChecker">
<return-scalar column="PId" type="java.lang.Long"/>
<![CDATA[select
item_pid as PId
from
item
where
end_date < trunc(sysdate)]]>
</sql-query>
</hibernate-mapping>
For the second error ("could not execute the query"), I don't know and I'm really wondering what the session looks like.
In deed, AFAIK, the persistent context is not available to Quartz Jobs as nothing take care of establishing a Hibernate Session for them (Quartz runs outside the context of Servlets and the open session in view pattern doesn't apply here). This is why you get the first error ("No hibernate session bound to thread").
One solution for this is described in AOP – Spring – Hibernate Sessions for background threads / jobs. In this post, the author shows how you can use Spring AOP proxies to wire a hibernate interceptor that gives you access to the persistence context and it takes cares of closing and opening the sessions for you.
Didn't test it myself though, but it should work.
I too was facing the same "HibernateException: No Hibernate Session bound to thread" exception
2012-01-13 13:16:15.005 DEBUG MyQuartzJob Caught an exception
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)
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:687)
at com.company.somemodule.dao.hibernate.AbstractHibernateDaoImpl.getSession(AbstractHibernateDaoImpl.java:107)
at com.company.somemodule.dao.hibernate.SomeDataDaoImpl.retrieveSomeData(SomeDataDaoImpl.java:264)
and I solved it by following the example here.
Relevant code
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.company.somemodule.dao.SomeDataDao;
import com.company.somemodule.SomeData;
public class MyQuartzJob extends QuartzJobBean implements Runnable {
private boolean existingTransaction;
private JobExecutionContext jobExecCtx;
private static Logger logger = LoggerFactory.getLogger(MyQuartzJob.class);
private SomeDataDao someDataDao; //set by Spring
private Session session;
private SessionFactory hibernateSessionFactory; //set by Spring
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException
this.jobExecCtx = ctx;
run();
}
private void handleHibernateTransactionIntricacies() {
session = SessionFactoryUtils.getSession(hibernateSessionFactory, true);
existingTransaction = SessionFactoryUtils.isSessionTransactional(session, hibernateSessionFactory);
if (existingTransaction) {
logger.debug("Found thread-bound Session for Quartz job");
} else {
TransactionSynchronizationManager.bindResource(hibernateSessionFactory, new SessionHolder(session));
}
}
private void releaseHibernateSessionConditionally() {
if (existingTransaction) {
logger.debug("Not closing pre-bound Hibernate Session after TransactionalQuartzTask");
} else {
TransactionSynchronizationManager.unbindResource(hibernateSessionFactory);
SessionFactoryUtils.releaseSession(session, hibernateSessionFactory);
}
}
#Override
public void run() {
// ..
// Do the required to avoid HibernateException: No Hibernate Session bound to thread
handleHibernateTransactionIntricacies();
// Do the transactional operations
try {
// Do DAO related operations ..
} finally {
releaseHibernateSessionConditionally();
}
}
public void setHibernateSessionFactory(SessionFactory hibernateSessionFactory) {
this.hibernateSessionFactory = hibernateSessionFactory;
}
public void setSomeDataDao(SomeDataDao someDataDao ) {
this.someDataDao = someDataDao ;
}
}
Relevant bean configuration inside applicationContext.xml
<bean name="myJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.somecompany.worker.MyQuartzJob" />
<property name="jobDataAsMap">
<map>
<entry key="hibernateSessionFactory" value-ref="sessionFactory" />
<entry key="someDataDao" value-ref="someDataDao" />
</map>
</property>
</bean>
There's bug spring https://jira.spring.io/browse/SPR-9020
And there's workaround.
Configure session with hibernate.current_session_context_class property with this class:
https://gist.github.com/seykron/4770724