Stand: 2013-08-28
Play 2 mit Scala und JPA/Hibernate
Von Haus aus ist die Scala-Variante von Play 2 nicht für die Kombination mit JPA (bzw. Hibernate) vorgesehen. Wie Play, Scala und JPA zusammenspielen zeigt dieser Artikel.
Inhalt
Transaktionen im Controller
Statt mit undurchsichtigen ThreadLocal-Objekten oder aspektorientierter Programmierung (AOP) zu arbeiten, reicht in Scala ein Lambda-Ausdruck, der innerhalb einer Transaktion ausgeführt wird. Ein weiterer Vorteil davon ist, dass man sehr genau steuern kann, wo eine Transaktion beginnen und enden soll. In einem Controller sieht eine Transaktionsklammer mit meinem Ansatz so aus:
def post(id: Long) = Action {
Transaction { implicit entityManager =>
val item = ItemRepository.findById(id)
item.foreach(_.incrementClicks())
Ok()
}
}
Der entityManager
wird als implizites Objekt in den Lambdaausdruck gereicht, damit man ihn nicht überall in den Parameterlisten wiederholen muss. Trotzdem ist dieser implizite Parameter expliziter als ein ThreadLocal-Objekt oder AOP.
Die zugehörige Repository-Methode in dem Beispiel hat dementsprechend zwei Parameterlisten, wovon eine implizit ist und den entityManager entgegen nimmt:
def findById(id: Long)(implicit entityManager: EntityManager): Option[Item] =
Option(entityManager.find(classOf[Item], id))
Dieses Verfahren ist für alle Methoden erforderlich, die Zugriff auf den EntityManager benötigen.
Transaktionen bereitstellen
Folgender Code realisiert das Objekt, das die Transkation bereitstellt und den übergebenen Lambdaausdruck ausführt:
package util
import javax.persistence.{EntityManager, Persistence}
object Transaction {
val emf = Persistence.createEntityManagerFactory("defaultPersistenceUnit")
def apply[A](action: EntityManager => A): A = {
val entityManager = emf.createEntityManager()
def transaction = entityManager.getTransaction
transaction.begin()
try {
val result = action(entityManager)
if (transaction.isActive) // Transaktion kann bereits explizit zurückgerollt sein
transaction.commit()
result
}
catch {
case exception: Throwable =>
if (transaction != null &&& transaction.isActive)
transaction.rollback()
throw exception
}
finally
entityManager.close()
}
def rollback()(implicit entitymanager: EntityManager) {
entitymanager.getTransaction().rollback()
}
}
Entitätsklassen mit Scala und JPA
Bisher ging alles ziemlich glatt, aber an ein paar Stellen merkt man doch, dass JPA aus der Java-Welt kommt. Wenn Scalas Klassenparameter mit dem Schlüsselwort var
auch gleich ein Feld anlegen, ist dort die BeanProperty
-Annotation erforderlich, weil der Scala-Compiler Felder sonst anders anlegt, als Hibernate es erwartet.
@Entity
class Item(
@BeanProperty
@ManyToMany(cascade=Array(CascadeType.PERSIST), fetch = FetchType.EAGER)
var tags: Set[Tag]
)
Das Beispiel zeigt mit der Verwendung von java.util.HashSet
noch eine weitere Besonderheit, die man bei der Kombination von Scala und JPA beachten muss: JPA bzw. Hibernate sind nur für Java Collections ausgelegt und wissen deshalb nicht, was sie mit Scala Collections anfangen sollen. Entweder muss man sich also Adapter schreiben, die Hibernate den Umgang mit Scala Collections beibringen, oder man verwendet eben die Java Collections.
pesistence.xml
JPA wird über die Datei conf/META-INF/persistence.xml konfiguriert, die etwa so aussehen kann:
<persistence 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_2_0.xsd"
version="2.0">
<persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<!-- Mit der folgenden Zeile kann automatisch ein DB-Schema erzeugt werden. -->
<!-- <property name="hibernate.hbm2ddl.auto" value="create-drop"/> -->
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/example"/>
<property name="javax.persistence.jdbc.user" value="userx"/>
<property name="javax.persistence.jdbc.password" value="topsecret"/>
<property name = "hibernate.show_sql" value="false" />
</properties>
</persistence-unit>
</persistence>
Benötigte Bibliotheken
Zu guter Letzt fehlen noch die Bibliotheken, die die Play-Anwendung benötigt, um JPA verwenden zu können. Trage dazu in die Datei Build.scala folgende Bibliotheken ein:
val appDependencies = Seq(
"postgresql" % "postgresql" % "9.1-901.jdbc4",
"org.hibernate" % "hibernate-core" % "4.1.9.Final",
"org.hibernate" % "hibernate-entitymanager" % "4.1.9.Final")
Wobei der JDBC-Treiber für PostgreSQL nur ein Beispiel ist; für andere Datenbanken musst du natürlich den jeweiligen Treiber angeben.
Wesentliche Änderungen
- 2013-07-28 Ergänzung von
BeanProperty