JPA implementation in Spring Data

In some of previous article we discovered how Hibernate and its JPA implementation work. We can use them as components for native-developed solutions. But we can also use them with Spring Framework.

This time we'll focus on using JPA with Spring Framework. We don't explain again the JPA concepts. If you don't remember them, please back to the article about Hibernate with JPA and come back here after reading it. Instead of reminding it, we'll start by configuring JPA with Spring's ORM package. After the configuration, we'll investigate what happens under the hood when EntityManagerFactory is created. After we'll focus on thread-safety of Spring's entity managers used through @PersistenceContext annotation.

Configure JPA in Spring

Configuring entity manager factory beans isn't different from configuring normal Spring beans. Simply, it can support different properties. Take a look on our sample:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
  </property>
  <property name="packagesToScan" value="com.mysite.data"/>
  <property name="jpaProperties">
    <props>
      <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect
      <prop key="hibernate.show_sql">true
    </props>
  </property>
</bean>

They are some explanations for different properties:

LocalContainerEntityManagerFactoryBean lifecycle

This class, placed in org.springframework.orm.jpa package, extends an abstract class AbstractEntityManagerFactoryBean. As we can read in the JavaDoc for this last class, it's used to create an instance of EntityManagerFactory in Spring's application context. We retrieve there a lot of setters, invoked at bean creation and defined in the previous part:
- setJpaProperties: called for jpaProperties attribute.
- setJpaVendorAdapter: called for the bean's attribute named jpaVendorAdapter.

Because AbstractEntityManagerFactoryBean implements org.springframework.beans.factory.InitializingBean interface, it overrides afterPropertiesSet() method. It's invoked directly after the set of all bean properties. Inside this method, our abstract class ensures that all needed objects to handle JPA are defined. So, it gets persistence provider and entity manager factory interface directly from jpaVendorAdapter. Next, it calls createNativeEntityManagerFactory.

This method is defined as abstract and is overridden by all AbstractEntityManagerFactoryBean subclasses. The role of this method is to initialize EntityManagerFactory for set configuration. In our case, LocalContainerEntityManagerFactoryBean creates the instance of this class based on createEntityManagerFactory defined in org.hibernate.jpa.HibernatePersistenceProvider.

Freshly created EntityManagerFactory instance, called "native entity manager factory", is used further to generate "proxied entity manager factory". We can observe that when we try to get EntityManagerFactory bean through application context, as here:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext-test.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class JpaRepositoryTest {
	
  @Autowired
  private ApplicationContext context;
	
  @Test
  public void isEmfProxied() {
    assertTrue("EntityManagerFactory should be a proxied bean but it wasn't",     
      context.getBean("emf").getClass().toString().contains("Proxy"));
  }
}

If we try to print a class name of "emf" bean, we get a string like "class com.sun.proxy.$Proxy28". Thanks to it, Spring is able to return transaction-aware proxies for EntityManager.

Proxied entity manager for @PersistenceContext

Thanks to this transaction-aware proxies, EntityManager injected as @PersistenceContext can be used as a thread-safe object. It means that each requests will be handled by its own EntityManager. Why EntityManager is used through a proxy ? By definition (JSR-317 Final Release, section "7.3 Obtaining an Entity Manager Factory"), EntityManager is not thread safe. But according to the Spring's documentation, EntityManager injected through @PersistenceContext is "thread-safe proxy for the actual transactional EntityManager".

If you don't believe to the documentation, you can provoke an Exception by injecting persistence context and getting transaction manually (forbidden when using Spring's transactions):

@PersistenceContext
private EntityManager em;

// ... inside one method
em.getTransaction();

The call of getTransaction will throw an IllegalStateException with given message:

java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
  at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:198)
  at com.sun.proxy.$Proxy38.getTransaction(Unknown Source)
  at com.waitingforcode.test.JpaRepositoryTest.sharedContext(JpaRepositoryTest.java:45)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.lang.reflect.Method.invoke(Unknown Source)
  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)

If we go to the SharedEntityManagerCreator.java file and look at line number 198, we'll see the override of invoke method. It contains some check to verify the methods invoked through the proxy and one of them, getTransaction, always throw an IllegalStateException, with exactly the same error message as our:

else if (method.getName().equals("getTransaction")) {
  throw new IllegalStateException(
    "Not allowed to create transaction on shared EntityManager - " +
    "use Spring transactions or EJB CMT instead");
}

Further in the code we can read that the doGetTransactionalEntityManager method of EntityManagerFactoryUtils (from the same package) is invoked to get current entity manager. In they're no entity manager already created, a new instance is initialized. Finally, initially called method is invoked by corresponding entity manager.

Thread-safety proof of Spring's @PersistenceContext

Notice that you can inject an no thread-safe persistence context by using type="PersistenceContextType.EXTENDED" attribute in @PersistenceContext annotation. We'll prove that through some sample code. First, let's make a service like that:

@Service("productService")
public class ProductServiceImpl implements ProductService {

  @PersistenceContext
  private EntityManager persistenceContext;
	
  @Override
  @Transactional
  public void testPersistenceContext() {
    long time = System.nanoTime();
    Session session = persistenceContext.unwrap(Session.class);
    System.out.println("["+time+"] Session's instance is : "+
      session);
    System.out.println("["+time+"] Session's instance's hashCode equals to: "+
      session.hashCode());
    checkAnotherEm(time);
  }
	
  @Transactional
  private void checkAnotherEm(long time) {
    Session session = persistenceContext.unwrap(Session.class);
    System.out.println("["+time+"] Session's instance's hashCode from " +
      " checkAnotherEm equals to: "+session.hashCode());
  }
}	

This code will be invoked inside one controller:

  // ...
  @Autowired
  private ProductService productService;
	
  @RequestMapping(value = "persistence")
  public String testPersistenceContext() {
    productService.testPersistenceContext();
    return "success";
  }
  // ...

Now, let's make an test case and observe what is written into the logs:

// we still use the same test configuration with a 
// supplementary @WebAppConfiguration annotation
// ...
  @Autowired
  private WebApplicationContext wac;

  private MockMvc mockMvc;
	
  @Before
  public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
  }
	
  @Test
  public void sharedContext() {
    this.mockMvc.perform(get("/persistence"));
    this.mockMvc.perform(get("/persistence"));
    this.mockMvc.perform(get("/persistence"));	
  }
// ...

After executing this code, you can read from the logs following entries:

[21938940200259] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[21938940200259] Session's instance's hashCode equals to: 1776952746
[21938940200259] Session's instance's hashCode from checkAnotherEm equals to: 1776952746
[21938950870854] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[21938950870854] Session's instance's hashCode equals to: 229461995
[21938950870854] Session's instance's hashCode from checkAnotherEm equals to: 229461995
[21938954404224] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[21938954404224] Session's instance's hashCode equals to: 359134399
[21938954404224] Session's instance's hashCode from checkAnotherEm equals to: 359134399

As we expected, each request has its own transaction handled by exactly one EntityManager. In this case, EntityManager is wrapped to Hibernate's Session class. By analyzing theirs hashCodes we can check if they are one instance for all requests or not. But what happens if we change persistence context type to EXTENDED, like that:

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager persistenceContext;

Now, if you relaunch the same code, the persistence context's hashCode will be the same for all requests. So it won't be thread safe because the same instance will be used:

[22207107017642] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[22207107017642] Session's instance's hashCode equals to: 1299675041
[22207107017642] Session's instance's hashCode from checkAnotherEm equals to: 1299675041
[22207131374531] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[22207131374531] Session's instance's hashCode equals to: 1299675041
[22207131374531] Session's instance's hashCode from checkAnotherEm equals to: 1299675041
[22207137182849] Session's instance is : SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])
[22207137182849] Session's instance's hashCode equals to: 1299675041
[22207137182849] Session's instance's hashCode from checkAnotherEm equals to: 1299675041

In this article we learned about JPA basics in Spring environment. We saw that entity manager factory is a simple bean which is proxied. Thanks to it, it can creates transaction-aware proxies for persistence contexts (@PersistenceContext) used in the application. These contexts are thread safe when they aren't of EXTENDED type.

If you liked it, you should read:

The comments are moderated. I publish them when I answer, so don't worry if you don't see yours immediately :)

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!