I am using ehcache-2.9.0 with Hibernate 4.3.5, i have configured it properly and EHCache is working fine in the following cache concurrency strategies :
READ_ONLY
NONSTRICT_READ_WRITE
TRANSACTIONAL
But its not working for READ_WRITE,
the
Statistics stats = sessionFactory.getStatistics();
stats.getSecondLevelCacheHitCount()) is always 0 in case of READ_WRITE. I am using it in simple JAVA Project. Please go through the code and configuration files.
Please help.
ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
monitoring="autodetect" dynamicConfig="true">
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000"
memoryStoreEvictionPolicy="LRU"
statistics="true">
<persistence strategy="localTempSwap" />
</defaultCache>
<cache name="com.slc.entities.Movie"
maxEntriesLocalHeap="100"
maxEntriesLocalDisk="100"
eternal="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>
<cache
name="org.hibernate.cache.StandardQueryCache"
maxEntriesLocalHeap="5"
eternal="false"
timeToLiveSeconds="120">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:oracle:thin:#localhost:1521:xe</property>
<property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="connection.username">usr</property>
<property name="connection.password">pwd</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
<property name="show_sql">true</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- Generate Statistics about the Second Level Cache. -->
<property name="hibernate.generate_statistics">true</property>
<mapping resource="com/slc/entities/Movie.hbm.xml" />
<class-cache usage="read-write" class="com.slc.entities.Movie"/>
</session-factory>
</hibernate-configuration>
Movie.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.slc.entities">
<class name="Movie" table="MOVIE" mutable="false">
<cache usage="read-write" region="com.slc.entities.Movie" />
<id name="movieId" column="MOVIE_ID">
<generator class="increment"/>
</id>
<property name="movieName" column="MOVIE_NAME"/>
<property name="genere" column="GENERE"/>
<property name="releaseYear" column="RELEASE_YEAR"/>
</class>
</hibernate-mapping>
TestClass
public class SLCTest {
public static void main(String[] args) {
Movie movie = null;
try {
// Initialize Sessions
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Statistics stats = sessionFactory.getStatistics();
System.out.println("Stats enabled=" + stats.isStatisticsEnabled());
Session session = sessionFactory.openSession();
Session otherSession = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Transaction otherTransaction = otherSession.beginTransaction();
printStats(stats, 0);
movie = (Movie) session.load(Movie.class, 1);
printData(movie, stats, 1);
movie = (Movie) session.load(Movie.class, 1);;
printData(movie, stats, 2);
// clear first level cache, so that second level cache is used
session.evict(movie);
movie = (Movie) session.load(Movie.class, 1);
printData(movie, stats, 3);
movie = (Movie) session.load(Movie.class, 3);
printData(movie, stats, 4);
transaction.commit();
session.close();
movie = (Movie) otherSession.load(Movie.class, 1);
printData(movie, stats, 5);
// Release resources
otherTransaction.commit();
sessionFactory.close();
} finally {
HibernateUtil.closeSessionFactory();
}
}
private static void printStats(Statistics stats, int i) {
System.out.println("***** " + i + " *****");
System.out.println("Fetch Count=" + stats.getEntityFetchCount());
System.out.println("Second Level Hit Count="
+ stats.getSecondLevelCacheHitCount());
System.out.println("Second Level Miss Count="
+ stats.getSecondLevelCacheMissCount());
System.out.println("Second Level Put Count="
+ stats.getSecondLevelCachePutCount());
System.out.println("****** end stats ******");
}
private static void printData(Movie movie, Statistics stats, int count) {
System.out.println(movie);
System.out.println(movie.getMovieName());
printStats(stats, count);
}
}
You caching is working perfectly. The thing is that the movie was evicted. So even though it is in Ehcache, Hibernate won't return it because it isn't eligible. It was evicted.
Same thing is you interleave two sessions. Something loaded in cache by one session can't be used until the session is finished.
However, if you have two separate sessions, you will see a cache hit. See the code below.
Movie movie;
try {
// Initialize Sessions
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Statistics stats = sessionFactory.getStatistics();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
movie = (Movie) session.load(Movie.class, 1);
printData(movie, stats, 1);
transaction.commit();
session.close();
Session otherSession = sessionFactory.openSession();
Transaction otherTransaction = otherSession.beginTransaction();
movie = (Movie) otherSession.load(Movie.class, 1);
printData(movie, stats, 3);
otherTransaction.commit();
otherSession.close();
} finally {
HibernateUtil.closeSessionFactory();
}
Related
Hi I am using the EHCache to cache the SQL results from some Hibernate Native Queries in a JPA app. I am wondering how I can set for a specific query how long the results should should be cached? i.e. to 24h but for other queries not?
Hibernate Properties
hibernate.dialect=org.hibernate.dialect.PostgreSQL95Dialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=none
hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
EHCache Config XML
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
<persistence strategy="localTempSwap"/>
/>
</ehcache>
Java Method in a Spring Repository Class
#Repository
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AnalyticsRepositoryImpl implements AnalyticsRepository {
public Map<Long, Long> getAgeStatistic(boolean onlyPaid) {
StringBuilder sb = new StringBuilder();
sb.append("SELECT (date_part('year',current_date) - date_part('year',a.date_value)) as age, COUNT(u.id) as count from answers a ");
sb.append("JOIN ...");
...
Query q = em.createNativeQuery(sb.toString());
// enable the cache for the native query
NativeQuery nativeQuery = q.unwrap(NativeQuery.class);
nativeQuery.addScalar("age", IntegerType.INSTANCE);
nativeQuery.addScalar("count", LongType.INSTANCE);
nativeQuery.setCacheable(true);
List<Object[]> result = q.getResultList();
// Place results in map
Map<Long, Long> map = convertResultSetToLongKeyMap(result);
return map;
}
I found the answer, the solution is for my problem to use "cache regions". so you can define a own region for your queries
e.g. set the "analytics" cache region
#Repository
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AnalyticsRepositoryImpl implements AnalyticsRepository {
public Map<Long, Long> getAgeStatistic(boolean onlyPaid) {
StringBuilder sb = new StringBuilder();
sb.append("SELECT (date_part('year',current_date) - date_part('year',a.date_value)) as age, COUNT(u.id) as count from answers a ");
sb.append("JOIN ...");
...
Query q = em.createNativeQuery(sb.toString());
// enable the cache for the native query
NativeQuery nativeQuery = q.unwrap(NativeQuery.class);
nativeQuery.addScalar("age", IntegerType.INSTANCE);
nativeQuery.addScalar("count", LongType.INSTANCE);
nativeQuery.setCacheable(true);
nativeQuery.setCacheRegion("analytics");
List<Object[]> result = q.getResultList();
// Place results in map
Map<Long, Long> map = convertResultSetToLongKeyMap(result);
return map;
}
and in the ehcache configuration
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="analytics"
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="6000"
timeToLiveSeconds="6000">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
When I try to persist an object nothing happens (Exception, error, etc) and I didn't find the cause.
My suspect is that the problem is the transactional control of Spring, because the queries work fine.
I'm using Spring 3.2 with JPA 2 and the JPA implementation is the Hibernate 4.2.18.
Entity
#Entity
#Table(name = "DOLAR")
#NamedQueries({
#NamedQuery(name = Dolar.FIND_ALL, query = "SELECT d FROM Dolar d"),
#NamedQuery(name = Dolar.FIND_BY_EMPRESA, query = "SELECT d FROM Dolar d WHERE d.empresa = :e")
})
public class Dolar implements Serializable, AbstractEntity {
#Transient
private static final long serialVersionUID = 1L;
#Transient
public static final String FIND_BY_EMPRESA = "Dolar.findByEmpr";
#Transient
public static final String FIND_ALL = "Dolar.findAll";
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name="ID")
private Long id;
#OneToOne
#JoinColumn(name = "EMPRESA_ID")
private Empresa empresa;
#Column(name = "VALOR")
private BigDecimal valor;
public Dolar() {
}
Managed bean
#Controller("dolarMB")
#Scope(ViewScope.VIEW_SCOPE)
public class DolarMB extends AbstractMB<Dolar> implements Serializable {
private static final long serialVersionUID = 7711019409135908863L;
private static final Logger LOGGER = Logger.getLogger(DolarMB.class);
#Autowired
private DaoDolar dao;
private List<Dolar> lista;
private Dolar cadastro;
private Empresa empresa;
#PostConstruct
public void init(){
cadastro = new Dolar();
LOGGER.info("init:\n" + cadastro);
}
public void salvar() {
if (!validate()){
LOGGER.info("erro no cadastro");
}else{
cadastro = dao.salvar(cadastro);
limparFiltro();
}
}
}
Dao
#Repository
public class DolarDaoImpl extends GenericDaoImpl<Dolar> implements DolarDao{
#Override
public Dolar recuperarPorEmpresa(Empresa e) {
Query q = getConexao().createNamedQuery(Dolar.FIND_BY_EMPRESA);
q.setParameter("empr", e);
return (Dolar) q.getSingleResult();
}
}
#Repository
public abstract class GenericDaoImpl<T extends AbstractEntity> implements GenericDao<T> {
#PersistenceContext
private EntityManager em;
private Class<T> clazz;
private Method m;
public GenericDaoImpl() {
carregarClass();
carregarMetodoId();
}
#Override
#Transactional
public final T salvar(T e) {
if (e == null)
return null;
try {
if (m.invoke(e) != null) {
e = em.merge(e);
} else {
em.persist(e);
}
return e;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
Logger.getLogger(getClass()).error(e1);
} catch (Exception e2) {
Logger.getLogger(getClass()).error(e2);
}
return null;
}
After the em.persist(e), the log show me this
08-04-2015 18:04:15 DEBUG (TransactionSynchronizationManager.java:136) - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#46e82828] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#7113aac4] bound to thread [http-nio-8080-exec-7]
- Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#46e82828] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#7113aac4] bound to thread [http-nio-8080-exec-7]
08-04-2015 18:04:15 TRACE (AbstractSaveEventListener.java:499) - Transient instance of: br.com.mycompany.sales.model.Dolar
- Transient instance of: br.com.mycompany.sales.model.Dolar
08-04-2015 18:04:15 TRACE (DefaultPersistEventListener.java:202) - Saving transient instance
- Saving transient instance
08-04-2015 18:04:15 TRACE (AbstractSaveEventListener.java:167) - Saving [br.com.mycompany.sales.model.Dolar#<null>]
- Saving [br.com.mycompany.sales.model.Dolar#<null>]
08-04-2015 18:04:15 TRACE (ActionQueue.java:192) - Adding an EntityIdentityInsertAction for [br.com.mycompany.sales.model.Dolar] object
- Adding an EntityIdentityInsertAction for [br.com.mycompany.sales.model.Dolar] object
08-04-2015 18:04:15 TRACE (ActionQueue.java:208) - Adding insert with no non-nullable, transient entities: [EntityIdentityInsertAction[br.com.mycompany.sales.model.Dolar#<delayed:2>]]
- Adding insert with no non-nullable, transient entities: [EntityIdentityInsertAction[br.com.mycompany.sales.model.Dolar#<delayed:2>]]
08-04-2015 18:04:15 TRACE (ActionQueue.java:232) - Adding resolved non-early insert action.
- Adding resolved non-early insert action.
08-04-2015 18:04:15 TRACE (UnresolvedEntityInsertActions.java:214) - No unresolved entity inserts that depended on [[br.com.mycompany.sales.model.Dolar#<delayed:2>]]
- No unresolved entity inserts that depended on [[br.com.mycompany.sales.model.Dolar#<delayed:2>]]
08-04-2015 18:04:15 TRACE (UnresolvedEntityInsertActions.java:121) - No entity insert actions have non-nullable, transient entity dependencies.
- No entity insert actions have non-nullable, transient entity dependencies.
08-04-2015 18:06:30 DEBUG (TransactionSynchronizationManager.java:136) - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#46e82828] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#7113aac4] bound to thread [http-nio-8080-exec-7]
- Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#46e82828] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#7113aac4] bound to thread [http-nio-8080-exec-7]
08-04-2015 18:06:30 TRACE (AbstractSaveEventListener.java:482) - Persistent instance of: br.com.mycompany.sales.model.Dolar
- Persistent instance of: br.com.mycompany.sales.model.Dolar
08-04-2015 18:06:30 TRACE (DefaultPersistEventListener.java:174) - Ignoring persistent instance
- Ignoring persistent instance
08-04-2015 18:06:30 TRACE (UnresolvedEntityInsertActions.java:121) - No entity insert actions have non-nullable, transient entity dependencies.
- No entity insert actions have non-nullable, transient entity dependencies.
This is my configuration file
<?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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- Datasource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${database.driver}" />
<property name="user" value="${database.user}" />
<property name="password" value="${database.password}" />
<property name="jdbcUrl" value="${database.url}"/>
<!-- C3P0 properties -->
<property name="acquireIncrement" value="1" />
<property name="maxPoolSize" value="4" />
<property name="minPoolSize" value="1" />
<property name="maxIdleTime" value="120" />
<property name="initialPoolSize" value="1" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="SALES-HOMOLOG" />
<property name="dataSource" ref="dataSource" />
<property name="jpaDialect" ref="jpaDialect" />
<property name="packagesToScan">
<list>
<value>br.com.mycompany.sales.model</value>
</list>
</property>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${database.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${database.hbm2ddl.auto}</prop>
<prop key="hibernate.show_sql">${database.showSql}</prop>
<prop key="hibernate.format_sql">${database.formatSql}</prop>
</props>
</property>
</bean>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="${database.showSql}" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="${database.dialect}" />
</bean>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
my persistence.xml
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="SALES-HOMOLOG" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>br.com.mycompany.sales.model.Dolar</class>
<class>br.com.mycompany.sales.model.Empresa</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
</persistence>
The packages to scan entities shows something else than your logs shows as
entity class.
Your class is br.com.mycompany.sales.model.Dolar and the package that should hold the entity classes is defined like this br.com.mycompany.sales.dao. Either move the class to that package or change the package name on the class.
first off you should show us the code that where you are trying to save the instance. try calling the method below and I think it will work.
#Override
#Transactional
public final T salvar(T e)
this if from your GenericDaoImpl try using this.
I solved the problem updating a dependency of Spring.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-dao</artifactId>
<version>2.0.3</version>
</dependency>
I updated to version 2.0.8 and the transaction control worked.
Is there a way to specify a different web.xml from the standard WEB-INF/web.xml when using an embedded tomcat instance?
I would like to put a web.xml in my src/test/resources (or some other area) and refer to that web.xml when starting the embedded tomcat.
Here is my existing code to start the tomcat instance
tomcat = new Tomcat();
String baseDir = ".";
tomcat.setPort(8080);
tomcat.setBaseDir(baseDir);
tomcat.getHost().setAppBase(baseDir);
tomcat.getHost().setAutoDeploy(true);
tomcat.enableNaming();
Context ctx = tomcat.addWebApp(tomcat.getHost(), "/sandbox-web", "src\\main\\webapp");
File configFile = new File("src\\main\\webapp\\META-INF\\context.xml");
ctx.setConfigFile(configFile.toURI().toURL());
tomcat.start();
I am starting this server from a tomcat instance and I would like to do the following when running unit tests
turn off the contextConfigLocation
specify a custom ContextLoaderListener that sets the parent ApplicationContext of the embedded tomcat.
This file might be specified like so:
File webXmlFile = new File("src\\test\\resources\\embedded-web.xml");
Edit
After much frustration I realized that no matter what I do, I cannot persuade tomcat from looking in WEB-INF for web.xml. It appears that I must ignore the web.xml altogether and set the items in the web.xml programmatically.
I ended up with this configuration:
cucumber.xml for configuring tests
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="applicationContextProvider" class="ca.statcan.icos.sandbox.ApplicationContextProvider"/>
<bean id="sandBoxDataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testdb;shutdown=true;" />
<property name="username" value="SA" />
<property name="password" value="" />
</bean>
<!-- Support for JPA related annotation support (#PersistenceUnit and #PersistenceContext) -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<!-- JTA Configuration -->
<bean id="jtaTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<property name="forceShutdown"><value>true</value></property>
</bean>
<bean id="jtaUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" />
<bean id="springTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="jtaTransactionManager" />
<property name="userTransaction" ref="jtaUserTransaction" />
</bean>
<!-- JPA Entity Manager configuration -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
lazy-init="true">
<property name="persistenceUnitName" value="sandBox" />
<property name="dataSource" ref="sandBoxDataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="SQL_SERVER" />
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="jpaPropertyMap">
<props>
<prop key="hibernate.archive.autodetection">class</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<!-- Second Level Cache : EHCache in dev
<prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop> -->
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<import resource="classpath:META-INF/applicationContext-core.xml" />
<import resource="classpath:META-INF/applicationContext-web.xml" />
</beans>
applicationContext-core.xml - where the services are configured
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd"
default-autowire="byName">
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath*:META-INF/fms-local.properties" />
<property name="systemPropertiesModeName">
<value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
</property>
</bean>
<!--
Classpath scanning to load all the service classes
-->
<context:component-scan base-package="ca.statcan"
use-default-filters="false">
<context:include-filter type="regex" expression="ca\.statcan\.icos.*\.service\..*Service" />
<context:include-filter type="regex" expression="ca\.statcan\.icos.*\.builders\..*Builder" />
</context:component-scan>
<!--
Spring TransactionManager
-->
<tx:advice id="txAdvice" transaction-manager="springTransactionManager">
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT"/>
<!-- other methods use the default transaction settings -->
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!--
AOP Weaving for all Service methods
-->
<aop:config proxy-target-class="true">
<aop:pointcut id="icosServiceMethods" expression="execution(* ca.statcan.icos..*.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="icosServiceMethods" />
</aop:config>
</beans>
Custom ContextLoaderListener
public class EmbeddedContextLoaderListener extends ContextLoaderListener {
#Override
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
GenericWebApplicationContext context = new GenericWebApplicationContext(sc);
context.setParent(ApplicationContextProvider.getApplicationContext());
return context;
}
#Override
protected ApplicationContext loadParentContext(ServletContext servletContext) {
return ApplicationContextProvider.getApplicationContext();
}
}
Modified Embedded Tomcat Wrapper
public class EmbeddedTomcat {
/** Log4j logger for this class. */
#SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(EmbeddedTomcat.class);
private Tomcat tomcat;
public void start() {
try {
tomcat = new Tomcat();
String baseDir = ".";
tomcat.setPort(8080);
tomcat.setBaseDir(baseDir);
tomcat.getHost().setAppBase(baseDir);
tomcat.getHost().setDeployOnStartup(true);
tomcat.getHost().setAutoDeploy(true);
tomcat.enableNaming();
Context context = tomcat.addContext("/sandbox-web", "src\\main\\webapp");
Tomcat.initWebappDefaults(context);
configureSimulatedWebXml(context);
LOG.info("Starting tomcat in: " + new File(tomcat.getHost().getAppBase()).getAbsolutePath());
tomcat.start();
} catch (LifecycleException e) {
throw new RuntimeException(e);
}
}
public void stop() {
try {
tomcat.stop();
tomcat.destroy();
FileUtils.deleteDirectory(new File("work"));
FileUtils.deleteDirectory(new File("tomcat.8080"));
} catch (LifecycleException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void deploy(String appName) {
tomcat.addWebapp(tomcat.getHost(), "/" + appName, "src\\main\\webapp");
}
public String getApplicationUrl(String appName) {
return String.format("http://%s:%d/%s", tomcat.getHost().getName(),
tomcat.getConnector().getLocalPort(), appName);
}
public boolean isRunning() {
return tomcat != null;
}
private void configureSimulatedWebXml(final Context context) {
// Programmatically configure the web.xml here
context.setDisplayName("Sandbox Web Application");
context.addParameter("org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG", "/WEB-INF/tiles-defs.xml,/WEB-INF/tiles-sandbox.xml");
final FilterDef struts2Filter = new FilterDef();
struts2Filter.setFilterName("struts2");
struts2Filter.setFilterClass("org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter");
struts2Filter.addInitParameter("actionPackages", "ca.statcan.icos.sandbox.web");
context.addFilterDef(struts2Filter);
final FilterMap struts2FilterMapping = new FilterMap();
struts2FilterMapping.setFilterName("struts2");
struts2FilterMapping.addURLPattern("/*");
context.addFilterMap(struts2FilterMapping);
context.addApplicationListener("org.apache.tiles.web.startup.TilesListener");
context.addApplicationListener("ca.statcan.icos.sandbox.EmbeddedContextLoaderListener");
context.addWelcomeFile("index.jsp");
}
}
Step definitions
public class StepDefs {
#Autowired
protected EmployeeEntityService employeeEntityService;
#Given("^the following divisions exist$")
public void the_following_divisions_exist(DataTable arg1) throws Throwable {
final Employee employee = new Employee(3, "Third", "John", null, "613-222-2223");
employeeEntityService.persistEmployee(employee);
}
#Given("^there are no existing surveys$")
public void there_are_no_existing_surveys() throws Throwable {
}
#When("^I register a new survey with the following information$")
public void I_register_a_new_survey_with_the_following_information(DataTable arg1) throws Throwable {
Capabilities capabilities = DesiredCapabilities.htmlUnit();
final HtmlUnitDriver driver = new HtmlUnitDriver(capabilities);
driver.get("http://localhost:8080/sandbox-web/myFirst");
}
#Then("^the surveys are created$")
public void the_surveys_are_created() throws Throwable {
// Express the Regexp above with the code you wish you had
throw new PendingException();
}
#Then("^a confirmation message is displayed saying: \"([^\"]*)\"$")
public void a_confirmation_message_is_displayed_saying(String arg1) throws Throwable {
// Express the Regexp above with the code you wish you had
throw new PendingException();
}
}
Action class
#Results({ #Result(name = "success", location = "myFirst.page", type = "tiles") })
#ParentPackage("default")
#Breadcrumb(labelKey = "ca.statcan.icos.sandbox.firstAction")
#SuppressWarnings("serial")
public class MyFirstAction extends HappyfActionSupport {
private List<Employee> employees;
#Autowired
private EmployeeEntityService employeeEntityService;
#Override
public String execute() {
employees = employeeEntityService.getAllEmployee();
if (employees.size() == 0) {
// persist data in memory
final Employee employee1 = new Employee(1, "First", "John", null, "613-222-2222");
employeeEntityService.persistEmployee(employee1);
final Employee employee2 = new Employee(2, "Second", "John", null, "613-222-2223");
employeeEntityService.persistEmployee(employee2);
employees = employeeEntityService.getAllEmployee();
}
return SUCCESS;
}
public List<Employee> getEmployees() {
return employees;
}
}
With this, the embedded tomcat starts correctly and all seems to go well until I try to navigate to a web page. In the StepDefs class, the EmployeeEntityService is injected correctly. However, in the Action class, EmployeeEntityservice is not injected (it remains null).
According to my knowledge, I am setting the parent ApplicationContext for the embedded Tomcat correctly. So why isn't the server using the parent context to get the EmployeeEntityService?
I was stuck with a similar problem and the solution for using an alternative web.xml is simpler than one would dare to think:
2 lines:
Context webContext = tomcat.addWebapp("/yourContextPath", "/web/app/docroot/");
webContext.getServletContext().setAttribute(Globals.ALT_DD_ATTR, "/path/to/custom/web.xml");
Voila! The magic happens in org.apache.catalina.startup.ContextConfig#getWebXmlSource
Disclaimer: Tested on Tomcat 7.0.42
I'm using JP2 in my current web project. My main database holds the main entities. To connect on this DB i defined a Persitence Unit with a JTA Datasource:
Persistance.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="MyPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>MyDB</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.logging.parameters" value="true"/>
<property name="eclipselink.logging.logger" value="ServerLogger"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
and the JTA Datasource defined in sun-resources.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="org.postgresql.ds.PGSimpleDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="post-gre-sql_mydb_mypool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
<property name="serverName" value="localhost"/>
<property name="portNumber" value="5432"/>
<property name="databaseName" value="mydb"/>
<property name="User" value="myuser"/>
<property name="Password" value="mypass"/>
<property name="URL" value="jdbc:postgresql://localhost:5432/mydb"/>
<property name="driverClass" value="org.postgresql.Driver"/>
<property name="characterEncoding" value="UTF-8" />
</jdbc-connection-pool>
<jdbc-resource enabled="true" jndi-name="MyDB" object-type="user" pool-name="post-gre-sql_mydb_mypoll"/>
</resources>
And this is how i access the database on my DAO classes (witch are #ManagedBeans and #SessionScoped):
#ManagedBean(name = "pageDao")
#SessionScoped
public class PageDao implements Serializable {
#Resource
private UserTransaction utx = null;
#PersistenceUnit(unitName = "MyPU")
private EntityManagerFactory emf = null;
public EntityManager getEntityManager() {
return emf.createEntityManager();
}
public List<PageEnt> getAll() { ... }
public PageEnt getOne(long pageId) { ... }
public void addPage(PageEnt newPage) throws RollbackFailureException, PreexistingEntityException, Exception { ... }
public PageEnt update(PageEnt page) throws RollbackFailureException, NonexistentEntityException, Exception { ... }
public void remove(PageEnt page) throws RollbackFailureException, Exception { ... }
}
One of entities (customer) has properties for connecting on a separate (per-customer) database, witch are defined in run-time. These properties includes:
Databse name
Host and port
User and Password
My question are:
How do I efficiently create a database connection in run-time?
How can I create a new EntityManager from container-managed resources if there is no per-customer PersistanceUnit and Datasources defined (witch are defined at deploy-time)?
If i have to manually deal with the EntityManagerFactory (witch, as i learned in college, is a heavy and expansive object), how do I efficiently do that? Is there a good-practice or pattern?
How would the DAO pattern work? How do my DAO class will get the EntityManager?
Big thanks from Brazil.
It is possible to switch between multiple data sources at run time. It is provided by Spring AbstractRoutingDataSource . It is required to override the #determineCurrentLookupKey() method which will return a key to decide the specific datasource that is needed to be connected. Also there should be spring configuration that maps each of the possible keys and the corresponding data sources that are to be connected. Some thing like
<jee:jndi-lookup id="DataSource_Client1" jndi-name="DataSource_Client1" />
<jee:jndi-lookup id="DataSource_Client2" jndi-name="DataSource_Client" />
<bean id="DynamicDataSource" class="concrete implementation class name of AbstractRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="Client1" value-ref="DataSource_Client1" />
<entry key="Client2" value-ref="DataSource_Client2" />
</map>
</property>
</bean>
A possible reference to this Dynamic DataSource
Hope this answers one of your questions
I found some strange behavior when using nested Spring transactions: when, in the same class, a method annotated as #Transactional calls another method also annotated as #Transactional the second annotation is not used.
Let's consider the following class:
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
final Main main = context.getBean(Main.class);
// First Op
System.out.println("Single insert: " + main.singleInsert());
// Second Op
main.batchInsert();
// Third Op
main.noTransBatchInsert();
}
#PersistenceContext
private EntityManager pm;
#Transactional(propagation=Propagation.REQUIRED)
public void batchInsert() {
System.out.println("batchInsert");
System.out.println("First insert: " + singleInsert());
System.out.println("Second insert: " + singleInsert());
}
public void noTransBatchInsert() {
System.out.println("noTransBatchInsert");
System.out.println("First insert: " + singleInsert());
System.out.println("Second insert: " + singleInsert());
}
#Transactional(propagation=Propagation.REQUIRES_NEW)
public int singleInsert() {
System.out.println("singleInsert");
Pojo p = new Pojo();
pm.persist(p);
return p.getId();
}
}
The entity if the following class:
#Entity
public class Pojo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Override
public String toString() {
return "Pojo: " + id;
}
public int getId() {
return id;
}
}
and the String parts applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<tx:annotation-driven />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="MyPersistenceUnit" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
and the configuration class (I could have merge this in applicationContext.xml).
#Configuration
#ImportResource("/META-INF/applicationContext.xml")
public class Config {
#Bean
public Main main() {
return new Main();
}
}
For completeness the persistence.xml file:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.connection.driver_class" value="org.h2.Driver" />
<property name="hibernate.connection.url" value="jdbc:h2:mem:TestDSJPA2;DB_CLOSE_DELAY=-1;LOCK_MODE=0" />
<!--<property name="hibernate.connection.url" value="jdbc:h2:mem:TestDSJPA2;DB_CLOSE_DELAY=-1;LOCK_MODE=0" />-->
<property name="hibernate.connection.username" value="sa" />
<property name="hibernate.connection.password" value="" />
<property name="hibernate.connection.autocommit" value="false"/>
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
So in the main class, the first operation is performed as expected that is in a new transaction. The output (including some DEBUG messages) is:
DEBUG o.h.transaction.JDBCTransaction - begin
singleInsert
DEBUG o.h.transaction.JDBCTransaction - commit
Single insert: 1
The second operation gives the following output:
batchInsert
singleInsert
DEBUG o.h.transaction.JDBCTransaction - begin
First insert: 2
singleInsert
Second insert: 3
DEBUG
This is not what I expected since in annotating singleInsert with #Transactional(propagation=Propagation.REQUIRES_NEW) I would expect a new transaction to be created for every call which is not what's happening since the same top level transaction is used for both insertion.
The third operation fails as well as no transaction is created at all:
noTransBatchInsert
singleInsert
DEBUG o.h.e.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
First insert: 0
singleInsert
DEBUG o.h.e.def.AbstractSaveEventListener - delaying identity-insert due to no transaction in progress
Second insert: 0
In the #Configuration beans Spring ensures that calls to the method on the same class are proxified which is obviously not happening here. Is there a way do change this behavior?
This behavior is the documented behavior of Spring when using the proxy mode for AOP. It can be changed by switching to the aspectj mode which perform code instrumentation either on compilation or at runtime.
This is not specifically a problem with #Transactional. It is due to the configuration of your <tx:annotation-driven/>.
Spring uses two different AOP mechanisms: JDK dynamic proxies or CGLIB. JDK dynamic proxies is the default and it works through the use of interfaces at run-time. CGLIB works by generating subclasses at compile-time. If you specify <tx:annotation-driven proxy-target-class="true"/>, Spring will use CGLIB, and your second #Transactional will fire.
You can read more about the subject here.
The default advice mode for processing #Transactional annotations is
proxy, which allows for interception of calls through the proxy only.
Local calls within the same class cannot get intercepted that way. For
a more advanced mode of interception, consider switching to aspectj
mode in combination with compile-time or load-time weaving.
Taken from Spring reference. https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#tx-propagation-nested