Identity and Equality of JPA Entities

JPA entities contain an id field (or property) annotaded with @Id. For this post I will assume a single field, but compound ids are possible as well.

Entity objects are business objects, so it is generally accepted that we will have to supply equals and hashCode for entity classes. Let me point out here, that it’s very unlikely for our business code to call any of these methods directly, because we don’t normally need explicit comparisons of entity objects in our business code, but we use collections which in turn may call equals and hashcode for looking up or adding entries.

We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:
We have several expectations regarding the semantics of equals and collection operations expecially when looking at entity objects:

  1. Objects based on the same database entry should be the same regarding equals and objects from different db rows should be different

a) including objects from different entity managers,
b) including detached objects,
c) including new (transient) objects,
d) even if some non-id attribute has been changed (they will end up in ‚their‘ db records!).

  1. Hashcode-based collections (e. g. HashSet) should behave friendly, i. e.

a) adding multiple objects should be possible – even for new (transient) objects,
b) the collection should remain intact even if contained objects get persisted into the db.

If the entity has a business id, i. e. the id attribute has some business meaning and is set by the constructor, the expectations can easily be met by using exactly the id attribute in equals and hashCode.

If you choose to compare all fields instead of just the id, your implementation breaks expectation 1.d). If you choose to not implement equals and hashCode at all and rather use the methods derived from Object, your implementation breaks expectations 1.a) and 1.b).

So, as an intermediate conclusion, implement equals and hashCode in your entity classes and base them on just the id attribute(s):


@Entity
public class Foo {

@Id
private String id;

...

public Foo(String id, ...) {
this.id = id;
...
}

public int hashCode() {
return (this.id == null) ? 0 : this.id.hashCode();
}

public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Foo other = (Foo) obj;
if (this.id == null) {
return other.id == null;
}
return this.id.equals(other.id);
}

Things get more complicated, if you want to use generated ids. JPA supports you in this with the annotation @GeneratedValue which has to be placed on the id attribute in addition to @Id. The generator works for integral number fields and it’s best to abstain from primitive types in order to have the additional value null for unset ids. So you would use Integer, Long or BigInteger depending on the amount of data entries expected:

@Entity
public class Foo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

The problems with generated ids looking at identity and equality arise from the fact, that the entity manager will set the ids late – they may be populated not until the commit of the inserting transaction.

To begin with you will use the id field for comparison in equals and in hashCode as you did for business ids before. But if you do that in exactly the way shown above, you break expectation 2.a), because equals will evaluate all new (transient) objects as equal, as their ids are all null.

You can fix this by modifying equals such that it returns false if any of the compared ids are still unset:

public boolean equals(Object obj) {
...
if (this.id == null) {
return false;
}
return this.id.equals(other.id);
}

While this now meets expectation 2.a), it still breaks expectation 2.b): If you add some new entities to a HashSet and persist them afterwards, the collection will be corrupt due to the changed hash codes.

And bad news: There is no way to get around this, if using generated ids! In many application this still is no issue, because the program sequence „Create multiple objects“, „add them to a hash-based collection“, „persist the objects“, „use the collection“ is not very common and can be circumvented by persisting (and flushing) the objects before they get added to the collection.

Please take into account, that the usage of a hash-based collection may not be directly visible and instead be hidden as association collection in some other entity class.

What are your options, if you want to use generated ids and your business logic suffers from the hash-based collection problem discussed above? Well, the solution is to use an id populated early, i. e. in the constructor, which can be generated easily and – most advisable for performance – without hitting the database. java.util.UUID offeres the desired functionalty. It supplies 128 bit integers commonly expressed as 36 character string (32 hex digits and 4 dashes). java.util.UUID uses random numbers as base and offers a distribution which makes duplicates very unlikely. Other implementations exist, which use MAC addresses and random numbers.

@Entity
public class Foo {

@Id
private String id;

private String description;

public Foo(String description) {
this.id = UUID.randomUUID().toString();
...

By using uuids you cherry-pick the advantages from having early set ids while still having generated values.

The size of uuids may be painful. They are roughly four times the size of an integer, but storage requirements depend on your database product. Remember that they are ids, so every foreign key in the db has the same size. If that is a problem, you can resort to having an early set uuid for supporting equals and hashCode, which is not annotated with @Id. Instead you add another integer field as JPA generated id, i. a. annotated with @Id @GeneratedValue. Now all tables contain a number as primary key as well as a non-primary uuid column, but all foreign keys are just numbers.

So, the subject of JPA ids, which seems easy at first glance, is far from that in reality. You may use the following recipe when designing entity classes:

  • If your entity contains some identifying business attribute, take this as id. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
    (-> ChemicalElement in showcase).
  • If you don’t find a suitable business id, and …
    • if the hash-based collection problem discussed above is no problem for your application, use a @Id @GeneratedValue annotated integer id. Let equals and hashCode use exactly this attribute, but modify equals such that unset ids render a false return value. All expectations expressed above will be met with the exception of 2.b)
      (-> LabExperiment3NonNullIdEquality in showcase).
    • you want / have to use early set ids, use uuids.
      • If db size does not really matter or your model contains just a few associations, use the uuids directly as JPA ids. Let equals and hashCode use exactly this attribute. All expectations expressed above will be met
        (-> LabExperiment4UUIdEquality in showcase).
      • If you care about the storage consumption of foreign keys in your database, use the uuid just for equals and hashCode and add a separate @Id @GeneratedValue id to your class. Let equals and hashCode use exactly the uuid attribute. All expectations expressed above will be met
        (-> LabExperiment5AddIdEquality in showcase).

There is a showcase on https://github.com/GEDOPLAN/jpa-equals-demo demonstrating the various options. You can find the referenced classes there.

See you in our trainings at Berlin, Bielefeld, Cologne or your site!
http://gedoplan-it-training.de/


 

Advertisements

JPA mit dynamischer DB-Verbindung

In Java-EE-Anwendungen, die JPA zum DB-Zugriff nutzen, wird im Normalfall eine Datasource zur Spezifikation der Datenbankverbindung genutzt:

<persistence ...>
  <persistence-unit name="seminar">
    <jta-data-source>jdbc/seminar</jta-data-source>

Für eine so definierte Persistence Unit lässt man dann einen EntityManager injizieren – üblicherweise in einem CDI Producer:

public class EntityManagerProducer {
  @PersistenceContext(unitName = "seminar")
  EntityManager entityManager;

  @Produces
  public EntityManager getEntityManager() {
    return this.entityManager;

Das funktioniert so auch sehr gut, ist allerdings in Bezug auf die genutzte Datenbank recht statisch: Die Datasource – im Beispiel mit dem JNDI-Namen jdbc/seminar – muss zum Deployment-Zeitpunkt der Anwendung im Server konfiguriert sein. Zur Anwendungslaufzeit lässt sie sich nicht wechseln.

Um eine dynamische Zuordnung der DB zur Laufzeit zu ermöglichen, muss man die EntityManagerFactory per API initialieren und dabei die Connect-Parameter für die gewünschte Datenbank als Properties mitgeben:

<persistence ...>
  <persistence-unit name="showcase" transaction-type="JTA">
    <properties>
      <!-- Gemeinsame Parameter für die im Beispiel genutzten Datenbanken -->
      <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
      <property name="javax.persistence.jdbc.user" value="showcase" />
      <property name="javax.persistence.jdbc.password" value="showcase" />

      <!-- Diese Property wird eigentlich nicht benötigt, da sie beim Aufbau der EntityManagerFactory übergeben wird -->
      <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:" />
public class EntityManagerProducer {
  private EntityManagerFactory fetchEntityManagerFactory() {

    private ConcurrentHashMap<String, EntityManagerFactory> factoryMap = new ConcurrentHashMap<>();

    // Aktueller URL kommt aus anderem Service, z. B. "jdbc:h2:mem:showcase_1;DB_CLOSE_DELAY=-1"
    String url = ...; 

    // Falls nötig, Map-Eintrag für URL erstellen
    this.factoryMap.computeIfAbsent(url, u -> {
      Map<String, String> prop = new HashMap<>();

      // dabei URL als Property übergeben
      prop.put("javax.persistence.jdbc.url", url);

      // und DDL erlauben
      prop.put("eclipselink.ddl-generation", "create-or-extend-tables");
      prop.put("eclipselink.ddl-generation.output-mode", "database");
      prop.put("hibernate.hbm2ddl.auto", "update");

      return Persistence.createEntityManagerFactory("showcase", prop);
    });

    return this.factoryMap.get(url);
  }

...

Im Beispiel wird nur der Datenbank-URL dynamisch übergeben: Die Property javax.persistence.jdbc.url wird in Zeile 14 in ein Map-Obekt eingetragen und anschliessend zur Initialisierung der EntityManagerFactory verwendet. Im Beispiel wird ein ConcurrentHashMap genutzt, um die erzeugten Factories pro URL als Singletons zu führen.

Der Producer liefert nun einen EntityManager aus der so erzeugten EntityManagerFactory. Der Synchronisationstyp SynchronizationType.SYNCHRONIZED sorgt dafür, dass der EntityManager sich automatisch mit der JTA-Transaktion verbindet.
Da der EntityManager nun Application-managed ist, muss ein Disposer zum Schliessen vorgesehen werden:

...
  @Produces
  @RequestScoped
  EntityManager createEntityMnager()  {
    return fetchEntityManagerFactory().createEntityManager(SynchronizationType.SYNCHRONIZED);
  }

  void closeEntityManager(@Disposes EntityManager entityManager) {
    if (entityManager.isOpen()) {
      entityManager.close();

Auf diese Weise gelingt die Umschaltung der DB-Verbindung zur Laufzeit. Im Beispielprojekt sind die DB-URLs in einem separaten Service fest definiert. Sie könnten aber auch problemlos aus einer anderen, noch dynamischeren Quelle kommen.

Es zeigt sich hier einmal mehr, wie mächtig das Konzept der Producer in CDI ist: Die Dynamisierung der DBs geschieht alleine dort. Die konkreten DB-Zugriffe in der restlichen Anwendung bleiben unverändert.

Der sich nun möglicherweise einstellenden Euphorie muss allerdings ein „Aber“ entgegengestellt werden: Die gezeigte Lösung nutzt ein Feature aus, das in der JPA-Spezifikation nur schwach spezifiziert ist. Um einen EntityManager an JTA anbinden zu können, muss die Persistence Unit mit dem Parameter transaction-type="JTA" versehen sein. In dem Fall wird in der Spezifikation die Nutzung einer Datasource „erwartet“ – nicht etwa „verlangt“. Es ist sehr schade, dass die JPA-Spezifikation eine ganze Reihe solcher unterschiedlich interpretierbaren Sätze enthält.
In der Beispiellösung wird die DB-Verbindung über Properties angegeben, was Hibernate als JPA-Provider ohne Murren akzeptiert, während EclipseLink den Dienst verweigert. Insofern läuft die gezeigte Lösung nicht auf einem GlassFish (mit EclipseLink), wohl aber auf WildFly (mit Hibernate).

Den gezeigten Code finden Sie in einem kompletten Demoprojekt auf GitHub.

Persistence Unit Defaults

JPA Entities defiieren wir i. d. R. mit Hilfe von Annotationen, d. h. das Mapping von Klassen und Attributen zu Tabellen und Spalten befindet sich im Java-Quelltext in Form von @Entity, @Table, @Id, @Column etc. Für Altanwendungen (vor der Einführung von Annotationen) und andere Situationen, in denen Annotatione nicht genutzt werden können kann man das Mapping alternativ auch im Descriptor orm.xml hinterlegen.

orm.xml hat aber nicht nur diese Legacy-Aufgabe; es können auch Default-Werte für alle Entities hinterlegt werden, z. B. das Schema, in dem die Tabellen per Default liegen:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">
<persistence-unit-metadata>
<persistence-unit-defaults>
<schema>S1060</schema>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>

Analog können angegeben werden:

  • <catalog>...</catalog>: Katalog der Tabellen,
  • <delimited-identifiers/>: Tabellennamen in Quotes setzen,
  • <access>FIELD</access>: Field Access verwenden (analog PROPERTY),
  • <cascade-persist/>: CascadeType.PERSIST implizit für alle Relationen annehmen.

Zudem ist es möglich, beliebig viele Entity Listener anzugeben:


<entity-listeners>
<entity-listener class="..."></entity-listener>
<entity-listener class="..."></entity-listener>
</entity-listeners>

Neben der standardmäßig genutzten Datei orm.xml können mit den Elementen <mapping-file>...</mapping-file> in der persistence.xml beliebige weitere Mapping Files referenziert werden (als Classpath Ressources).

Eclipselink und 0 als ID/PK-Wert

JPA-Entities können ID-Attribute der primitiven Typen int oder long (u. a.) besitzen. Für diese ist der Wert 0 grundsätzlich OK. Eclipselink interpretiert in der Default-Konfiguration solche Werte jedoch als „nicht gesetzt“, was für generierte IDs eine Anforderung eines neuen Wertes bei persist oder merge auslöst.

Soll der Wert 0 als normaler Wert ohne die genannte Sonderbedeutung genutzt werden, muss man Eclipselink dahingehend umkonfigurieren. Dazu muss in der persistence.xml die folgende Property eingesetzt werden:

<property name="eclipselink.id-validation" value="NULL"/>

Damit wird nur noch der Wert null als „nicht gesetzt“ interpretiert. In der Konsequenz können dann generierte ID natürlich keinen primitiven Typ haben.

Anstelle der globalen Einstellung mit Hilfe der genannten Property kann dasselbe auch pro Entity-Klasse mit Hilfe der Annotation @PrimaryKey erreicht werden.

Weitere Details siehe Java Persistence API (JPA) Extensions Reference for EclipseLink.

Hibernate-Generator für das JPA-Metamodell schließt XML-Dateien nicht

Wenn man den Metamodell-Generator von Hibernate zur Erzeugung der JPA-Metamodellklassen nutzt, stellt man gelegentlich fest, dass Source- oder Target-Dateien über den Generatorlauf hinaus blockiert bleiben. Das äußert sich dann in den gängigen IDEs so, dass ein Project Clean (Eclipse) bzw. ein Clean (NetBeans) mit der Meldung fehlschlagen, dass die Datei persistence.xml (oder auch orm.xml) nicht gelöscht werden könnten. Der Effekt scheint nur unter Windows aufzutreten (bzw. aufzufallen).

Eine Lösung für das Problem existiert m. W. noch nicht. Es bleibt also nur der Wechsel des Generators. So kann bspw. der EclipseLink-Generator genutzt werden, wie ich dies in einem früheren Post beschrieben habe. Da die generierten Klassen nicht vom genutzten Provider abhängen, kann der EclipseLink-Generator auch genutzt werden, wenn im Projekt ansonsten z. B. Hibernate eingesetzt wird.

JPA-Metamodell mit Hilfe von EclipseLink erzeugen

EclipseLink bietet – wie andere JPA-Provider auch – einen Generator zur Erzeugung des statischen Metamodells an. Er ist in einem separaten JAR verfügbar, das während des Compile-Laufs im Classpath sein muss. Der Generator klinkt sich dann über das Java-6-Serviceloader-Konzept in den Compiler ein.

Für Maven-Projekte wird diese Dependency benötigt:

<dependency>
  <groupId>org.eclipse.persistence</groupId>
  <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
  <version>2.5.1</version>
  <scope>provided</scope>
  <optional>true</optional>
</dependency>

Die generierten Klassen werden per Default in target/generated-sources/annotations abgelegt.

JPA-Metamodell-Generierung in Eclipse automatisch konfigurieren

Die Erzeugung des statischen Metamodells für JPA-Entities wird vom (normalen) Java-Compiler ab Java 6 automatisch erledigt, wenn ein entsprechender Metamodell-Generator im Classpath ist, z. B. hibernate-jpamodelgen. Ein Maven-Projekt braucht dazu nur die folgende Konfiguration:

<project …>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <inherited>true</inherited>
        <configuration>
          <generatedSourcesDirectory>
            target/generated-sources/annotations
          </generatedSourcesDirectory>

        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>7.0</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-jpamodelgen</artifactId>
      <version>1.3.0.Final</version>
      <scope>provided</scope>
      <optional>true</optional>
      <exclusions>
        <exclusion>
          <groupId>org.hibernate.javax.persistence</groupId>
          <artifactId>hibernate-jpa-2.0-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</project>

Statt der Dependency javax:javaee-api würde natürlich eine ausreichen, die nur JPA umfasst.

Mit dieser Projektkonfiguration wird zu einer Entity-Klassen Xyz in target/generated-sources/annotations eine Metamodellklasse namens Xyz_ erzeugt und kompiliert.

Um dieses Projekt auch in Eclipse bearbeiten zu können, muss vor dem Import des Projektes in der Workspace-Einstellung Maven -> Annotation Processing die Option Automatically configure JDT APT aktiviert werden. Diese Einstellung gehört zum Plugin m2e-apt, das seit einiger Zeit Bestandteil von Eclipse mit Maven-Integration ist, also bspw. im JBoss Developer Studio 7, aber auch bei Bedarf aus dem Marketplace nachinstalliert werden kann. Wird das Projekt dann als Maven-Projekt in Eclipse importiert, werden die Projekteigenschaften Java -> Compiler -> Annotation Processing und Java -> Compiler -> Annotation Processing -> Factory Path automatisch gefüllt, sodass auch bei der Bearbeitung durch Eclipse Metamodellklassen generiert werden.