Aus A mach B

Konvertierungen mithilfe von JPA Attribute Converters

Thorben Janssen

© Shutterstock.com/Tashatuvango

Mit dem Java Persistence API verfügt die Java-Enterprise-Welt über eine weit verbreitete Spezifikation für die standardisierte Speicherung und den Zugriff auf Daten in einer Datenbank. Aber was ist, wenn die Standardpersistierung nicht ausreicht und eine spezielle Persistierung einer Eigenschaft erforderlich ist? Bis einschließlich JPA 2.0 war dies im Rahmen der Spezifikation nur schwierig zu erreichen und wurde durch die Einführung von Attribute Converters in JPA 2.1 stark vereinfacht.

Mit dem Java Persistence API 2.1 wurde das Konzept der Attribute Converter eingeführt, mit denen eine Konvertierung für einzelne Eigenschaften einer Entität auf einfache Weise möglich ist. Ausgeschlossen von der Konvertierung sind dabei alle Bestandteile der ID, Versions- und Beziehungseigenschaften sowie explizit als Enumerated oder Temporal gekennzeichnete Eigenschaften. Die Konvertierung wird für jeden Zugriff auf die konvertierte Eigenschaft berücksichtigt, sodass die Konvertierung nicht nur beim Lesen und Schreiben, sondern auch beim Ausführen von Abfragen zum Tragen kommt. Die Konvertierung erfolgt dabei für den Entwickler vollständig transparent.

Abb. 1: Schematische Darstellung der Verwendung des Attribute Converters

Abb. 1: Schematische Darstellung der Verwendung des Attribute Converters

Die offensichtlichste Verwendung eines Konverters ist natürlich die Persistierung eines nicht unterstützten Datentyps, aber auch die Umwandlung eines unterstützten Datentyps in eine andere Darstellung ist denkbar (siehe z.B. hier).

Im Rahmen dieses Artikels wird beispielhaft ein Konverter vorgestellt, der Objekte der Klasse java.awt.Color in Objekte der Klasse java.lang.String umwandelt. Dieser wird anschließend verwendet, um die durch die RectangleEntity (Listing 1) abgebildeten Rechtecke mit Größen- und Farbinformationen in der Datenbank zu speichern, und um nach Rechtecken mit einer vorgegebenen Farbe zu suchen. Dazu wird bei allen Schreiboperationen der Attribute Converter aufgerufen, um die Eigenschaft der Entität in einen String umzuwandeln. Bei allen Leseoperationen wird der Attribute Converter verwendet, um den String wieder in ein Objekt vom Typ java.awt.Color zu konvertieren (Abb. 1).

Um Attribute Converter verwenden zu können, wird eine JPA-2.1-Implementierung benötigt, z. B. EclipseLink ab Version 2.5 (JPA-2.1-Referenzimplementierung) oder Hibernate ab Version 4.3. Für das Beispiel wurde ein Wildfly-8.1.0.Final-Applikationsserver mit Hibernate 4.3.5 verwendet. Der hier in Auszügen dargestellte Quellcode steht auf GitHub zur Verfügung.

Listing 1

@Entity
public class RectangleEntity
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  @Column
  private Integer x;

  @Column
  private Integer y;

  @Column
  private Color color;

  ...
}

Typkonvertierung vor JPA 2.1

Die Implementierung einer Typkonvertierung war auch schon vor JPA 2.1 möglich. Dazu wurden in der Regel entweder proprietäre Features der verschiedenen Frameworks oder EntityListener verwendet. Auch wenn proprietäre Features den Standard häufig sinnvoll erweitern, erzeugen sie dennoch eine starke Abhängigkeit zu einem spezifischen Framework und sollten daher möglichst vermieden werden. Daher werden diese im Rahmen dieses Artikels nicht näher betrachtet. Mithilfe von EntityListenern kann vor und nach dem Anlegen, Bearbeiten und Löschen einer Entität zusätzlicher Code ausgeführt werden. Für eine Konvertierung einzelner Eigenschaften sind diese allerdings nicht gut geeignet.

Einer von zwei großen Nachteilen ist, dass der EntityListener spezifisch für eine Entität und nicht für einen Datentyp implementiert wird. Dieser lässt sich somit nicht wiederverwenden und muss für jede Entität, die den zu konvertierenden Typ verwendet, erneut implementiert werden. Auch wenn die eigentliche Konvertierung in eine Hilfsmethode ausgelagert wird, stellt dies einen unnötigen Mehraufwand und eine zusätzliche Fehlerquelle dar.

Ein weiterer Nachteil ist, dass die durch den EntityListener durchgeführte Konvertierung nicht bei der Auswertung von Abfrageparametern berücksichtigt wird. Der Entwickler muss also selbst dafür sorgen, dass bei jeder Abfrage, die einen zu konvertierenden Parameter verwendet, die Konvertierung berücksichtigt wird. Dies stellt eine unnötige Fehlerquelle dar und erschwert es zusätzlich, in einer bestehenden Codebasis die Konvertierung zu ändern oder neu einzuführen.

Durch die Verwendung eines Attribute Converters können diese Probleme vermieden werden. Insgesamt ist damit eine einfachere und weniger fehleranfällige Implementierung der Konvertierung möglich.

Implementierung eines Attribute Converters

Die Implementierung eines Attribute Converters besteht aus nur einer Klasse. Diese muss mit javax.persistence.Converter annotiert sein und das Interface javax.persistence.AttributeConverter<X, Y> implementieren. Dabei repräsentiert X den Typ der Eigenschaft der Entität und Y den Datentyp, der in die Datenbank geschrieben werden soll. Das Interface AttributeConverter definiert die Methoden convertToDatabaseColumn(X attribute) und convertToEntityAttribute(Y dbData), die zur Konvertierung des Datentyps der Entität in den zu speichernden Datentyp sowie in die entgegengesetzte Richtung verwendet werden.

Listing 2

@Converter
public class ColorConverter implements AttributeConverter<Color, String> {

  private static final String SEPARATOR = ";";

  /**
  * Convert Color object to a String 
  * with format red;green;blue;alpha
  */
  @Override
  public String convertToDatabaseColumn(Color color) {
    StringBuilder sb = new StringBuilder();
    sb.append(color.getRed()).append(SEPARATOR)
    .append(color.getGreen())
    .append(SEPARATOR)
    .append(color.getBlue())
    .append(SEPARATOR)
    .append(color.getAlpha());
    return sb.toString();
  }

  /**
  * Convert a String with format red;green;blue;alpha
  * to a Color object
  */
  @Override
  public Color convertToEntityAttribute(String colorString) {
    String[] rgb = colorString.split(SEPARATOR);
    return new Color(Integer.parseInt(rgb[0]), 
    Integer.parseInt(rgb[1]),
    Integer.parseInt(rgb[2]), 
    Integer.parseInt(rgb[3]));
  }
}

In diesem Beispiel soll ein Objekt vom Typ Color als String in der Datenbank gespeichert werden. Die Konvertierung erfolgt durch den in Listing 2 dargestellten ColorConverter, der das Interface javax.persistence.AttributeConverter<java.awt.Color, java.lang.String> implementiert.

Die Konvertierung von Color zu String und zurück ist in diesem Beispiel bewusst einfach gehalten. Die Eigenschaften red, green, blue und alpha des Color-Objekts werden zum Speichern, durch ein Semikolon getrennt, in ein String-Objekt geschrieben. Werden die Werte aus der Datenbank ausgelesen, wird der String am Trennzeichen wieder zerlegt und der Konstruktor der Klasse Color mit den so ermittelten Werten aufgerufen (Listing 2). Für andere Anwendungsfälle sind auch deutlich aufwändigere Konvertierungsverfahren denkbar.

Der so erstellte ColorConverter kann jetzt zur Konvertierung aller Eigenschaften vom Typ java.awt.Color in den Typ java.lang.String verwendet werden.

Verwendung eines Attribute Converters

Der so implementierte Attribute Converter kann auf verschiedene Art und Weise verwendet werden. Dabei besteht die Möglichkeit, die Konvertierung für alle Eigenschaften eines Typs oder für jede Eigenschaft einzeln festzulegen.

Implizit wird der Converter bereits durch die Eigenschaft autoApply der javax.persistence.Converter-Annotation für alle Eigenschaften des entsprechenden Typs aktiviert. Diese globale Konfiguration kann für einzelne Eigenschaften überschrieben und somit die Verwendung des Konverters verhindert werden.

Die spezifische Konfiguration für eine Eigenschaft kann per Annotation oder XML erfolgen. Dazu kann die Annotation javax.persistence.Convert eingesetzt werden, die über die Eigenschaft converter verfügt. Mit dieser kann die zu benutzende Converter-Klasse definiert werden. Diese Variante kommt im folgenden Beispiel zum Einsatz (Listing 3).

Listing 3

@Entity
public class RectangleEntity
{
  ...
  
  @Column
  @Convert(converter = ColorConverter.class)
  private Color color;
  
  ...
}

Alternativ kann die Konfiguration in der Mapping-Datei vorgenommen werden. Die für unser Beispiel passende Konfiguration ist in Listing 4 dargestellt.

Die Konfiguration mittels XML bietet die Möglichkeit, die Verwendung des Converters außerhalb der Implementierung zu definieren und somit auch zu einem späteren Zeitpunkt hinzuzufügen, zu ändern oder zu entfernen.

Listing 4

<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_2_1.xsd">

  <entity class="blog.thoughts.on.java.jpa21.entity.RectangleEntity">
    <convert 
      converter=" blog.thoughts.on.java.jpa21.converter.ColorConverter"
      attribute-name="color"/>
  </entity>
</entity-mappings>

Verwendung von Entitäten mit Attribute Converters

Die Verwendung einer Entität, die einen Attribute Converter einsetzt, unterscheidet sich nicht von der Verwendung einer Entität ohne Attribute Converter. Um eine Instanz der RectangleEntity (Listing 1) zu speichern, wird eine neue Ausprägung der Entität instanziiert, und die einzelnen Eigenschaften werden gesetzt (Listing 5). Schreibt man die so erstellte RectangleEntity in die Datenbank, wird die Eigenschaft vom Typ java.awt.Color vorher durch den beschriebenen ColorConverter in einen String umgewandelt. Im Loglevel TRACE schreibt Hibernate dabei zwei Lognachrichten, mit denen die erfolgte Konvertierung nachvollzogen werden kann (Listing 6). In den beiden hervorgehobenen Zeilen ist zuerst das übergebene Objekt vom Typ java.awt.Color und anschließend der durch den Attribute Converter erzeugte String mit den durch Semikolon getrennten Werten für rot, grün, blau und alpha zu sehen.

Listing 5

RectangleEntity rectangle = new RectangleEntity();
rectangle.setX(100);
rectangle.setY(50);
rectangle.setColor(new Color(50, 100, 150, 200));
this.em.persist(rectangle);

Listing 6

2014-09-13 22:32:14,865 DEBUG [org.hibernate.SQL] (default task-1) insert into Rectangle (id, color, x, y) values (null, ?, ?, ?)
...
2014-09-13 22:32:14,871 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [VARCHAR] - [java.awt.Color[r=50,g=100,b=150]]
2014-09-13 22:32:14,871 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [VARCHAR] - [50;100;150;200]
2014-09-13 22:32:14,871 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [2] as [INTEGER] - [100]
2014-09-13 22:32:14,871 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [3] as [INTEGER] - [50]

Wie bereits erläutert, werden die Converter auch beim Ausführen von Abfragen verwendet, um das übergebene Objekt in die entsprechende Datenbankrepräsentation zu überführen. Der Entwickler benötigt bei der Erstellung von Abfragen somit kein Wissen über den verwendeten Converter. Um z. B. nach einem Rechteck mit einer bestimmten Farbe zu suchen, wird ein entsprechend initialisiertes Color-Objekt als Parameter an die JPQL-Abfrage übergeben (Listing 7). Der JPA-Provider verwendet dann den ColorConverter, um das Color-Objekt in einen String zu konvertieren und die Abfrage durchzuführen. Auch hier enthält die Hibernate-Logdatei wieder entsprechende Hinweise auf die Verwendung des Converters (s. Hervorhebung in Listing 8).

Listing 7

Color color = new Color(50, 100, 150, 200);
RectangleEntity rectangle = this.em
.createQuery("SELECT r FROM Rectangle r WHERE color=:color",
RectangleEntity.class).setParameter("color", color).getSingleResult();

Listing 8

2014-09-13 22:32:15,067 DEBUG [org.hibernate.hql.internal.ast.QueryTranslatorImpl] (default task-7) parse() - HQL: SELECT r FROM blog.thoughts.on.java.jpa21.entity.RectangleEntity r WHERE color=:color
...
2014-09-13 22:32:15,108 TRACE [org.hibernate.hql.internal.ast.tree.FromElement] (default task-7) Handling property dereference [blog.thoughts.on.java.jpa21.entity.RectangleEntity (r) -> color (class)]
2014-09-13 22:32:15,108 DEBUG [org.hibernate.hql.internal.ast.tree.DotNode] (default task-7) getDataType() : color -> org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter@68ea5770

Fazit

Wie zu Beginn des Artikels beschrieben, gab es vor JPA 2.1 keine einfache, standardisierte Möglichkeit zur Konvertierung von Eigenschaften. Eine verbreitete Lösung stellte die Verwendung von EntityListenern dar. Diese mussten jedoch spezifisch für jede Entität entwickelt und vom Entwickler bei der Erstellung von Abfragen berücksichtigt werden.

Die mit JPA 2.1 eingeführten Attribute Converter bieten eine deutlich bessere Möglichkeit, einzelne Eigenschaften einer Entität für die Speicherung in der Datenbank zu konvertieren. Die besonderen Stärken liegen dabei vor allem in der einfachen Implementierung, der Wiederverwendbarkeit für alle Eigenschaften eines Typs und der transparenten Verwendung beim Anlegen, Bearbeiten, Löschen, Lesen und Suchen einer Entität. Dies vereinfacht die Verwendung von Attribute Converters und vermeidet unnötige Fehlerquellen. Dies wird auch durch die Möglichkeit unterstützt, einen Converter sowohl global zu aktivieren als auch per Annotation oder XML für einzelne Eigenschaften zu konfigurieren. Vor allem die globale Aktivierung des AttributeConverters erleichtert die Verwendung und hilft, Fehler zu vermeiden. Dies stellt eine deutliche Verbesserung zu den früher verwendeten EntityListenern dar. Der vollständige Quellcode des Beispiels steht im GitHub Repository zur Verfügung.

Verwandte Themen:

Geschrieben von
Thorben Janssen

Thorben Janssen arbeitet als Senior-Entwickler und Architekt für die Qualitype GmbH und entwickelt seit mehr als zehn Jahren Anwendungen auf Basis von Java EE. Er ist Mitglied der JSR 365 Expert Group und bloggt über Themen rund um Java Enterprise auf http://www.thoughts-on-java.org.

Twitter: @thjanssen123

Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Konvertierungen mithilfe von JPA Attribute Converters"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
Torsten
Gast

„Implizit wird der Converter bereits durch die Eigenschaft autoApply der javax.persistence.Converter-Annotation für alle Eigenschaften des entsprechenden Typs aktiviert.“

durch das kleine Wörtchen ‚bereits‘ liest es sich etwas mißverständlich, ist doch der default für autoApply false 😉