Skip to content

8. Spring Data JPA OpenJpa

8.1. Introduction

We are reusing the previous architecture, which we are now implementing with a JPA/OpenJpa layer.

8.2. Setting up the development environment

Using STS, download the [myql-config-jpa-eclipselink] project [1-4]:

then import the [mysl-config-jpa-openjpa] project [5] located in the [<examples>/spring-database-config/mysql/eclipse] folder [6]:

Once this is done, refresh the Maven environment (Alt-F5) for all projects in [Package Explorer]:

 

Then, to verify the working environment, run the build configuration named [spring-jpa-generic-JUnitTestDao-openjpa]:

This configuration runs the [JUnitTestDao] test. This test should pass:

 

8.3. The JPA layer configuration project

  

The purpose of this project is to configure the JPA layer of the architecture shown below:

8.3.1. Maven Configuration

The project is a Maven project configured by the following [pom.xml] file:


<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>generic-config-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mysql openjpa configuration</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- variable dependencies ********************************************** -->
        <!-- JPA provider -->
        <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- constant dependencies ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- legacy JDBC configuration -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>generic-config-jdbc</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • lines 5–7: the Maven artifact generated by this project. It is the same as that of the [mysql-config-jpa-hibernate] project. This means that only one of these projects can be active at any given time;
  • lines 10–14: the parent Maven project that sets the version of most of the dependencies required by the project;
  • lines 19–23: the OpenJpa library;
  • lines 26–29: the Spring Data library;
  • lines 32–34: the JPA layer configuration project is based on the JDBC layer configuration project, which defines, among other things, the JDBC driver for the DBMS being used and the database connection details;
  • lines 35–40: the JDBC layer configuration project includes the [Spring JDBC] library, which is replaced here by the [Spring Data JPA] library. Therefore, we specify not to include it in the project dependencies. However, if it remains, this does not cause any errors;

In the end, the dependencies are as follows:

  

8.3.2. Spring Configuration

 

The [ConfigJpa] class configures the Spring project as follows:


package generic.jpa.config;

import generic.jdbc.config.ConfigJdbc;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@Import({ConfigJdbc.class})
public class ConfigJpa {

    // the JPA provider
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        OpenJpaVendorAdapter openJpaVendorAdapter = new OpenJpaVendorAdapter();
        openJpaVendorAdapter.setShowSql(false);
        openJpaVendorAdapter.setDatabase(Database.MYSQL);
        openJpaVendorAdapter.setGenerateDdl(true);
        return openJpaVendorAdapter;
    }

    // JPA entity packages
    public final static String[] ENTITIES_PACKAGES = { "generic.jpa.entities.dbproduitscategories" };

    // data source
    @Bean
    public DataSource dataSource() {
        // TomcatJdbc data source
        DataSource dataSource = new DataSource();
        // JDBC access configuration
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // Initial open connections
        dataSource.setInitialSize(5);
        // result
        return dataSource;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan(ENTITIES_PACKAGES);
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // Transaction manager
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

This configuration is similar to the one described in Section 6.3.2 for the Hibernate JPA implementation. We will only detail the differences:

  • lines 23–30: the [jpaVendorAdapter] bean is now implemented using OpenJpa;

8.4. The [persistence.xml] file

  

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
    <persistence-unit name="generic-jpa-entities-dbproduitscategories" transaction-type="RESOURCE_LOCAL">
        <!-- JPA entities -->
        <class>generic.jpa.entities.dbproduitscategories.Category</class>
        <class>generic.jpa.entities.dbproduitscategories.Product</class>
        <class>generic.jpa.entities.dbproduitscategories.User</class>
        <class>generic.jpa.entities.dbproduitscategories.Role</class>
        <class>generic.jpa.entities.dbproduitscategories.UserRole</class>
        <exclude-unlisted-classes />
    </persistence-unit>
    <persistence-unit name="generic-jpa-entities-dbproduits" transaction-type="RESOURCE_LOCAL">
        <!-- JPA entities -->
        <class>generic.jpa.entities.dbproduits.Product</class>
        <exclude-unlisted-classes />
    </persistence-unit>
</persistence>
  • line 4: the persistence unit linked to the [dbproduitscategories] database. It can have any name (name attribute);
  • lines 6–10: the five JPA entities to be managed;
  • lines 13–17: another persistence unit linked to the [dbproduitscategories] database. OpenJpa allows for a persistence file containing multiple persistence units. EclipseLink does not. Recall that with Hibernate, we did not use a persistence file;

8.5. The test layer

  

The tests above are identical to those for the Spring JDBC, Spring JPA Hibernate, and Spring JPA EclipseLink implementations. Refer to the following pages if necessary:

  • [JUnitTestCheckArguments]: Section 4.11.1;
  • [JUnitTestDao]: Section 4.11.2;
  • [JUnitTestPushTheLimits]: section 4.11.3;
  • [JUnitTestProxies]: section 6.4.5;

The execution configurations to use are as follows:

For JPA entities to be woven by OpenJpa, Java applications or JUnit tests must be launched with a Java agent. This agent will weave the JPA entities before they are loaded by the JVM [http://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/ref_guide_pc_enhance.html]. Let’s look, for example, at the configuration of the [JUnitTestDao] test:

  • In the [Arguments] tab [1], we provide the Java agent reference to the JVM using the syntax [2]. The Java agent is the [openjpa-<version>.jar] archive found in the [<m2-repo>/org/apache/openjpa/<version>] folder. The <m2-repo> folder is specified in the Eclipse configuration ([3] below):

In [2] above, we used a user-defined variable [M2_REPO]:

 

We assigned the value shown in [3] to the [M2_REPO] variable.

The results obtained for the [JUnitTestDao] and [JUnitTestPushTheLimits] tests are as follows:

  • in [1], [JUnitTestPushTheLimits-EclipseLink]: 70.583 s;
  • in [2], [JUnitTestPushTheLimits-Hibernate]: 78.945 s;
  • in [3], [JUnitTestPushTheLimits-JDBC]: 36.09 s;
  • in [4], [JUnitTestPushTheLimits-OpenJpa]: 80.394 s;

The console results obtained for the [JUnitTestProxies] test are as follows:

Dumping the database --------------------------------
doNothing
Emptying the database --------------------------------
getShortCategoriesByName1 --------------------------------
Category type: PROXY
Category:
Exception: java.lang.NullPointerException, Message: null
Dumping the database --------------------------------
getLongCategoriesByName1 --------------------------------
Category type: POJO
Category:
1
Dumping the database --------------------------------
getShortProductsByName1 --------------------------------
Product type: PROXY
Product category name:
Exception: java.lang.NullPointerException, Message: null
Dumping the database --------------------------------
getLongProductsByName1 --------------------------------
Product type: POJO
Product category name:
category[0]

Here we see that when accessing the [Categorie.produits] field of a PROXY-type category and the [Produit.categorie] field of a PROXY-type product, we have a null pointer in both cases (lines 7 and 17).