20
Feb
16

using JPA 2.x with spring 3.x transactions


This page demonstrates how to use JPA EntityManager within a Spring Transaction using HSQLDB a Java in memory database. We will use a Spring DataManager responsible for database interaction, Spring service class for transaction management.

This is a update for a blog post I written back in 2010. That post dealt with the same topic using plain JdbcTemplate.

Highlights

  • Spring Pure Annotation based configuration (ZERO XML files) with an exception of the pom.xml file
  • Java JPA
  • HSQLDB

Background

Based on the default behavior, when the application throws a run time exception any method that is defined to be transactional issues a roll-back on whatever resource is currently participating in the transaction. To define a method as transactional just put an @Transactional annotation before it.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Requirements

  • Java 8

Implementation

The following example will demonstrate the transaction capabilities of the spring framework. Consider it a very basic “hello world” program for transactional code.

Create the pom.xml

vi 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.test</groupId>
	<artifactId>springJPATXTest</artifactId>
	<packaging>jar</packaging>
	<version>20160220</version>
	<url>http://maven.apache.org</url>
	
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>
		
	<properties>
		<spring.version>4.1.4.RELEASE</spring.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>1.7.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.3.3</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.1-api</artifactId>
			<version>1.0.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.3.7.Final</version>
		</dependency>		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>

			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

		</plugins>
	</build>	
</project>

The following class uses Spring Annotation based configuration that sets up the data source and transaction manager.

vi src/main/java/com/test/SpringConfig.java

package com.test;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan("com.test")
@EnableJpaRepositories("com.test")
@EnableTransactionManagement
public class SpringConfig {

	@Bean
	public DataSource getDataSource() {
		DriverManagerDataSource dm = new DriverManagerDataSource();
		dm.setDriverClassName("org.hsqldb.jdbcDriver");
		// change the "/tmp" to be another location on your system.
		dm.setUrl("jdbc:hsqldb:file:/tmp/springTXTestDB;shutdown=true");
		dm.setUsername("sa");
		return dm;
	}

	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
		em.setDataSource(getDataSource());
		em.setPackagesToScan(new String[] { "com.test" });
		JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		em.setJpaVendorAdapter(vendorAdapter);
		em.setJpaProperties(additionalProperties());
		return em;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(emf);
		return transactionManager;
	}

	@Bean
	public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
		return new PersistenceExceptionTranslationPostProcessor();
	}

	Properties additionalProperties() {
		Properties properties = new Properties();
		properties.setProperty("hibernate.flushMode", "FLUSH_AUTO");
		properties.setProperty("hibernate.hbm2ddl.auto", "update");
		properties.setProperty("hibernate.show_sql", "true");
		return properties;
	}
}

The following is just an interface.

vi src/main/java/com/test/TestTransactionDataManager.java

package com.test;

public interface TestTransactionDataManager {
	public abstract Number writeData(String data);
}

vi src/main/java/com/test/TestTransactionDataManagerImpl.java

package com.test;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;

/**
 * This data manager is used to test the transactional capabilities
 * of the spring framework. The code below is not marked as transactional
 * however it participates in one when called from a transactional method.
 */
@Repository("testTransactionDataManager")
public class TestTransactionDataManagerImpl implements TestTransactionDataManager {

	@PersistenceContext 
	private EntityManager em;

	/**
	 * Responsible for writing data to a transactional resource.
	 * @param data
	 */
	public Number writeData(String data) {
		DBRecord record = new DBRecord();
		record.setData(data);
    	em.persist(record);
    	em.flush();
    	return record.getId();
	}
}

Just an interface…

vi src/main/java/com/test/TestTransactionModel.java

package com.test;

public interface TestTransactionModel {
	void writeDataWithOutTx(String message);
	void writeDataWithMultipleCalls(String message);
	void writeDataWithTx(String message);
	void writeDataWithTxSuccess();
}

This is our Model class. The model layer is a natural place to define unit of work. Model methods are called from “control layer” in the MVC architecture. (Struts actions, SpringMVC Command, or Servlets) THERE SHOULD BE AT MOST ONE call to a transaction enabled model method to get a the “unit of work” behavior. Otherwise if you have more than one call to these methods and one succeeds and the other fails you will not get the “unit of work” behavior. Please keep this in mind… Only one call to a transactional method from the “control layer”.

vi src/main/java/com/test/TestTransactionModelImpl.java

package com.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("testTransactionModel")
public class TestTransactionModelImpl implements TestTransactionModel {

	@Autowired
	private TestTransactionDataManager testTransactionDataManager;
	
	@Transactional
	@Override
	public void writeDataWithTxSuccess() {
		testTransactionDataManager.writeData("Writing data within a transactional method. You WILL see this record.");
	}

	public void writeDataWithOutTx(String message) {
		testTransactionDataManager.writeData("Writing data within a NON transactional method. You WILL NOT see this record.");
	}

	/**
	 * This method will throw a runtime exception and the data will be rolled
	 * back.
	 */
	@Transactional  // remember @Transaction for public methods only!
	public void writeDataWithTx(String message) {
		if(message == null) {
			message = "Writing data within a transactional method with an exception. You will NOT see this record.";
		}
		Number number = testTransactionDataManager.writeData(message);
		System.out.println("wrote item: " + number);
		throw new RuntimeException();
	}

	/**
	 * This method tests the capability of calling another method that is
	 * declared to be transactional where the second method throws an exception
	 * but this method still commits. This is important to know that calling nested
	 * transactional methods will not result in a commit.
	 */
	@Transactional
	public void writeDataWithMultipleCalls(String message) {
		String msg = "Suppressing runtimeexception and committing anyway you will see this record.";
		try {
			writeDataWithTx(msg);
		} catch(RuntimeException ex) {
			System.out.println(msg);
		}
	}
}

The following is the JPA Entity we will be writing to the database.
vi src/main/java/com/test/DBRecord.java

package com.test;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="test_table")
public class DBRecord {
	@Id
	@GeneratedValue
	private Integer id;
	
	private String data;	
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getData() {
		return data;
	}
	public void setData(String data) {
		this.data = data;
	}
	@Override
	public String toString() {
		return "DBRecord [id=" + id + ", data=" + data + "]";
	}
}

The App.java JUnit Test doIt() method will call methods on the model layer.

vi src/test/java/com/test/App.java

package com.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
//	private ApplicationContext context = new ClassPathXmlApplicationContext("com/test/applicationContext.xml");
	static private ApplicationContext context = null;
	
	static {
		context = new AnnotationConfigApplicationContext(com.test.SpringConfig.class);
	}
		
	@Test
	public void doIt() {
		TestTransactionModel model = (TestTransactionModel) context.getBean("testTransactionModel");
		
		try {
			model.writeDataWithTxSuccess();
		} catch(Exception ex) {
		}
		try {
			model.writeDataWithOutTx(null);
		} catch (RuntimeException ex) { // do nothing
		}

		try {
			model.writeDataWithTx(null);
		} catch (RuntimeException ex) { // do nothing
		}

		try {
			model.writeDataWithMultipleCalls(null);
		} catch (RuntimeException rex) { // do nothing
		}
	}

	public App() {
		doIt();
	}


}

Results

The results get saved to the HSQLDB database location that is specified in the SpringConfig.java file. It defaults to a file in the /tmp dir. Open the .script file and you will find the data saved to the table towards the bottom.

Advertisements

0 Responses to “using JPA 2.x with spring 3.x transactions”



  1. Leave a Comment

Leave a Reply

Please log in using one of these methods to post your comment:

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


Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 78 other followers

February 2016
S M T W T F S
« Dec   Mar »
 123456
78910111213
14151617181920
21222324252627
2829  

Blog Stats

  • 822,600 hits

%d bloggers like this: