Nov 4, 2021 12:13:19 PM Thomas Dumont avatar

Using JPA

Why use JPA?

The data access layer of Lutece, based on native SQL and inspired by the EJB Entity 1.x model in POJO (Plain Old Java Object), is certainly simple and robust but relies on a specific implementation with limitations (weak code synthetic and repetitive, complex transactional management).

JPA is a very recent standardized API. It is part of the EJB3 specification of the JEE 5.0 platform that was released in 2007 (JPA 2.0 has been released for JEE 6.0).

This API is an abstraction layer allowing to use various implementations (Hibernate Red Hat, Oracle Toplink, OpenJPA, Datanucleus / Google AppEngine, ...) and no longer requires a container EJB because based on POJO.

Functionally JPA supports inheritance mechanisms, provides transactional service writing facilities, and an advanced query language.

The main concepts

Entity objects: These are the business objects. They constitute what is generally called the "model".

Entity manager : It is the entity manager that loads and writes objects from the database and manages these objects in memory. This object is usually instantiated by a factory supplied by the ORM implementation that has been selected.

DAO : These are the objects that perform the Data Access Object operations. In JPA the notion of CAD has evolved significantly. Indeed, while originally these objects were running the low level code to manipulate the database, these operations are now provided by the persistence framework. Their role in JPA is now closer to the role of the Home object.

persistence units : These are the persistence management units. In particular, they define the database access information and all the options applicable to this unit (transaction mode, isolation, etc.).

The principles and peculiarities of the new implementation of JPA in Lutece

The transformation of business classes into entities

This transformation includes several aspects

  • Class IDs must be classes and not basic types. Int must be converted to Integer or Long .
  • Entities must be Serializable
  • Annotations must be added:
    • at the class level
      • @ Entity : To indicate that this class corresponds to an entity
      • @ Table : To link this entity to a table in the database
    • at the getters
      • @ Id : To indicate that this getter matches the primary key of the entity
      • @ GeneratedValue : To complete @ Id if we want the primary key to be generated
      • @ Column : To link the field to a column of the table

Here is the code of a business object before transformation

/ **
 * This class represents business objects Level
 * /

public class Level
{
    private int _nId;
    private String _strName;

    / **
     * Returns the level right identifier
     *
     * @return the level right identifier
     * /
    public int getId ()
    {
        return _nId;
    }

    / **
     * Sets the level right identify
     *
     * @param nId the level right identify
     * /
    public void setId (int nId)
    {
        _nId = nId;
    }

    / **
     * Returns the name of the right level
     *
     * @return the level right name
     * /
    public String getName ()
    {
        return _strName;
    }

    / **
     * Sets the name of the right level
     *
     * @param strName the level right name
     * /
    public void setName (String strName)
    {
        _strName = strName;
    }
}

Here is the code transformed for JPA

/ **
 * This class reprsents business objects Fashion
 * /
@Entity
@Table (name = "core_level_right")
public class Level implements Serializable
{
    private Integer _nId;
    private String _strName;

    / **
     * Returns the level right identifier
     *
     * @return the level right identifier
     * /
    @Id
    @GeneratedValue (strategy = GenerationType.AUTO)
    @Column (name = "id_level", unique = true, nullable = false)
    public Integer getId ()
    {
        return _nId;
    }

    / **
     * Sets the level right identify
     *
     * @param nId the level right identify
     * /
    public void setId (Integer nId)
    {
        _nId = nId;
    }

    / **
     * Returns the name of the right level
     *
     * @return the level right name
     * /
    @Column (name = "name")
    public String getName ()
    {
        return _strName;
    }

    / **
     * Sets the name of the right level
     *
     * @param strName the level right name
     * /
    public void setName (String strName)
    {
        _strName = strName;
    }
}

The EntityManagerService

It is important to keep the possibility for each plugin to access a specific database that is not the portal's. It is therefore a question of preserving the notion of connection pool associated with the plugin. It is thus necessary to have as many EntityManager objects as there are specific databases to manage for the plugins of a Lutece instance. The EntityManagerService is therefore responsible for providing each plugin EntityManager corresponding to the database to which it is assigned. The assignment principle in managing the back-end plugins of a pool (or rather a persistence unit now) remains. This is, in summary, a Map containing the EntityManagerFactory initialized by the JPAStartupService. The creation of the EntityManager is delegated to Spring who manages the transactions. Generic DAO

The generics introduced by Java 5 and the persistence management mode proposed by JPA make it possible to create generic components.

Here is the interface of fr.paris.lutece.util.jpa.IGenericDAO proposed by Lutece

/ **
 * Class GenericDAO
 * @param <K> Type of the entity's key
 * @param <E> Type of the entity
 * /
public interface IGenericDAO <K, E>
{
    / **
     * Create an entity
     * @param entity The entity to create
     * /
    void create (E entity);

    / **
     * Update an entity
     * @param entity An entity that contains new values
     * /
    void update (E entity);

    / **
     * Remove an entity
     * @param key The key of the entity to remove
     * /
    void remove (K key);

    / **
     * Find an entity by its Id
     * @param key The entity key
     * @return The entity object
     * /
    E findById (K key);

    / **
     * Find all entities
     * @return A list of entities
     * /
    List <E> findAll ();

    / **
     * Synchronize the persistence context to the underlying database.
     * /
    void flush ();

    / **
     * Remove the given entity from the persistence context, cause a managed entity to become detached.
     * @param entity the entity
     * /
    void detach (E entity);
}

Several abstract classes support generic implementation:

  • JPAGenericDAO: Generic implementation independent of the Lutèce core.
  • JPALuteceDAO: Implementation integrating the recovery of the EntityManager from Lutece EntityManagerService. The method giving the name of the plugin is overloaded to select the EntityManager. This class is located at the package fr.paris.lutece.portal.service.jpa.
  • JPALuteceCoreDAO: Same as JPALuteceDAO but no method to overload; the core EntityManager is provided by default.

None of these classes use and should use classes from specific implementations such as Hibernate. The only external references must be based on javax.persistence. * Packages.

The implementation of a simple CAD of the core, such as LevelDAO, is then realized as follows:

/ **
 * Class LevelDAO
 * /
public class LevelDAO extends JPALuteceCoreDAO <Integer, Level> implements ILevelJpaDAO
{
}

or another classic example including an additional method to provide a ReferenceList

/ **
 * Class RoleJpaDAO
 * /
public class RoleJpaDAO extends JPALuteceCoreDAO <String, Role> implements IRoleJpaDAO
{

    public ReferenceList selectRolesList ()
    {
        ReferenceList list = new ReferenceList ();
        for (Role role: findAll ())
        {
            list.addItem (role.getRole (), role.getRoleDescription ());
        }
        return list;
    }
}

For a DAO of a plugin that can access a specific database, here is the example code

/ **
 * Class PluginDAO
 * /
public class PluginDAO extends JPALuteceDAO <String, Role> implements IRoleJpaDAO
{
    private static final String PLUGIN_NAME = "myplugin";

    @Override
    public String getPluginName ()
    {
        return PLUGIN_NAME;
    }
}

This CAD no longer derives from the JPALuteceCoreDAO class, but from JPALuteceDAO. It must therefore implement the getPluginName () method. The implementation to remember

Implementations JPA

Since JPA is an API, several implementations can be used to ensure persistence. However, not all offer the same functionality or coverage of the standard; for example the JPA implementation of datanucleus is far from covering the entire standard.

Only one implementation is currently included in Lutèce: Hibernate (the version below may not be the last update)

<dependency>
     <groupId>fr.paris.lutece.plugins</groupId>
     <artifactId>Module-jpa-hibernate</artifactId>
     <version>1.0.7</Version>
     <type>Lutece-plugin</type>
</dependency>

However, it is possible to integrate another implementation based on the jpa-hibernate_context.xml, the integration relying mainly on Spring + JPA. See the operation of JPAStartupService for more information on the data retrieved in the XML and the integration with the transaction manager (s) and properties retrieved in the context.

The main configuration file (usable by default, the configuration is usually done for developments or cache optimizations). Note that the configuration of the datasource is not to be done, and uses the properties of db.properties.

<? xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns: context = "http://www.springframework.org/schema/context"
xmlns:tx = "http://www.springframework.org/schema/tx" xmlns: jdbc = "http://www.springframework.org/schema/jdbc"
xmlns:p = "http://www.springframework.org/schema/p"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">


       <bean id = "jpaStartupService" class = "en.paris.lutece.portal.service.jpa.JPAStartupService" />

       <bean id = "jpaDialect" class = "org.springframework.orm.jpa.vendor.HibernateJpaDialect" />

       <bean id = "jpaVendorAdapter" class = "org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
       <! - uncomment to view SQL ->
                <! - this value can be set to true during developments ->
                <! - <property name = "showSql" value = "true" /> ->
       </bean>


        <bean id = "jpaPropertiesMap" class = "java.util.HashMap">
        <constructor-arg>
         <map>
                <entry key = "hibernate.dialect" value = "org.hibernate.dialect.MySQL5InnoDBDialect" />
                <entry key = "hibernate.format_sql" value = "true" />
                <entry key = "hibernate.id.new_generator_mappings" value = "true" />

                <! - db generation ->
                <! - uncomment the following line to enable db generation ->
                <! - this value can be set to update during developments ->
                <! - <entry key = "hibernate.hbm2ddl.auto" value = "update" /> ->

                <! - Cache configuration ->
                <! - uncomment the following lines to enable caching ->
                <! -
                <entry key = "javax.persistence.sharedCache.mode" value = "ENABLE_SELECTIVE" />
                <entry key = "hibernate.cache.use_query_cache" value = "true" />
                <entry key = "hibernate.cache.use_second_level_cache" value = "true" />
                <entry key = "hibernate.cache.region.factory_class" value = "org.hibernate.cache.SingletonEhCacheRegionFactory" />
                ->
         </map>
        </constructor-arg>
       </bean>

       <! - Monitoring (use of cache ...) ->
       <bean id = "jpa-hibernate.monitorService" class = "en.paris.lutece.plugins.jpa.modules.hibernate.service.HibernateMonitorService">
       <property name = "entityManagerService" ref = "entityManagerService" />
       <property name = "JMXEnabled" value = "false" />
       <property name = "statisticsEnabled" value = "false" />
       </bean>
</beans>

The jpaPropertiesMap map contains the initialization properties of Hibernate. See [1] for more information on available properties (including hibernate.dialect for cases where the base is not MySQL5).

Statistics are available in Back Office only when they are enabled (disabled by default).

MultiPools management

The multipool is managed in a "simplistic" way: creation and commit / revert transactions on all datasources. It is possible to integrate more robust commit mechanisms with JTA (Atomikos for example) with the Spring configuration.

In the case of a multipool with bases of different type (example MySQL and PostgreSQL), it is possible to overload the dialect for each of the pools in the db.properties: example with the portal pool in mysql and the pool myPlugin in PostgreSQL :

...
portal.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
...
monPlugin.dialect = org.hibernate.dialect.PostgreSQLDialect

By default, org.hibernate.dialect.MySQL5InnoDBDialect is used - it is actually the hibernate.dialect property of the jpa-hibernate_context.xml file.