testing Hibernate with Spring's EmbeddedDatabaseBuilder issues with SpringSessionContext - spring

I am unit-testing with Spring Framework's EmbeddedDatabaseBuilder as a datasource and passing this into the Hibernate config for my SessionFactory, using Spring 4 and Hibernate 4. I am not using the Spring context in any way - the only config I use is programmatic (no annotations, no XML except the Hibernate mapping files).
I expected that Hibernate would use its default ThreadLocalSessionContext and that I would be able to start and rollback transactions in the unit test.
However somehow Hibernate has set its SessionFactory.currentSessionContext to Spring's SpringSessionContext and this complains whenever I try to call SessionFactory.getCurrentSession():
HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
In the code below in my unit test, I have set hibernate.current_session_context_class to thread but this is ignored or replaced with the Spring implementation.
public abstract class HibernateTestBase {
private static EmbeddedDatabase dataSource;
private static SessionFactory sessionFactory;
private Session session;
#BeforeClass
public static void setupClass() {
dataSource = new EmbeddedDatabaseBuilder().
setType(EmbeddedDatabaseType.H2).
addScript("file:SQLResources/schema-1.1.sql").
addScript("file:SQLResources/schema-1.2.sql").
build();
Configuration configuration = new Configuration();
configuration.addResource("hibernate-mappings/HierarchyLevel.hbm.xml");
configuration.addResource("hibernate-mappings/HierarchyFilter.hbm.xml");
configuration.addResource("hibernate-mappings/AuditLog.hbm.xml");
configuration.setProperty("hibernate.dialect",
"org.hibernate.dialect.Oracle10gDialect");
configuration.setProperty("hibernate.show_sql", "true");
configuration.setProperty("hibernate.current_session_context_class",
"thread");
StandardServiceRegistryBuilder serviceRegistryBuilder =
new StandardServiceRegistryBuilder();
serviceRegistryBuilder.applySetting(Environment.DATASOURCE, dataSource);
serviceRegistryBuilder.applySettings(configuration.getProperties());
StandardServiceRegistry serviceRegistry =
serviceRegistryBuilder.build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
sessionFactory.openSession();
}
#AfterClass
public static void tearDown() {
if (sessionFactory != null) {
sessionFactory.close();
}
if (dataSource != null) {
dataSource.shutdown();
}
}
#Before
public final void startTransaction() {
session = sessionFactory.getCurrentSession();
session.beginTransaction();
}
#After
public final void rollBack() {
session.flush();
Transaction transaction = session.getTransaction();
transaction.rollback();
}
}
I am forced to call SessionFactory.openSession() instead but then I won't be able to use my DAOs with the same SessionFactory because they all call getCurrentSession(). So I won't be able to benefit from the already coded data handling functionality.
What can I do?

It works OK when setting up the Configuration object using a traditional hibernate.cfg.xml using the SpringSessionContext as for production like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"classpath://org/hibernate/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</property>
<!-- property name="hibernate.current_session_context_class">thread</property-->
<mapping resource="hibernate-mappings/AccessRight.hbm.xml" />
</session-factory>
</hibernate-configuration>
and loading that up in the test, but overwriting the session context like this:
Configuration configuration = new Configuration();
configuration.configure(
"hibernate-mappings/hibernate.cfg.xml");
configuration.setProperty("hibernate.current_session_context_class",
"thread");
So there is something going on in the background when Hibernate sets itself up with its hibernate.cfg.xml that is required.

Related

problems configuring Hibernate with spring boot, CommandLineRunner

I'm using Spring Tool Suite to create a Spring Boot application that uses Hibernate.
Here's my application.properties file:
spring.datasource.url=jdbc:mysql:someurl
spring.datasource.username=somename
spring.datasource.password=somepassword
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.SQLServerDialect
spring.jpa.show-sql=true
I'm trying to run the following CommandLineRunner:
#Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
Configuration configuration = new Configuration().configure();
ServiceRegistryBuilder registry = new ServiceRegistryBuilder();
registry.applySettings(configuration.getProperties());
ServiceRegistry serviceRegistry = registry.buildServiceRegistry();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
Session session = sessionFactory.openSession();
Transaction t = session.beginTransaction();
Customer e1 = new Customer();
//blah blah blah
session.persist(e1);
t.commit();
session.close();
};
}
My problem is:
Configuration configuration = new Configuration().configure();
looks for "hibernate.cfg.xml" and all my hibernate config is in application.properties
How do I cause the Hibernate config stuff to initialize itself with the stuff in application.properties?
Configurations are automatically loaded with application properties if you are using spring boot.It would be needed only if additional configurations are required or if modifying an existing configuration programatically.
I guess,you do not need either of them for this job,that is ,you could always use this and not set configuration to get sessionfactory :
#Autowired
private EntityManagerFactory entityManagerFactory;
#Bean
public SessionFactory getSessionFactory() {
if (entityManagerFactory.unwrap(SessionFactory.class) == null) {
throw new NullPointerException("Not a hibernate factory exception");
}
return entityManagerFactory.unwrap(SessionFactory.class);
}
This way , your configurations are loaded automatically and you get your sessionfactory.
Hope this helps.

Mixing XML and java configuration

I am migrating an application from an XmlWebApplicationContext to a pure java configuration solution using AnnotationConfigApplicationContext. I am having a problem reusing existing xml configuration files via #ImportResource. We are using spring 3.2.11.
When I use the xml based context, beans defined in the xml files that are java configuration (#Configuration) are automatically picked up by the context and any beans they define are visible. However, when imported through #ImportResource, #Beans in the configuration objects are not created.
Here is a unit test that illustrates my problem:
XmlConfigTest.java
#Test
public void testAnnotationContext()
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(XmlFromJava.class);
ctx.refresh();
assertEquals("xml value", ctx.getBean("xmlBean", String.class));
assertEquals("nested value", ctx.getBean("nestedBean", String.class));
}
#Test
public void testXmlContext()
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:test.xml");
ctx.refresh();
assertEquals("xml value", ctx.getBean("xmlBean", String.class));
// fails here
assertEquals("nested value", ctx.getBean("nestedBean", String.class));
}
#Configuration
#ImportResource("classpath:test.xml")
public static class XmlFromJava { }
NestedConfig.java
#Configuration
public class NestedConfig
{
#Bean
public String nestedBean()
{
return "nested value";
}
}
test.xml
<context:annotation-config/>
<bean class="NestedConfig"/>
<bean name="xmlBean" class="java.lang.String">
<constructor-arg value="xml value"/>
</bean>
I would expect the bean 'nestedBean' to exist from the NestedConfig class. testAnnotationContext() fails to load the 'nestedBean' but testXmlContext() works.

Using spring to load properties as System properties

I'm using Spring 4.1.6.
I have something like the following:
foo.properties:
valueX=a
valueY=b
Spring bean:
<context:property-placeholder location="classpath:foo.properties" ignore-unresolvable="false" ignore-resource-not-found="false" />
<bean id="foo" class="com.foo.bar.MyClass" >
<property name="someValue" value="${valueX}" />
</bean>
I have a non-Spring class which also needs to use a value from foo.properties.
Non Spring Class:
public void doSomething() {
String valueY = System.getProperty("valueY");
}
When Spring loads foo.properties, is there a way to populate all the properties into System properties so that I can get "valueY" using System.getProperty("valueY").
I don't want to load foo.properties again in my non-Spring class.
The context:property-placeholder will create a PropertySourcesPlaceholderConfigurer config bean for you. You cannot access the properties from this bean programatically as stated here.
What you can do is to load the properties into a separate spring bean as given below.
#Bean(name = "mapper")
public PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("application.properties"));
return bean;
}
and then set the system property when the context load is finished using a listener as given below. Got the code from this answer
#Component
public class YourJobClass implements ApplicationListener<ContextRefreshedEvent> {
#Resource(name = "mapper")
private Properties myTranslator;
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.setProperties(myTranslator);
}
}

dynamic "jta-data-source" prop in persistence.xml

I have one application that i install on different servers
each server has different jdbc name
the jdbc name also Defined under the (java) system-prop "jdbc_name" in all servers
i try to change "jta-data-source" dynamically to work with value from system-prop("jdbc_name")
for now
in my case i use
#PersistenceContext(unitName="MyUnit")
private EntityManager em;
persistence.xml:
<persistence-unit name="MyUnit" transaction-type="JTA">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
<jta-data-source>jdbc/myJdbcName</jta-data-source>
<class>com.myCom.myClass</class>
<properties>
<property name="openjpa.TransactionMode" value="managed"/>
</properties>
</persistence-unit>
im using:
websphere
ejb3
openjpa
As far as i know, there is now way you can do this if you are using container-managed entity-managers.
You will have to resort back to application-managed entity-managers, and in order not to do the manual work, you can integrate this with CDI.
This will involve managing transaction boundaries manually. In order to avoid this kind of manual transaction boundary management, you can use interceptors:
#ApplicationEntityManagerInterceptorBinding
#Interceptor
public class ApplicationEntityManagerInterceptor {
#Inject
#ApplicationEntityManager
private EntityManager em;
#AroundInvoke
public Object intercept(final InvocationContext ic) {
//check if transaction is active, otherwise create a new one
em.createTransaction....
try {
return ic.proceed();
} catch (Exception ex) {
//does the exception require rollback? rollback the transaction here
} finally {
//commit the transaction if necessary.
}
}
}
#Stateless
public class EntityManagerFactoryProducer {
#Produces
#ApplicationEntityManager
public EntityManager entityManager() {
final EntityManagerFactory emf = Persistence.createEntityManager("persistence_name");
return emf.createEntityManager();
}
//you will have to manage this yourself
public void dispose(#Dispose final EntityManager em) {
em.close();
}
}
and then wherever you need it!
#Stateless
#ApplicationEntityManagerInterceptorBinding
public class MyDAO {
#Inject
#ApplicationEntityManager
private EntityManager em;
}

How can I add an entity listener to a JPA (EclipseLink) entity without an active session?

I am trying to do the following inside a spring bean:
#PostConstruct
public void registerTorchEntityListeners()
{
Session session = entityManager.unwrap(Session.class);
for (EntityType<?> entity : entityManager.getMetamodel().getEntities())
{
if (entity.getJavaType().isAnnotationPresent(TorchEntityListeners.class))
{
TorchEntityListeners annotation = (TorchEntityListeners) entity.getJavaType().getAnnotation(TorchEntityListeners.class);
for (Class listenerClass : annotation.value())
{
Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
for (DescriptorEventListener listenerBean : map.values())
{
session.getClassDescriptor(entity.getClass()).getEventManager().addListener(listenerBean);
}
}
}
}
}
The problem is I get the following exception because (I think) I am not in a transaction and therefore do not have a session available to grab the ClassDescriptor so that I can add a listener to a particular entity:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'torchEntityListenerConfigurer': Invocation of init method failed; nested exception is java.lang.IllegalStateException: No transactional EntityManager available
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:396)
Basically I am trying to do the EclipseLink equivalent of this: http://invariantproperties.com/2013/09/29/spring-injected-beans-in-jpa-entitylisteners/. I would prefer to annotate the entity with the listener rather than doing something like this: Injecting a Spring dependency into a JPA EntityListener.
Thoughts?
Of course I figure it out 30 minutes after I add a bounty :)
I finally got this to work by getting the entityManager from a wired in EntityManagerFactory instead of using: #PersistenceContext to inject it into the TorchEntityListenerConfigurer
Here is the working solution...and it works great!
Here is the config:
<bean id="approvalEntityListener" class="com.prometheus.torchlms.core.activity.approval.ApprovalEntityListener">
<property name="activityRepository" ref="activityRepository" />
<property name="notificationFactory" ref="notificationFactory" />
<property name="notificationService" ref="notificationService" />
</bean>
<bean id="springEntityListenerConfigurer" class="com.prometheus.torchlms.core.SpringEntityListenerConfigurer">
<constructor-arg ref="entityManagerFactory" />
</bean>
Here is where the magic happens (in case this is useful to someone):
public class SpringEntityListenerConfigurer implements ApplicationContextAware
{
private ApplicationContext applicationContext;
private EntityManagerFactory entityManagerFactory;
public SpringEntityListenerConfigurer(EntityManagerFactory entityManagerFactory)
{
this.entityManagerFactory = entityManagerFactory;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
this.applicationContext = applicationContext;
}
#PostConstruct
public void registerTorchEntityListeners()
{
//entityManager.
EntityManager entityManager = entityManagerFactory.createEntityManager();
Session session = entityManager.unwrap(Session.class);
for (EntityType<?> entity : entityManagerFactory.getMetamodel().getEntities())
{
if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class))
{
SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(SpringEntityListeners.class);
for (Class listenerClass : annotation.value())
{
Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
for (DescriptorEventListener listenerBean : map.values())
{
ClassDescriptor classDescriptor = session.getClassDescriptor(entity.getJavaType());
if (null != classDescriptor)
{
classDescriptor.getEventManager().addListener(listenerBean);
}
}
}
}
}
}
}
So now I can add my #SpringEntityListeners({ApprovalEntityListener.class}) to any entity I want with any listener that I want and those listeners can be spring beans!
In case it helps here's the annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface SpringEntityListeners
{
Class<?>[] value();
}
In fact you can do your registrations without getting an EntityManager, because you only use it to get a client session, and you only use the client session to register listeners with the server session without any direct interaction with the database nor changing any object.
The EntityManagerFactory is actually an EntityManagerFactoryImpl that can directly expose the ServerSession with unwrap. I had to dig through classes explicitly marked as INTERNAL, to find that, and the ServerSession (also marked as INTERNAL) explicitely states in his javadoc : All changes to objects and the database must be done through a unit of work acquired from the client session, this allows the changes to occur in a transactional object space and under a exclusive database connection.
But for your use case, I think it is correct to use it like that, using the server session only to get access to the Project that actually contains the class descriptors and is a public class in EclipseLink :
public void registerTorchEntityListeners()
{
// no entityManager here and Session is only used to get access to Project
Project proj = entityManagerFactory.unwrap(Session.class).getProject();
for (EntityType<?> entity : entityManagerFactory.getMetamodel().getEntities())
{
if (entity.getJavaType().isAnnotationPresent(SpringEntityListeners.class))
{
SpringEntityListeners annotation = (SpringEntityListeners) entity.getJavaType().getAnnotation(SpringEntityListeners.class);
for (Class listenerClass : annotation.value())
{
Map<String, DescriptorEventListener> map = applicationContext.getBeansOfType(listenerClass);
for (DescriptorEventListener listenerBean : map.values())
{
ClassDescriptor classDescriptor = proj.getClassDescriptor(entity.getJavaType());
if (null != classDescriptor)
{
classDescriptor.getEventManager().addListener(listenerBean);
}
}
}
}
}
}

Resources