Persistence Model

Data sources

Since HPM has multiple data sources we provide a corresponding extension point for the application layer to introduce the data sources and to provide needed configuration settings for the JPA persistence-units. Additionally to that the persistence layer gives access to the needed JPA objects so the application layer can also use raw JPQL statements if they absolutely must.

Extension point

The application layer contributes the datasources to the com.heiler.ppm.persistence.db.datasource extension point. The extension-point provides elements for the data source (identifier, alias) as well as the jpa persistence unit. The standard HPM application contributes three data sources MAIN, MASTER and SUPPLIER. For every supported database system there must be a separate persistence unit contribution which provides the configuration file for this unit. A persistence unit is a combination of the DataSource and the database management system with the corresponding link to the configuration file. All persistence unit configuration files are located in the configuration area's sub-directory called: database
Configuration files do have a naming schema like this: [HPM:dataSourceIdentifier].properties.template.[HPM:dbmsIdentifier] (e.g. main.properties.template.MSSQL2005)

Data source registry

The data source registry which can be obtained with PersistenceLayer.getContext().getDataSourceRegistry() provides methods to aquire a JPA EntityManager. Please take a look at the corresponding javadoc for DataSourceRegistry for details on the available methods.

Connection Pool

HPM uses the Tomcat connection pool which can be configured using the persistence-unit configuration files located in the configurationArea/database. Please see the connection pool documentation for details.

Persistence model

The Heiler Product Manager persistence model is build using standard java persistence technologies (JPA). The HPM platform provides base classes and interfaces which can (or sometimes must) be implemented by the application layer in order to function properly in the HPM context.

Example of an application level .db bundle:
images/download/attachments/82644299/PriceGroupDB.PNG

The persistence model must follow certain naming conventions as well as implementation rules which assure that the generic HPM functions can use them properly. These conventions are described in further sections. Additionally to that it makes life easier for everyone when similar things are implemented the same way.

Bundles

The persistence layer is strictly separated from other parts of the application and therefore we use own bundles for this layer. All persistence classes as well as JPQL named queries must be located in the plugins which end with .db. For example: com.heiler.ppm.article.db or com.heiler.ppm.catalog.db

This separation has to be maintained since we might need the persistence layer in the database setup again - without the whole HPM application.

Dependencies

The persistence model must not have any dependency to other then .db bundles or to third party libraries which are not already reexported by the platform .db bundles. It must be guaranteed that the set of all .db bundles can work on their own without having any dependency to .core, .server or .ui bundles, either directly or indirectly!

Model classes

Model classes (aka JPA Entities) must be located in the .model package and named exactly like the corresponding database table. Note that they must conform to the Java Bean standard, therefore the members must not be named like the database columns regarding the case of the first character.

JPA limitations

Generic functionality in HPM makes heavy usage of the JPA annotations as well as the meta models of the JPA provider etc. For abitrary reasons we had to limit the available JPA functionalities a bit.

  • Only bi-directional relations are allowed currently (so every @OneToMany annotation should have corresponding @ManyToOne)

  • Only 1:n or 1:1 relations are allowed, direct n:m mappings are not supported

  • The primary key of every jpa entity must be a bigint column named id which uses either a sequence or is an identity column

  • Only use attribute level annotations, no annotation on getters and setters

  • Only use sets for collections, no lists!

  • Only use objects, no primitives (Long instead of long!)

  • Every JPA entity must also have an implemented toString() method which omits null values.
    Note: only return the string values of own members, no containment references (not the parent, no children) should be logged in the toString() method

Available interfaces/base classes which must or can be used

Persistable

The persistable interface is a maker interface which identifies a class as a HPM persistable object. All persistence model classes must implement this interface, either directly or through a base class or interface.

Deletable

The deletable class provides members, methods and annotations for the SoftDelete feature which has been introduced with HPM 5.3. JPA Entities which must support the soft delete feature should subclass this class, directly or indirectly.

The deletable class also declares all JPA filters for the SoftDelete feature (aliveOnly, deadOnly, deadSince), so they do not have to be declared in the model class itself. For performance reasons, all @OneToMany attributes should have the following JPA filters (for SoftDelete feature) annotated:

@Filters( { @Filter( name = Persistable.FILTER_ALIVE_ONLY ), @Filter( name = Persistable.FILTER_DEAD_ONLY ), @Filter( name = Persistable.FILTER_DEAD_SINCE ) } )

Auditable

The auditable class extends the deletable class and adds members, methods and annotations for the physical audit log functionality which has also been introduced in HPM 5.3.
JPA Entities which must store when and by whom they have been created, changed, deleted should subclass from Auditable.
The database tables must have the following columns in order to be able to use the Auditable interface properly:

Name

Datatype

Nullable

CreationUserID

bigint

yes

CreationTimestamp

datetime

yes

ModificationUserID

bigint

yes

ModificationTimestamp

datetime

yes

DeletionUserID

bigint

yes

DeletionTimestamp

datetime

yes

AuditLogSupport

The AuditLogSupport class reflects the standard business audit log entity which has been introduced for every root entity of HPM 5.3. In contrast to the Auditable this class represents all members for the corresponding AuditLogEntity like ArticleAuditLog or CatalogAuditLog etc. The AuditLogSupport is no physical audit log only (which stores audit log in a simple but effective way for every table) but a more business driven feature. Clients can implement arbitraty AuditLogProviders which have then their own logging algorithms. AuditLogSupport is only available for root entities!
The database table which implements the audit log support must follow the following scheme:
The AuditLog table naming convention is: <Tablename of root entity>AuditLog (e.G. ArticleAuditLog, StructureGroupAuditLog etc.)

The table has a foreign key to it's corresponding root/revision root table and an unique index on the same and the classifier column. Therefore we have a 1:n relationship, where there can be only one record per item and classifier.

Name

Datatype

Nullable

Classifier

nvarchar(50)

no

CreationUserID

bigint

yes

CreationTimestamp

datetime

yes

ModificationUserID

bigint

yes

ModificationTimestamp

datetime

yes

DeletionUserID

bigint

yes

DeletionTimestamp

datetime

yes

Res_Text250_01

nvarchar(250)

yes

Res_Text250_02

nvarchar(250)

yes

Res_Text250_03

nvarchar(250)

yes

Res_Text250_04

nvarchar(250)

yes

Res_Text2G_01

ntext

yes

Res_Text2G_02

ntext

yes

Res_Text2G_03

ntext

yes

Res_Text2G_04

ntext

yes

Res_Text2G_05

ntext

yes

Res_Text2G_06

ntext

yes

Res_Text2G_07

ntext

yes

Res_Text2G_08

ntext

yes

Res_Bit_01

bit

yes

Res_Bit_02

bit

yes

Res_Bit_03

bit

yes

Res_Bit_04

bit

yes

Res_Bit_05

bit

yes

Res_Bit_06

bit

yes

Res_Bit_07

bit

yes

Res_Bit_08

bit

yes

Res_Int_01

bigint

yes

Res_Int_02

bigint

yes

Res_Int_03

bigint

yes

Res_Int_04

bigint

yes

Res_Int_05

bigint

yes

Res_Int_06

bigint

yes

Res_Int_07

bigint

yes

Res_Int_08

bigint

yes

Res_BigDecimal12_01

decimal(12,4)

yes

Res_BigDecimal12_02

decimal(12,4)

yes

Res_BigDecimal12_03

decimal(12,4)

yes

Res_BigDecimal12_04

decimal(12,4)

yes

Res_BigDecimal16_01

decimal(16,6)

yes

Res_BigDecimal16_02

decimal(16,6)

yes

Res_BigDecimal16_03

decimal(16,6)

yes

Res_BigDecimal16_04

decimal(16,6)

yes

Res_DateTime_01

datetime

yes

Res_DateTime_02

datetime

yes

Res_DateTime_03

datetime

yes

Res_DateTime_04

datetime

yes

Example

An abbreviated example of a typical HPM persistence model class: com.heiler.ppm.article.db.model.ArticleAttribute

@Entity
@DynamicInsert
@DynamicUpdate
@Table( name = "`ArticleAttribute`" )
public class ArticleAttribute extends Auditable implements FieldHandled
{
@Id
@GeneratedValue( strategy = GenerationType.AUTO, generator = "SEQ_ArticleAttribute" )
@SequenceGenerator( name = "SEQ_ArticleAttribute", sequenceName = "`SEQ_ArticleAttribute`", allocationSize = 50 )
@Column( name = "`ID`" )
private Long id = null;
 
@ManyToOne( fetch = FetchType.LAZY )
@JoinColumn( name = "`ArticleRevisionID`", nullable = false )
private ArticleRevision articleRevision = null;
 
@Column( name = "`Identifier`" )
private String identifier = null;
 
 
@Column( name = "`NameInKeyLanguage`" )
private String nameInKeyLanguage = null;
 
 
@OneToMany( mappedBy = "articleAttribute", cascade = CascadeType.ALL, fetch = FetchType.EAGER )
@Filters( { @Filter( name = Persistable.FILTER_ALIVE_ONLY ), @Filter( name = Persistable.FILTER_DEAD_ONLY ),
@Filter( name = Persistable.FILTER_DEAD_SINCE ) } )
@Fetch( FetchMode.SUBSELECT )
private Set< ArticleAttributeLang > articleAttributeLangs = new HashSet< ArticleAttributeLang >();
 
public Long getId()
{
return this.id;
}
 
 
public void setId( Long newId )
{
this.id = newId;
}
 
 
public Set< ArticleAttributeLang > getArticleAttributeLangs()
{
return this.articleAttributeLangs;
}
 
 
public void setArticleAttributeLangs( Set< ArticleAttributeLang > newArticleAttributeLangs )
{
this.articleAttributeLangs = newArticleAttributeLangs;
}
 
 
//[...] getters and setters for all other fields too [...]
 
 
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append( "ArticleAttribute [" ); //$NON-NLS-1$
if ( this.id != null )
{
builder.append( "id=" ); //$NON-NLS-1$
builder.append( this.id );
builder.append( ", " ); //$NON-NLS-1$
}
if ( this.identifier != null )
{
builder.append( "identifier=" ); //$NON-NLS-1$
builder.append( this.identifier );
builder.append( ", " ); //$NON-NLS-1$
}
if ( this.nameInKeyLanguage != null )
{
builder.append( "nameInKeyLanguage=" ); //$NON-NLS-1$
builder.append( this.nameInKeyLanguage );
builder.append( ", " ); //$NON-NLS-1$
}
builder.append( "]" ); //$NON-NLS-1$
return builder.toString();
}
}

Extension point

Since JPA is not able to have modular persistence units HPM implemented it's own persistence unit configuration using the com.heiler.ppm.persistence.db.mapping extension point. JPA entity classes or packages containing JPA entities must be contributed to this extension point, for each persistence unit they should be available for.

The usual JPA implementations require some sort of persistence.xml file which contains the packages or classes which are available as JPA entities. Since HPM is a highly modular application we couldn't provide this kind of file at all - therefore we configured the JPA provider programatically

Named queries

Named queries offer a lot advantages over dynamic string concatenation or just normal HQL strings.

  • they will be cached by hibernate and JDBC which improves performance drastically in case they will be used often

  • they will be compiled at server startup which makes sure that - in case the server starts at all - all queries are at least syntactically correct

  • they are easy to use and to find if you follow our conventions

Conventions

Named queries have to be defined as annotations - usually they will be integrated in the JPA Entities which is, not a very good practice in our opinion. Therefore we use separate classes which only define the named queries and might also contain methods to apply the query to a jpa entity manager or something similar. Classes which contain named queries must be located in the .query package and should be named by the application area for which they contain queries of course.

Example

com.heiler.ppm.article.db.query.ArticleRelationJPQL

/** @formatter:off*/
@NamedQueries( {
@NamedQuery( name = ArticleRelationJPQL.FIND_ARTICLES_BY_CATALOGS,
query = "SELECT ar.catalogId, ar.article.id, ar.entityId "
+ " FROM ArticleRevision ar "
+ " WHERE ar.catalogId IN (:catalogIds) ")
} )
@MappedSuperclass
/** @formatter:on */
public class ArticleRelationJPQL
{
public static final String FIND_ARTICLES_BY_CATALOGS = "hpm.articles.findArticlesByCatalogs"; //$NON-NLS-1$
public static final String PARAM_CATALOG_ID = "catalogIds"; //$NON-NLS-1$
 
/**
* Finds all articles which belong to the given catalogs
* @return query with articleRevision.catalogId, articleRevision.article.id, articleRevision.entityId
*/
public static Query findArticlesByCatalogs( EntityManager entityManager, Collection< Long > catalogIds )
{
Query query = entityManager.createNamedQuery( FIND_ARTICLES_BY_CATALOGS );
query.setParameter( PARAM_CATALOG_ID, catalogIds );
return query;
}
}
  • Formatter settings
    The @formatter:off javadoc annotation will prevent the eclipse code formatting from reformating the named queries. This must be turned on in the Eclipse preferences:

    images/download/attachments/82644299/EclipseFormatterSettings.bmp
  • MappedSuperclass
    The @MappedSuperclass annotation makes sure that the class will be evaluated by the JPA provider although it is no real JPA entity.

Extension point

Classes containing named queries are also some kind of JPA entity and therefore must also be made visible to the persistence unit configuration by contributing the files or packages to the com.heiler.ppm.persistence.db.mapping extension point.

3rd party libraries

Since HPM 5.3 we rely on the JPA standard in all areas where possible. Sometime it is still necessary to have access to the underlying JPA provider's classes. HPM uses Hibernate as JPA provider which is located in the org.hibernate bundle in the target platform. You can obtain the Hibernate session using:

DataSourceRegistry dsr = PersisteLayer.getContext().getDataSourceRegistry();
EntityManager entityManager = dsr.aquireManager(...);
Session session = entityManager.unwrap(Session.class);

To obtain the session factory use:

DataSourceRegistry dsr = PersisteLayer.getContext().getDataSourceRegistry();
EntityManagerFactory factory = dsr.getFactory(...);
SessionFactory sessionFactory = factory.unwrap(SessionFactory.class);