No inserts using Hibernate and Spring

I was setting up a simple side-project with Spring, Spring Data and Hibernate. My repositories are generated using Spring Data’s jpa:repositories to save on development time and the database schema is generated using Hibernate, based on my JPA and JSR-303 annotated entities.

However, one of the issues I encountered was that my repositories did not insert anything in the database. After searching the web I followed a suggestion on Stackoverflow to add an entityManager.flush() to the repository to see if a transaction was running or not. There was indeed no transaction running but I had yet to find out why.

Exception in thread "main" javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.checkTransactionNeeded(AbstractEntityManagerImpl.java:1136)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1297)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:344)
at com.sun.proxy.$Proxy31.flush(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:293)
at com.sun.proxy.$Proxy31.flush(Unknown Source)
at be.pw999.photocol.service.PhotoColServiceImpl.makeImage(PhotoColServiceImpl.java:45)
...

At that point my code looked like this:

@Service
public class PhotoColServiceImpl implements PhotoColService {

// My Injected beans

@Autowired
private EntityManager em;

@Transactional(readOnly = false)
@Override
public MakeImageResponse makeImage(MakeImageRequest request) {
em.flush();
}
}

 

 

public class Backend {

 public static void main(String[] args) {
  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:/META-INF/webapp/WEB-INF/application-context.xml");
  
  PhotoColService service = ac.getBean(PhotoColService.class);
  
  MakeImageRequest r = new MakeImageRequest("DSC0012.JPG", "/home/phillip/Pictures", "12345678901234567890123456789012");
  MakeImageResponse resp = service.makeImage(r);
  
  ac.close();
 }
}

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
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.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd">

 <context:component-scan base-package="be.pw999.photocol.service"></context:component-scan>
 <!-- Define your application beans here. They will be available to the
beans defined in your web-context because it is a sub-context. Beans defined
in the web-context will not be available in the application context. -->
 <context:property-placeholder location="classpath:persistence-h2.properties" />

 <bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="packagesToScan" value="be.pw999.photocol.model" />
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
    <prop key="hibernate.dialect">${hibernate.dialect}</prop>
   </props>
  </property>
 </bean>

 <bean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.user}" />
  <property name="password" value="${jdbc.pass}" />
 </bean>

 <bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
 </bean>

 <bean id="persistenceExceptionTranslationPostProcessor"
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

 <bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
  <property name="showSql" value="true" />
  <property name="generateDdl" value="true" />
  <property name="database" value="H2" />
 </bean>

 <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
  <!-- spring based scanning for entity classes> -->
  <property name="packagesToScan" value="be.pw999.photocol.model" />
</bean>


 <jpa:repositories base-package="be.pw999.photocol.repository"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
</beans>

There were actually a couple of issues with this configuration.

Mistake 1

Using the wrong @Transaction annotation. There are two @Transaction annotations: @javax.persistence.Transaction and @org.springframework.transaction.annotation.Transactional. Only the latter will add transaction management to your Spring services and I had used the first one (because I trust autocomplete too much).

Mistake 2

Second mistake was that I had forgotten to add <tx:annotation-driven /> to my configuration. Stupid, stupid, stupid !

Mistake 3

After solving the first two mistakes I still had no transaction and no inserts in my H2 database. I quickly realized that my transaction manager was an instance of org.springframework.orm.hibernate5.HibernateTransactionManager and even though I use Hibernate, I’m not using it directly. My repositories are generated with Spring’s jpa:repositories and they use a JPA EntityManager instead of the Hibernate SessionFactory. So even though my configuration looked great, the JPA repositories did not use the Hibernate managed transactions.

So instead of using the HibernateTransactionManager, I used the org.springframework.orm.jpa.JpaTransactionManager and this did solve the no-inserts problem.

So this is now my configuration that works:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
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.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

 <context:component-scan base-package="be.pw999.photocol.service"></context:component-scan>
 <!-- Define your application beans here. They will be available to the
beans defined in your web-context because it is a sub-context. Beans defined
in the web-context will not be available in the application context. -->
 <context:property-placeholder location="classpath:persistence-h2.properties" />
 <tx:annotation-driven transaction-manager="jpaTransactionManager" />

 <bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="packagesToScan" value="be.pw999.photocol.model" />
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
    <prop key="hibernate.dialect">${hibernate.dialect}</prop>
   </props>
  </property>
 </bean>

 <bean id="dataSource" class="org.apache.tomcat.dbcp.dbcp.BasicDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.user}" />
  <property name="password" value="${jdbc.pass}" />
 </bean>

 <bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
 </bean>

 <bean id="persistenceExceptionTranslationPostProcessor"
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

 <bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
  <property name="showSql" value="true" />
  <property name="generateDdl" value="true" />
  <property name="database" value="H2" />
 </bean>

 <bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
  <!-- spring based scanning for entity classes> -->
  <property name="packagesToScan" value="be.pw999.photocol.model" />
 </bean>

 <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory"></property>
 </bean>

 <jpa:repositories base-package="be.pw999.photocol.repository"
transaction-manager-ref="jpaTransactionManager"
entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
</beans>
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s