Recently i was thinking about persisting Scala classes with JPA. I tried to set up a mixed Java/Scala project as described here , using Java code to do the JPA stuff and using Scala to create the entities. For a more complex example, i decided to persist a master/detail association to test if the Scala collections can be persisted, too. For the test i am using Scala 2.10.1 and the Hibernate 4.1.10.Final EntityManager as the JPA 2.0 vendor.
Here is the Java code to create and persist two instances of type Parent, one with no childs at all and one with one child attached:
package de.mirkosertic.java;
import de.mirkosertic.scala.Child;
import de.mirkosertic.scala.Parent;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaQuery;
public class JPA20 {
public static void main(String[] args) {
EntityManagerFactory theEMF = Persistence.createEntityManagerFactory("ScalaMixedTutorial");
EntityManager theEntityManager = theEMF.createEntityManager();
// Enter some testdata
theEntityManager.getTransaction().begin();
Parent p = new Parent("name1","name2");
theEntityManager.persist(p);
theEntityManager.getTransaction().commit();
theEntityManager.getTransaction().begin();
p = new Parent("name2","name2");
Child c = new Child("child1");
p.addChild(c);
theEntityManager.persist(p);
theEntityManager.getTransaction().commit();
// Query it
theEntityManager.getTransaction().begin();
CriteriaQuery<Parent> theQuery = theEntityManager.getCriteriaBuilder().createQuery(Parent.class);
theQuery.distinct(true).from(Parent.class);
for (Parent theParent : theEntityManager.createQuery(theQuery).getResultList()) {
System.out.println(theParent);
}
}
}
And here is the console output:
Hibernate: create table CHILD (id integer generated by default as identity (start with 1), name varchar(255), PARENT_ID integer, primary key (id)) Hibernate: create table PARENT (id integer generated by default as identity (start with 1), name1 varchar(255), name2 varchar(255), primary key (id)) Hibernate: alter table CHILD add constraint FK3D1FCFC63FC2D55 foreign key (PARENT_ID) references PARENT Mrz 19, 2013nto PARENT (id, name1, name2) values (default, ?, ?) Hibernate: insert into PARENT (id, name1, name2) values (default, ?, ?) Hibernate: insert into CHILD (id, name) values (default, ?) Hibernate: update CHILD set PARENT_ID=? where id=? Hibernate: select distinct parent0_.id as id1_, parent0_.name1 as name2_1_, parent0_.name2 as name3_1_ from PARENT parent0_ 1 name1 name2 [] 2 name2 name2 [child1]
The two created entites are persisted and correctly retrieved. Fine!
The tricky part starts with defining the entites. Let’s start with the Parent.scala class:
package de.mirkosertic.scala
import beans.BeanProperty
import javax.persistence._
import java.util
@Entity
@Table(name = "PARENT")
class Parent(@BeanProperty var name1: String, @BeanProperty var name2: String) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int = _
@OneToMany(cascade = Array(CascadeType.ALL))
@JoinColumn(name = "PARENT_ID")
@BeanProperty
// Use Java collection types instead of Scala ones to make JPA happy
var childs: java.util.Set[Child] = new util.HashSet[Child]()
// Default constructor for hibernate
// No public visibility required
private def this() = this(null, null)
/**
* Method to add a child to the parent.
*
* @param aChild the child to add
*/
def addChild(aChild: Child) {
childs.add(aChild)
}
/**
* Overridden toString() implementation.
*
* @return id + name1 + name2 + childs as a String.
*/
override def toString() = id + " " + name1 + " " + name2 + " " + childs;
}
This is a very good example. I am creating two constructors, one default constructor with private visibility to enable JPA/Hibernate proxy support, and one constructor which takes name1 and name2 as arguments. Finally i add a toString() implementation to the entity class.
Please note that i am using field level annotations, so field access is used instead of property access. I don’t want to code redundant getter and setters, so i am using the Scala @BeanProperty annotation, this will tell the Scala compiler to create the getter and setter for me. This removes a lot of boilerplate code, which would otherwise just be there to make JPA happy and force property access. So data access is encapsulated within getter and setter, persistence is declared using annotations at field level, and construction is defined by a set of public constructors.
One ugly thing remains: we have to use the Java collection API instead of Scala collections. This is a requirement by the JPA spec, as the field type must be a Java collection interface.Let’s take a look at the Child.scala class:
package de.mirkosertic.scala
import beans.BeanProperty
import javax.persistence._
@Entity
@Table(name = "CHILD")
class Child(@BeanProperty var name: String) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int = _
// Default constructor for hibernate
// No public visibility required
private def this() = this(null)
override def toString() = name
}
The Child entity is straight forward. No comments here.
Finally let’s take a look at the META-INF/persistence.xml file(i am using an embedded in-memory HSQLDB):
<?xml version="1.0" encoding="UTF-8"?>
<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="ScalaMixedTutorial">
<class>de.mirkosertic.scala.Parent</class>
<class>de.mirkosertic.scala.Child</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:mem:MixedScala"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
and also the Maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>MavenScalaMixed</groupId>
<artifactId>MavenScalaMixed</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<phase>compile</phase>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>testCompile</goal>
</goals>
<phase>test-compile</phase>
</execution>
<execution>
<phase>process-resources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.10.Final</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.9</version>
</dependency>
</dependencies>
</project>
So it is possible to persist Scala classes with JPA. We just have to watch out for the collection types! Cool!
Git revision: 63a36b0