Skip to content

8. Spring Data JPA OpenJpa

8.1. Introduction

Ми повертаємося до попередньої архітектури, яку тепер реалізуємо за допомогою шару JPA / OpenJpa.

8.2. Налаштування робочого середовища

За допомогою STS завантажте проект [myql-config-jpa-eclipselink] [1-4]:

потім імпортуйте проект [mysl-config-jpa-openjpa] [5], який знаходиться у папці [<exemples>/spring-database-config/mysql/eclipse] [6]:

Після цього скиньте налаштування середовища Maven (Alt-F5) для всіх проектів, що містяться в [Package Explorer]:

 

Потім, щоб перевірити робоче середовище, запустіть конфігурацію виконання з назвою [spring-jpa-generic-JUnitTestDao-openjpa]:

Ця конфігурація виконує тест [JUnitTestDao]. Цей тест повинен пройти успішно:

 

8.3. Проект конфігурації рівня JPA

  

Цей проект призначений для конфігурації шару JPA у наведеній нижче архітектурі:

8.3.1. Конфігурація Maven

Проєкт є проєктом Maven і налаштовується за допомогою такого файлу [pom.xml]:


<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>configuration mysql openjpa</name>

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

    <dependencies>
        <!-- змінні залежності ********************************************** -->
        <!-- JPA — постачальник -->
        <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- постійні залежності ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- успадкована конфігурація JDBC -->
        <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>
  • рядки 5–7: артефакт Maven, згенерований цим проєктом. Він такий самий, як і в проєкті [mysql-config-jpa-hibernate]. Це означає, що в будь-який момент часу активним може бути лише один із цих проєктів;
  • рядки 10–14: батьківський проект Maven, який визначає версії більшості залежностей, необхідних для проекту;
  • рядки 19–23: бібліотека OpenJpa;
  • рядки 26–29: бібліотека Spring Data;
  • рядки 32–34: проект конфігурації шару JPA базується на проекті конфігурації шару JDBC, який, серед іншого, визначає драйвер JDBC для використовуваного SGBD та координати бази даних, що має використовуватися;
  • рядки 35–40: проект конфігурації шару JDBC включає бібліотеку [Spring JDBC], яка тут замінена на бібліотеку [Spring Data JPA]. Тому рекомендується не включати її до залежностей проекту. Якщо вона залишиться, це, однак, не спричинить помилок;

У підсумку залежності мають такий вигляд:

  

8.3.2. Конфігурація Spring

 

Клас [ConfigJpa] налаштовує проект Spring наступним чином:


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 {

    // провайдер JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        OpenJpaVendorAdapter openJpaVendorAdapter = new OpenJpaVendorAdapter();
        openJpaVendorAdapter.setShowSql(false);
        openJpaVendorAdapter.setDatabase(Database.MYSQL);
        openJpaVendorAdapter.setGenerateDdl(true);
        return openJpaVendorAdapter;
    }

    // пакети сутностей JPA
    public final static String[] ENTITIES_PACKAGES = { "generic.jpa.entities.dbproduitscategories" };

    // джерело даних
    @Bean
    public DataSource dataSource() {
        // джерело даних TomcatJdbc
        DataSource dataSource = new DataSource();
        // конфігурація доступу JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // спочатку відкриті з'єднання
        dataSource.setInitialSize(5);
        // результат
        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();
    }

    // Менеджер транзакцій
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }
}

Ця конфігурація аналогічна тій, що детально описана в розділі 6.3.2 для реалізації JPA Hibernate. Ми детально опишемо лише відмінності:

  • рядки 23–30: бін [jpaVendorAdapter] тепер реалізовано за допомогою OpenJpa;

8.4. Файл [persistence.xml]

  

<?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 -->
        <class>generic.jpa.entities.dbproduitscategories.Categorie</class>
        <class>generic.jpa.entities.dbproduitscategories.Produit</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 -->
        <class>generic.jpa.entities.dbproduits.Produit</class>
        <exclude-unlisted-classes />
    </persistence-unit>
</persistence>
  • рядок 4: одиниця збереження, пов’язана з базою [dbproduitscategories]. Вона може мати будь-яку назву (атрибут name);
  • рядки 6–10: п’ять об’єктів JPA, які потрібно обробити;
  • рядки 13–17: ще одна одиниця збереження, пов’язана з базою [dbproduits]. OpenJpa дозволяє мати файл збереження з кількома одиницями збереження. EclipseLink цього не дозволяє. Нагадаємо, що з Hibernate ми не використовували файл персистентності;

8.5. Рівень тестування

  

Наведені вище тести ідентичні тестам для реалізацій Spring JDBC, Spring JPA Hibernate та Spring JPA EclipseLink. У разі потреби див. наступні сторінки:

  • [JUnitTestCheckArguments]: розділ 4.11.1;
  • [JUnitTestDao]: пункт 4.11.2;
  • [JUnitTestPushTheLimits]: пункт 4.11.3;
  • [JUnitTestProxies]: пункт 6.4.5;

Необхідно використовувати такі конфігурації виконання:

Щоб сутності JPA були доповнені (woven) за допомогою OpenJpa, Java-додатки або тести JUnit потрібно запускати за допомогою Java-агента. Він доповнить сутності JPA до того, як вони будуть завантажені JVM та [http://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/ref_guide_pc_enhance.html]. Розглянемо, наприклад, конфігурацію тесту [JUnitTestDao]:

  • на вкладці [Arguments] [1] вказується посилання на Java-агент у JVM відповідно до синтаксису [2]. Java-агентом є архів [openjpa-<version>.jar], який знаходиться у папці [<m2-repo>/org/apache/openjpa/<version>]. Папка <m2-repo> вказана у налаштуваннях Eclipse ([3] нижче):

У [2], наведеному вище, було використано визначену користувачем змінну [M2_REPO]:

 

Змінній [M2_REPO] було присвоєно значення, яке видно у [3].

Результати, отримані для тестів [JUnitTestDao] та [JUnitTestPushTheLimits], такі:

  • у [1], [JUnitTestPushTheLimits-EclipseLink]: 70,583 с;
  • у [2], [JUnitTestPushTheLimits-Hibernate]: 78,945 с;
  • у [3], [JUnitTestPushTheLimits-JDBC]: 36,09 с;
  • у [4], [JUnitTestPushTheLimits-OpenJpa]: 80,394 с;

Результати тесту [JUnitTestProxies], отримані на консолі, такі:

Vidage de la base de données --------------------------------
doNothing
Vidage de la base de données --------------------------------
getShortCategoriesByName1 --------------------------------
Catégorie de type : PROXY
Catégorie :
Exception : java.lang.NullPointerException, Message : null
Vidage de la base de données --------------------------------
getLongCategoriesByName1 --------------------------------
Catégorie de type : POJO
Catégorie :
1
Vidage de la base de données --------------------------------
getShortProduitsByName1 --------------------------------
Produit de type : PROXY
Nom de la catégorie du produit :
Exception : java.lang.NullPointerException, Message : null
Vidage de la base de données --------------------------------
getLongProduitsByName1 --------------------------------
Produit de type : POJO
Nom de la catégorie du produit :
categorie[0]

Тут видно, що при зверненні до поля [Categorie.produits] категорії типу PROXY та до поля [Produit.categorie] товару типу PROXY, в обох випадках (рядки 7 і 17) маємо покажчик null.