I have a Spring (core) 5.3.18 application configured with a mixed XML/annotation-driven approach.
To access the backing MariaDB, I use Hibernate core 5.6.7.Final and JPA (javax.persistence-api:2.2) with an HikariCP 4.0.3 connection pool.
The data source is an HikariDataSource with a default connection pool size of 10, and with the leakDetectionThreshold set to 6 seconds (transactions are really short).
The configured JpaTransactionManager fom spring uses as entityManagerFactory a LocalContainerEntityManagerFactoryBean that configures Hibernate via the HibernateJpaVendorAdapter.
At runtime, with just one thread performing DB operations everything works fine.
When multiple threads start requiring the DB at the same time though, threads get stuck on what seems like a starvation condition, all waiting to retrieve a connection.
HikariCP reports the following leak, reported 10 times for all of the connections available in the pool:
java.lang.Exception: Apparent connection leak detected
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
In my tests, for N threads running apparently I needed a connection pool size of exactly N*2 to avoid this behaviour, which led me to think that by some mistake (or expected behaviour unknown to me) the application I set up consumes two connections for every transaction, instead of just one.
This would explain why not even one request succeeds when all threads are sent requests at the same time, but it's just my guess: each of them acquires the first connection object at some point, and then when they try to acquire the second they all get stuck at the same time.
I really can't figure out what's happening behind Spring's and JPA's magic though. In my understanding of the documentation, a public method of a #Transactional class be wrapped in a spring proxy that gets the connection just before the transaction occurs, and closes it (actually causing the connection to return to the pool instead of bein phisically closed, see also When are connections returned to the connection pool with Spring JPA (Hibernate) Entity Manager?) after the transactin is committed/rolled-back.
Something's amiss, though. I would really appreciate any help or hint about what to do, I've been stuck on this for ages.
Below is the XML spring configuration. There are no additional persistence.xml nor hibernate.properties/cfg.xml.
<bean id="dataSourceHikari" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="org.mariadb.jdbc.Driver" />
<property name="jdbcUrl" value="${db.url}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.password}" />
<property name="dataSourceProperties">
<props>
<prop key="autoReconnect">true</prop>
<prop key="zeroDateTimeBehavior">convertToNull</prop>
</props>
</property>
<property name="validationTimeout" value="3000" />
<property name="readOnly" value="false" />
<property name="connectionTimeout" value="60000" />
<property name="maxLifetime" value="60000" />
<property name="maximumPoolSize" value="${db.maxPoolSize:10}" />
<property name="leakDetectionThreshold" value="6000" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSourceHikari" />
<property name="packagesToScan" value="my.application.package" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
My DB layer instead looks like this below.
Each application thread simply invokes DBLayerClass#createSession(String) on the #Autowired DBLayerClass myDBObj once for every incoming request.
import javax.persistence.EntityManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/* other stuff */
#Component
#Transactional(readOnly = false, rollbackFor = {RuntimeException.class, MyCustomExceptions.class})
public class DBLayerClass {
#PersistenceContext
private EntityManager entityManager;
public Session createSession(String sessionId) throws MyCustomExceptions {
try {
if (getSessionById(sessionId) != null)
throw new MyCustomExceptions("...");
Session session = new Session(sessionId);
entityManager.persist(session);
return session;
} catch (EntityExistsException e) {
throw new MyCustomExceptions("...", e);
} catch (PersistenceException e) {
throw new MyCustomExceptions("...", e);
}
}
private Session getSessionById(String sessionId) throws MyCustomExceptions {
try {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Session> cq = cb.createQuery(Session.class);
Root<Session> from = cq.from(Session.class);
cq.where(cb.equal(from.get("sessionId"), sessionId));
TypedQuery<Session> q = entityManager.createQuery(cq);
return q.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (PersistenceException e) {
throw new MyCustomExceptions("...", e);
}
}
}
The fields on my #Entity classes use #Id #GeneratedValue(strategy = GenerationType.SEQUENCE) annotations for Long primary keys, and other regular annotations such as #Column or #Temporal. Most fancy ones are collections with #OneToMany.
I re-wrote a simpler and basic test scenario, made to start a bunch of worker threads which keep sending db requests in a loop. A handful of createSession(...) might work at first, but the test starves soon enough and the above leaks are reported by HikariCP.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/META-INF/spring/dao-test.xml" })
public class MyTestClass {
#Autowired
private DBLayerClass db;
#Test
public void testConcurrentUsage() throws Exception {
Callable<Exception> c = new Callable<Exception>() {
private AtomicLong nextId = new AtomicLong(0);
#Override
public Exception call() throws Exception {
try {
long id;
while ((id = nextId.incrementAndGet()) < 100L) {
db.createSession(String.format("session-%d", id));
}
return null;
} catch (Exception e) {
return e;
}
}
};
final int nThreads = 30;
Thread[] threads = new Thread[nThreads];
ArrayList<Future<Exception>> threadResults = new ArrayList<>(nThreads);
for (int i = 0; i < threads.length; i++) {
FutureTask<Exception> threadResult = new FutureTask<>(c);
threadResults.add(threadResult);
threads[i] = new Thread(threadResult);
threads[i].start();
}
for (Future<Exception> result : threadResults) {
Exception e = result.get();
if (e != null) {
for (Thread thread : threads) {
thread.stop();
}
throw e;
}
}
}
Finally, these below are the dependencies:
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.7.Final</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.23.Final</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
Related
I am doing a simple POC to write a large volume of entries to ignite cache following client server mode, observed below during testing;
1) If thin client and server reside on same host , it takes around ~10 minutes to persist 1 million entries into two cache.
2) If thin client and server reside on different hosts , it takes around ~4 minutes to persist just 500 entries into two cache.This looks very bad.
I am not able to justify this significant delay in case2(mode we want to adopt for implementation) even if we take some network latency into account.I am wondering if its something to do with my cache configuration which is as below?
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="workDirectory" value="/path/"/>
<property name="activeOnStart" value="true"/>
<property name="autoActivationEnabled" value="true"/>
<property name="deploymentMode" value="SHARED"/>
<property name="igniteInstanceName" value="test"/>
<property name="dataStorageConfiguration">
<bean class="org.apache.ignite.configuration.DataStorageConfiguration">
<property name="defaultDataRegionConfiguration">
<bean class="org.apache.ignite.configuration.DataRegionConfiguration">
<property name="persistenceEnabled" value="true"/>
</bean>
</property>
<property name="storagePath" value="/path/"/>
</bean>
</property>
<!--
For better performance set this property to false in case
peer deployment is not used.
Default value is true.
-->
<property name="peerClassLoadingEnabled" value="false"/>
<property name="cacheConfiguration">
<!--
Specify list of cache configurations here. Any property from
CacheConfiguration interface can be configured here.
Note that absolutely all configuration properties are optional.
-->
<list>
<bean parent="cache-template">
<!-- Cache name is 'testcache1'. -->
<property name="name" value="testcache1"/>
</bean>
<bean parent="cache-template">
<!-- Cache name is 'testcache2'. -->
<property name="name" value="testcache2"/>
</bean>
</list>
</property>
</bean>
<!-- Template for all example cache configurations. -->
<bean id="cache-template" abstract="true" class="org.apache.ignite.configuration.CacheConfiguration">
<!-- REPLICATED cache mode. -->
<property name="cacheMode" value="REPLICATED"/>
<!-- Set synchronous rebalancing (default is asynchronous). -->
<property name="rebalanceMode" value="SYNC"/>
<!-- Set to FULL_SYNC for examples, default is PRIMARY_SYNC. -->
<property name="writeSynchronizationMode" value="FULL_SYNC"/>
<property name="atomicityMode" value="TRANSACTIONAL"/>
</bean>
Thin Client Code:
public class IgniteDataGridApplication {
static DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private ClientCache<String, String> testcache1;
private ClientCache<String, String> testcache2;
public IgniteDataGridApplication() {
ClientConfiguration cfg = new ClientConfiguration().setAddresses("serverhostname.net:10800");
IgniteClient ignite = Ignition.startClient(cfg);
testcache1 = ignite.cache("testcache1");
testcache2 = ignite.cache("testcache2");
}
public static void main(String[] args) throws Exception {
IgniteDataGridApplication igniteDataGridApplication = new IgniteDataGridApplication();
igniteDataGridApplication.load();
}
private void load() throws Exception {
List<ThreadProducer> cacheMessages = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
String testentry = i+"";
cacheMessages.add(new ThreadProducer("testKey" + i, testentry));
}
ExecutorService executorService = Executors.newFixedThreadPool(1000);
cacheMessages.forEach(executorService::submit);
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
class ThreadProducer implements Runnable {
private String String;
private String key;
public ThreadProducer(String key, String String) {
this.key = key;
this.String = String;
}
public void run() {
testcache1.putIfAbsent(key, String);
testcache2.putIfAbsent(key, String);
System.out.println("entry :: " + key + " :: " + sdf.format(Calendar.getInstance().getTime()));
}
}
}
Try profiling server nodes and the thin clients using JFR, JProfiler or another profiler of your choice to find a bottleneck that slows down the operation.
Make sure that in both cases the same number of nodes are present in the baseline topology. With improper configuration of the baseline topology data may be loaded only to one of the nodes.
You can try using API methods that load data in batches to improve the performance. ClientCache#putAll() is one of such methods.
Before it work but now it is not working.And previously when it worked i use to get session Null in CrediCarddao.java for updating the customer status and inserting new creditcard record into database.
Apr 14, 2017 9:32:36 AM org.springframework.context.support.FileSystemXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.FileSystemXmlApplicationContext#68de145: startup date [Fri Apr 14 09:32:36 CDT 2017]; root of context hierarchy
Apr 14, 2017 9:32:36 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from file [C:\BCJ_DEC_2016\workspace\CoreJava\creditcardprocess\spring.xml]
Apr 14, 2017 9:32:37 AM org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
INFO: Looking for #ControllerAdvice: org.springframework.context.support.FileSystemXmlApplicationContext#68de145: startup date [Fri Apr 14 09:32:36 CDT 2017]; root of context hierarchy
Apr 14, 2017 9:32:37 AM org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
INFO: Looking for #ControllerAdvice: org.springframework.context.support.FileSystemXmlApplicationContext#68de145: startup date [Fri Apr 14 09:32:36 CDT 2017]; root of context hierarchy
Apr 14, 2017 9:32:37 AM org.springframework.context.support.FileSystemXmlApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'creditCardDao' defined in file [C:\BCJ_DEC_2016\workspace\CoreJava\creditcardprocess\target\classes\com\bcj\creditcardprocess\dao\CreditCardDao.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bcj.creditcardprocess.dao.CreditCardDao]: Constructor threw exception; nested exception is java.lang.NullPointerException
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'creditCardDao' defined in file [C:\BCJ_DEC_2016\workspace\CoreJava\creditcardprocess\target\classes\com\bcj\creditcardprocess\dao\CreditCardDao.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bcj.creditcardprocess.dao.CreditCardDao]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1155)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1099)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.context.support.FileSystemXmlApplicationContext.(FileSystemXmlApplicationContext.java:140)
at org.springframework.context.support.FileSystemXmlApplicationContext.(FileSystemXmlApplicationContext.java:84)
at com.bcj.creditcardprocess.CreditCardMain.main(CreditCardMain.java:15)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bcj.creditcardprocess.dao.CreditCardDao]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:154)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:89)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1147)
... 13 more
Caused by: java.lang.NullPointerException
at com.bcj.creditcardprocess.dao.CreditCardDao.(CreditCardDao.java:22)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:142)
... 15 more
CreditCardMain.java*
package com.bcj.creditcardprocess;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.bcj.creditcardprocess.service.CreditCardService;
public class CreditCardMain {
public static void main(String[] args) {
#SuppressWarnings("resource")
ApplicationContext context = new FileSystemXmlApplicationContext("spring.xml");
CreditCardService obj = (CreditCardService) context.getBean("cCardService");
//CreditCardService cCardService = new CreditCardService();
obj.processCreditCard();
}
}
CreditCardService.java
package com.bcj.creditcardprocess.service;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bcj.creditcardprocess.dao.CreditCardDao;
import com.bcj.creditcardprocess.model.Customer;
#Service
public class CreditCardService {
#Autowired
private CreditCardDao cCardDao;
public void setcCardDao(CreditCardDao cCardDao) {
this.cCardDao = cCardDao;
}
public void processCreditCard() {
List<Customer> customerList = cCardDao.getCustomerList();
// creating a pool of 5 threads
ExecutorService executor = Executors.newFixedThreadPool(5);
for (Iterator iterator = customerList.iterator(); iterator.hasNext();) {
Customer cust = (Customer) iterator.next();
System.out.println(cust);
WorkerThread thread = new WorkerThread();
thread.threadmain(customerList);
//Runnable worker = new WorkerThread(cust);
// calling execute method of ExecutorService
//executor.execute(worker);
/*WorkerThread thread = new WorkerThread();
thread.threadmain(customerList);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");*/
/*
* List<Object> result = (List<Object>) customerList; Iterator itr =
* result.iterator(); while(itr.hasNext()){ Object[] obj = (Object[])
* itr.next();
*
* System.out.println(String.valueOf(obj[0])+" "+String.valueOf(obj[1]
* ));
*
*/
/*
* for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread(obj
* ); executor.execute(worker);//calling execute method of
* ExecutorService }
*/
/*executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");*/
}
}
}
WorkerThread.java
package com.bcj.creditcardprocess.service;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bcj.creditcardprocess.dao.CreditCardDao;
import com.bcj.creditcardprocess.model.CreditCard;
import com.bcj.creditcardprocess.model.Customer;
#Service
public class WorkerThread implements Runnable {
#Autowired
private CreditCardDao cCardDao;
public void setcCardDao(CreditCardDao cCardDao) {
this.cCardDao = cCardDao;
}
public WorkerThread() {
}
WorkerThread(Customer cust2) {
this.cust = cust2;
}
Customer cust;
public void run() {
System.out.println(Thread.currentThread().getName() + " (Start) message = " + cust.getFirstName());
try {
getCutomerDetailsByTextFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " (End)");
}
public synchronized void getCutomerDetailsByTextFile() throws IOException {
FileReader fr = new FileReader("details.txt");
BufferedReader br = new BufferedReader(fr);
String line = "";
while ((line = br.readLine()) != null) {
System.out.println(line);
String[] details = line.split(" ");
if (details[0].equalsIgnoreCase(cust.getFirstName()) && details[1].equalsIgnoreCase(cust.getLastName())
&& details[2].equals(cust.getSsn())) {
System.out.println(" ssn had");
if (Integer.parseInt(details[3]) > 700 && Integer.parseInt(cust.getAnnualIncome()) > 100000) {
CreditCard card = new CreditCard();
card.setCreditLimit(5000);
card.setCustomerId(cust.getId());
generateCardNumber(card, cust);
cust.setStatus("Approved");
} else if (Integer.parseInt(details[3]) > 600 && Integer.parseInt(details[3]) < 700
&& Integer.parseInt(cust.getAnnualIncome()) > 70000) {
CreditCard card = new CreditCard();
card.setCreditLimit(3000);
card.setCustomerId(cust.getId());
generateCardNumber(card, cust);
cust.setStatus("Approved");
} else {
cCardDao.updateCustomer(cust);
cust.setStatus("Declined");
}
break;
}
}
}
public void threadmain(List<Customer> customerList) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (Iterator iterator = customerList.iterator(); iterator.hasNext();) {
Customer cust = (Customer) iterator.next();
Runnable worker = new WorkerThread(cust);
executor.execute(worker);// calling execute method of
// ExecutorService
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
private void generateCardNumber(CreditCard cCard, Customer cust) {
Random rand = new Random();
String cardNumber = Integer.toString(rand.nextInt(9999) + 1000) + Integer.toString(rand.nextInt(9999) + 1000)
+ Integer.toString(rand.nextInt(9999) + 1000) + Integer.toString(rand.nextInt(9999) + 1000);
String cvv = Integer.toString(rand.nextInt(999) + 100);
Calendar cal = Calendar.getInstance();
Date today = cal.getTime();
cal.add(Calendar.YEAR, 3); // to get previous year add -1
Date next = cal.getTime();
SimpleDateFormat adf = new SimpleDateFormat("MM/YY");
String expiryDate = adf.format(next);
cCard.setCardNumber(cardNumber);
cCard.setCvv(cvv);
cCard.setExpiryDate(expiryDate);
cCardDao.persistCreditCard(cCard, cust);
}
}
CreditCardDao.java
package com.bcj.creditcardprocess.dao;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.bcj.creditcardprocess.model.CreditCard;
import com.bcj.creditcardprocess.model.Customer;
#Repository
#Transactional
public class CreditCardDao {
#Autowired
private SessionFactory sessionFactory;
private Session session = sessionFactory.getCurrentSession();
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
// Session session = sessionFactory.getCurrentSession();
List<Customer> custs;
public List<Customer> getCustomerList() {
Transaction tx = session.beginTransaction();
List custs = session.createQuery("FROM Customer WHERE status='New'").list();
return custs;
}
public void persistCreditCard(CreditCard cCard, Customer cust) {
int x = cCard.getCustomerId();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.persist(cCard);
session.update(cust);
System.out.println("customer saved sucessfully" + cCard);
}
public void updateCustomer(Customer cust) {
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.update(cust);
}
}
Spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<mvc:annotation-driven />
<context:component-scan base-package="com.bcj.creditcardprocess" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/views/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="cCardService" class="com.bcj.creditcardprocess.service.CreditCardService" />
<bean id="cCardDao" class="com.bcj.creditcardprocess.dao.CreditCardDao" />
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="dataSource" 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/citibank" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<!--Hibernate 4 SessionFactory Bean definition -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>com.bcj.creditcardprocess.model.CreditCard</value>
<value>com.bcj.creditcardprocess.model.Customer</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.hbm2ddl.auto">update
</prop>
<prop key="hibernate.show_sql">true</prop>
<!-- <prop key="hibernate.current_session_context_class">thread</prop> -->
</props>
</property>
</bean>
</beans>
POM.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bcj</groupId>
<artifactId>creditcardprocess</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>creditcardprocess</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.3.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
</dependencies>
</project>
Maybe try to change
private Session session = sessionFactory.getCurrentSession();
to a function
public Session getSession() {
return sessionFactory.getCurrentSession();
}
I am thinking during the class initialization, the class is trying initialize private Session session. And sessionFactory is not ready yet.
Your getSessionFactory() in CreditCardDao.java is throwing the nullPointerExceptionbecause You haven't initialized the SessionFactory so it's initialy pointing to null.
You would do that like this:
private SessionFactory sessionFactory=//build sessionfactory code
Note that the SessionFactoryshould be created once only, you can use the singleton design pattern for that.
I'm trying create some kind of integration test environment and encountered with issue when Test Context Framework doesn't create transaction proxies for my beans. My code:
JUnit class: FileServiceImplTest
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(value = "file:src/main/webapp/WEB-INF/spring/applicationContext-db.xml"),
#ContextConfiguration(value = "file:src/main/webapp/WEB-INF/spring/applicationContext.xml")
})
public class FileServiceImplTest {
#Autowired
private FileService fileService;
#Test
public void testSaveFolder() {
FolderDTO rootFolder = new FolderDTO();
rootFolder.setName("ROOT");
rootFolder.setParentId(null);
rootFolder.setIdPath("/");
rootFolder.setPath("/");
fileService.saveFile(rootFolder);
List<AbstractFileDTO> rootFiles = fileService.getRootFiles();
assertEquals(1, rootFiles.size());
AbstractFileDTO abstractFileDTO = rootFiles.get(0);
assertEquals(rootFolder, abstractFileDTO);
}
}
Test context frame work inject into 'fileService' filed the FileService bean itself, not transaction proxy. It is reason of exception:
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:132)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:697)
at org.sbezgin.p2016.db.dao.impl.FileDAOImpl.getSession(FileDAOImpl.java:69)
at org.sbezgin.p2016.db.dao.impl.FileDAOImpl.saveOrUpdateFile(FileDAOImpl.java:33)
at org.sbezgin.p2016.services.file.impl.FileServiceImpl.saveFile(FileServiceImpl.java:41)
at org.sbezgin.p2016.services.file.impl.FileServiceImplTest.testSaveFolder(FileServiceImplTest.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
When I run this code under tomcat everything works fine, issue is appear only during running tests. Please help to fix this.
Rest of my code:
FileServiceImpl
#Transactional
public class FileServiceImpl implements FileService {
private FileDAO fileDAO;
private BeanTransformer beanTransformer;
#Override
public AbstractFileDTO getFileByID(long fileID) {
return null;
}
#Override
public FolderDTO getFolder(long folderID) {
return null;
}
#Transactional(propagation = Propagation.REQUIRED)
#Override
public void saveFile(AbstractFileDTO file) {
Long id = file.getId();
if (id == null) {
AbstractFile fileEntity = (AbstractFile) beanTransformer.transformDTOToEntity(file);
User user = new User();
user.setId(1);
fileDAO.saveOrUpdateFile(user, fileEntity);
}
}
#Override
public void setPermission(long fileD, PermissionDTO perm) {
}
#Override
public void renameFile(long fileID, String newName) {
}
#Override
public void deleteFile(long fileID, boolean recursively) {
}
#Override
public List<AbstractFileDTO> getRootFiles() {
User user = new User();
user.setId(1);
List<AbstractFile> rootFiles = fileDAO.getRootFiles(user);
List<AbstractFileDTO> abstractFileDTOs = new ArrayList<>(rootFiles.size());
abstractFileDTOs.addAll(
rootFiles.stream().map(
rootFile -> (AbstractFileDTO) beanTransformer.transformEntityToDTO(rootFile)
).collect(Collectors.toList())
);
return abstractFileDTOs;
}
#Override
public List<AbstractFileDTO> getChildren(long folderID) {
return null;
}
#Override
public List<AbstractFileDTO> getFilesByType(String javaType) {
return null;
}
public FileDAO getFileDAO() {
return fileDAO;
}
public void setFileDAO(FileDAO fileDAO) {
this.fileDAO = fileDAO;
}
public BeanTransformer getBeanTransformer() {
return beanTransformer;
}
public void setBeanTransformer(BeanTransformer beanTransformer) {
this.beanTransformer = beanTransformer;
}
}
FileDAOImpl.java
public class FileDAOImpl implements FileDAO {
private SessionFactory sessionFactory;
#Override
public AbstractFile getFileByID(User user, long fileID) {
return null;
}
#Override
public Folder getFolder(User user, long folderID) {
return null;
}
#Transactional(propagation = Propagation.REQUIRED)
#Override
public void saveOrUpdateFile(User user, AbstractFile file) {
Session session = getSession();
file.setClassName(file.getClass().getCanonicalName());
file.setOwnerID(user.getId());
session.save(file);
}
#Override
public void saveOrUpdateFiles(User user, List<AbstractFile> files) {
}
#Override
public void deleteFile(User user, long fileID, boolean recursively) {
}
#Override
public List<AbstractFile> getRootFiles(User user) {
Session session = getSession();
Query query = session.createQuery("from AbstractFile as file where file.ownerID = :ownerId and file.parentId is null ");
query.setParameter("ownerId", user.getId());
List list = query.list();
return list;
}
#Override
public List<AbstractFile> getChildren(User user, long folderID) {
return null;
}
#Override
public List<AbstractFile> getFilesByType(User user, String javaType) {
return null;
}
private Session getSession() {
return sessionFactory.getCurrentSession();
}
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean name="dozer" class="org.dozer.DozerBeanMapper" />
<bean name="beanTransformer" class="org.sbezgin.p2016.services.impl.BeanTransformerImpl">
<property name="dozerBeanMapper" ref="dozer"/>
<property name="beanMap">
<map>
<entry key="org.sbezgin.p2016.db.dto.file.FolderDTO" value="org.sbezgin.p2016.db.entity.file.Folder"/>
</map>
</property>
</bean>
<!-- services -->
<bean name="fileService" class="org.sbezgin.p2016.services.file.impl.FileServiceImpl">
<property name="fileDAO" ref="fileDAO" />
<property name="beanTransformer" ref="beanTransformer"/>
</bean>
<!-- dao -->
<bean name="fileDAO" class="org.sbezgin.p2016.db.dao.impl.FileDAOImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
applicationContext-db.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:property-placeholder location="WEB-INF/hibernate.properties" ignore-unresolvable="false"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<list>
<value>org.sbezgin.p2016.db.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql:false}</prop>
<prop key="hibernate.id.new_generator_mappings">${hibernate.id.new_generator_mappings}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
hibernate.properties
jdbc.driverClassName = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://localhost:9001/xdb
jdbc.username = sa
jdbc.password =
hibernate.dialect = org.hibernate.dialect.HSQLDialect
hibernate.show_sql = true
hibernate.format_sql = true
hibernate.id.new_generator_mappings = false
hibernate.hbm2ddl.auto = create-drop
pom.xml
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.7.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!-- TEST artifacts -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.8</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>p2016</finalName>
<plugins>
<plugin>
<!-- current version -->
<groupId>fr.avianey.mojo</groupId>
<artifactId>hsqldb-maven-plugin</artifactId>
<version>1.0.0</version>
<!--
default value for in memory jdbc:hsqldb:hsql://localhost/xdb
override only values you want to change
-->
<configuration>
<driver>org.hsqldb.jdbcDriver</driver>
<address>localhost</address>
<port>9001</port>
<name>xdb</name>
<username>sa</username>
<password></password>
<validationQuery>SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS</validationQuery>
</configuration>
<!-- call start and stop -->
<executions>
<execution>
<id>start-hsqldb</id>
<phase>process-test-classes</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-hsqldb</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
I found that I had to add #EnableTransactionManagement to my Configuration. Prior to that Spring wasn't running the SpringTransactionAnnotationParser and adding the proxy transaction methods to my bean. I only added it to one test because it will make all of the tests run differently than they test framework wants to (managing transactions itself and rolling them back auto-magically)
Based on Spring Framework's documentation:
In the TestContext framework, transactions are managed by the
TransactionalTestExecutionListener which is configured by default,
even if you do not explicitly declare #TestExecutionListeners on your
test class. To enable support for transactions, however, you must
configure a PlatformTransactionManager bean in the ApplicationContext
that is loaded via #ContextConfiguration semantics. In addition, you must declare Spring’s
#Transactional annotation either at the class or method level for your
tests.
You did define a PlatformTransactionManager in your applicationContext-db.xml but did not add a #Transactional on your FileServiceImplTest class or testSaveFolder method. Add the #Transactional on your test class or methods.
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 running an Apache CXF web service under spring. I use JPA to persist the information. The service has a method that updates a series of rows. Before persisting each row, I check that the values to be persisted really exist in the databes. If there is a value that does not exists, then an Exception is thrown. The problem is I need to rollback al the values updated. I though that using #Transactional in my web service method would do the trick, but instead of that, the values that got persisted are really modified in the database, wich is not the desired behavior.
This is the code of the web service method
#Transactional( propagation = Propagation.REQUIRED )
public UpdateDescriptionResponse updateDescription(UpdateDescriptionRequest updateDescriptionRequest) throws SIASFaultMessage {
try {
SubstanceEntity substance = service.findSubstanceBySubstanceID(updateDescriptionRequest.getUpdateDescriptionRequestData().getIdentity().getSubstanceID());
if (substance!=null){
for(DescriptionKeyValueType keyValue: updateDescriptionRequest.getUpdateDescriptionRequestData().getSubstanceDescriptionData() ){
boolean descriptionExists = false;
for(DescriptionEntity desc: substance.getDescriptionsById()){
if (desc.getDescKey().equals(keyValue.getKey())) {
descriptionExists = true;
break;
}
}
if (!descriptionExists){
SIASFaultDetail faultDetail = new SIASFaultDetail();
faultDetail.setSIASFaultDescription("Description key does not match given substance ID");
faultDetail.setSIASFaultMessage(SIASFaultCode.INVALID_INPUT.toString());
faultDetail.setSIASFaultType(SIASFaultCode.INVALID_INPUT);
SIASFaultMessage fault = new SIASFaultMessage("Description key does not match given substance ID", faultDetail);
throw fault;
}
else
descriptionLogic.updateDescription(substance.getSubstanceId(), keyValue.getKey(),keyValue.getValue());
}
UpdateDescriptionResponse response = new UpdateDescriptionResponse();
UpdateDescriptionResponse.UpdateDescriptionResponsePackage responsePackage = new UpdateDescriptionResponse.UpdateDescriptionResponsePackage();
ResponseStatus status = new ResponseStatus();
status.setMessage(messageOk);
status.setReturn(BigInteger.valueOf(0));
responsePackage.setResponseStatus(status);
response.setUpdateDescriptionResponsePackage(responsePackage);
return response;
}
else
{
SIASFaultDetail faultDetail = new SIASFaultDetail();
faultDetail.setSIASFaultDescription("Substance ID does not exists");
faultDetail.setSIASFaultMessage(SIASFaultCode.INVALID_SUBSTANCE_ID.toString());
faultDetail.setSIASFaultType(SIASFaultCode.INVALID_SUBSTANCE_ID);
SIASFaultMessage fault = new SIASFaultMessage("Substance ID does not exists", faultDetail);
throw fault;
}
} catch (SIASFaultMessage ex) {
throw ex;
} catch (Exception ex) {
SIASFaultDetail a = new SIASFaultDetail();
a.setSIASFaultDescription("Unknown error processing enroll request");
a.setSIASFaultMessage("SERVICE_ERROR");
a.setSIASFaultType(SIASFaultCode.UNKNOWN_ERROR);
SIASFaultMessage fault = new SIASFaultMessage("Something happened", a);
throw fault;
}
}
This is the code for the instance of descriptionLogic.updateDescription(...)
#Override
public void updateDescription(String substanceID, String key, String value) {
PageRequest page = new PageRequest(1, 1);
Map<String, Object> filters = new HashMap<String, Object>();
filters.put("SUBSTANCE_ID", substanceID);
List<SubstanceEntity> substances = substanceService.findAll(page, filters);
if (substances.size() == 0) {
return;
}
SubstanceEntity substanceEntity = substances.get(0);
for (DescriptionEntity desc : substanceEntity.getDescriptionsById()) {
if (desc.getDescKey().equals(key)) {
desc.setDescValue(value);
descriptionService.persist(desc);
}
}
}
This is the test that fails
#Test()
public void testUpdateDescription_does_not_modify_description_with_invalid_values() throws Exception {
UpdateDescriptionRequest request = new UpdateDescriptionRequest();
UpdateDescriptionRequest.UpdateDescriptionRequestData data = new UpdateDescriptionRequest.UpdateDescriptionRequestData();
SIASIdentity identity = new SIASIdentity();
identity.setSubstanceID("804ab00f-d5e9-40ff-a4d3-11c51c2e7479");
data.getSubstanceDescriptionData().add(new DescriptionKeyValueType() {{
setKey("KEY3_1");
setValue("NEW_VALUE_1");
}});
data.getSubstanceDescriptionData().add(new DescriptionKeyValueType() {{
setKey("KEY3_5");
setValue("NEW_VALUE_2");
}});
data.setIdentity(identity);
request.setUpdateDescriptionRequestData(data);
try {
siasService.updateDescription(request);
}
catch (SIASFaultMessage ex){
}
DescriptionEntity descriptionEntity1 = descriptionService.findById(1);
DescriptionEntity descriptionEntity2 = descriptionService.findById(2);
assertThat("The value does not math",descriptionEntity1.getDescValue(), not(equalTo("NEW_VALUE_1")));
assertThat("The value does not math",descriptionEntity2.getDescValue(), not(equalTo("NEW_VALUE_2")));
Assert.assertEquals("The description does not math","KEY3_1", descriptionEntity1.getDescKey());
Assert.assertEquals("The description does not math","KEY3_2", descriptionEntity2.getDescKey());
}
It fails in this line:
assertThat("The value does not math",descriptionEntity1.getDescValue(), not(equalTo("NEW_VALUE_1")));
This is my datasource configuration in my spring context configuration file
.
.
.
<bean id="myDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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>
<jdbc:initialize-database data-source="myDataSource">
<jdbc:script location="classpath:test-data.sql" />
</jdbc:initialize-database>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="packagesToScan" value="cu.jpa"/>
<property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
<property name="jpaDialect">
<bean class="cu.jpa.specifications.IsolationSupportHibernateJpaDialect" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">${hdm2ddl.auto}</prop>
</props>
</property>
<property value="/META-INF/persistence.xml" name="persistenceXmlLocation"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven/>
.
.
.
This is my persistence.xml file content:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="NewPersistenceUnit">
<class>cu.jpa.entities.PatternEntity</class>
.
.
.
<class>cu.jpa.entities.TraceRegEntity</class>
</persistence-unit>
</persistence>
Extract of the test class:
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/repositories.xml"})
public class ServiceImplUpdateDescriptionTest {
.
.
.
#Test()
public void testUpdateDescription_does_not_modify_description_with_invalid_values() throws Exception{
.
.
.
}
}
Spring will only rollback the transaction if it is an unchecked exception, if the exception is a checked exception then you will have to add that to your #Transactional annotation.
#Transactional(rollbackFor = SIASFaultMessage.class)