An essential component found in almost any web application involves the storage and retrieval of data in a persistent store. Whether relational or NoSQL based, a database often occupies the most important place since it holds the application data. When a technology stack becomes a legacy and needs to be refactored or ported to a new one, the database is usually the starting point since it holds the domain knowledge.
In this chapter, we are first going to study how to integrate and reuse persistence frameworks inherited from Java that deal with Object Relational Mapping (ORM) such as those supporting Java Persistence API (JPA), for example, Hibernate and EclipseLink. We will then experiment with the default persistence framework available in the Play Framework, Anorm. Finally, we will introduce and discover a Scala alternative to ORM and a rather novel approach that adds type safety and composition to the more traditional SQL-based queries, the Slick framework. We will experiment with Slick in the context of Play web development. We will also cover the generation of CRUD-like applications out of existing relational databases that can be a boost in productivity when starting out from a legacy database.
As defined by Wikipedia:
"Object-relational mapping (ORM, O/RM, and O/R mapping) in computer software is a programming technique for converting data between incompatible type systems in object-oriented programming languages".
The popular adoption of ORM frameworks in Java such as Hibernate is largely due to the simplicity and diminution of code you need to write to persist and query data.
Although Scala has its own modern standard for data persistence (that is, Slick, which we will introduce later on), in this section, we will cover a possible integration of JPA (Java Persistence API, documented at http://docs.oracle.com/javaee/6/tutorial/doc/bnbpz.html) within the Scala world by building an SBT project that uses JPA-annotated Scala classes to persist data in a relational database. It is derived from an online sample available at http://www.brainoverload.nl/scala/105/jpa-with-scala, which should be particularly interesting to Java developers since it illustrates how to use the Spring framework both for dependency injection and configuration of beans in the context of a Scala project at the same time. As a reminder, the Spring framework, created by Rod Johnson, came out in 2002 as a way to provide inversion of control, that is, dependency injection increased in popularity to become a full-featured framework now containing many aspects of Java EE 7. More information about Spring is available at http://projects.spring.io/spring-framework/.
We are going to connect to the already existing CustomerDB sample database that we have introduced in Chapter 2, Code Integration, to show both how to read existing data and create new entities/tables to persist data.
As we have seen in Chapter 3, Understanding the Scala Ecosystem, creating a blank Scala SBT project is a matter of opening a command terminal, creating a directory to put the project in, and running SBT as follows:
> mkdir sbtjpasample > cd sbtjpasample > sbt > set name:="sbtjpasample" > session save
We can navigate to the project/
folder that SBT created, and add a plugins.sbt
file containing the following one-liner to import the sbteclipse
plugin so that we can work with the project under the Eclipse IDE:
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
Since we are going to use Hibernate- and Spring-related classes, we need to include such dependencies into our build.sbt
build file (as well as the derby-client driver to connect to the CustomerDB sample
database) so that it looks like the following code snippet:
name:="sbtjpasample" scalaVersion:="2.10.3" libraryDependencies ++= Seq( "junit" % "junit" % "4.11", "org.hibernate" % "hibernate-core" % "3.5.6-Final", "org.hibernate" % "hibernate-entitymanager" % "3.5.6-Final", "org.springframework" % "spring-core" % "4.0.0.RELEASE", "org.springframework" % "spring-context" % "4.0.0.RELEASE", "org.springframework" % "spring-beans" % "4.0.0.RELEASE", "org.springframework" % "spring-tx" % "4.0.0.RELEASE", "org.springframework" % "spring-jdbc" % "4.0.0.RELEASE", "org.springframework" % "spring-orm" % "4.0.0.RELEASE", "org.slf4j" % "slf4j-simple" % "1.6.4", "org.apache.derby" % "derbyclient" % "10.8.1.2", "org.scalatest" % "scalatest_2.10" % "2.0.M7" )
As a reminder to make these dependencies available in Eclipse, we have to run the > sbt eclipse
command again and refresh our project in the IDE.
Now, from the root directory of the project, enter > sbt eclipse
and import the project into the IDE.
Now let's add a couple of domain entities (under a new package se.sfjd
) that we want to annotate with Java-based JPA annotations. The Customer
entity defined in a Customer.scala
file in the se.sfjd
package will map (at least partially) to the existing CUSTOMER
database table:
import javax.persistence._ import scala.reflect.BeanProperty @Entity @Table(name = "customer") class Customer(n: String) { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "CUSTOMER_ID") @BeanProperty var id: Int = _ @BeanProperty @Column(name = "NAME") var name: String = n def this() = this (null) override def toString = id + " = " + name }
Notice the underscore (_) representing a default value when declaring var id: Int = _
. The default value will be set according to the type T
of a variable, as defined by the Scala specification:
0
if T
is Int
or one of its subrange types0L
if T
is Long
0.0f
if T
is Float
0.0d
if T
is Double
false
if T
is Boolean
()
if T
is Unit
null
for all other types of T
The Language
entity corresponds to the addition of a new concept we want to persist and therefore requires a new database table, as follows:
@Entity @Table(name = "language") class Language(l: String) { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") @BeanProperty var id: Int = _ @BeanProperty @Column(name = "NAME") var name: String = l def this() = this (null) override def toString = id + " = " + name }
As we saw in Chapter 2, Code Integration, the @BeanProperty
annotation is a way to generate getters and setters conforming to Java, and the this()
method is a no argument constructor needed by Hibernate.
Moving on, the controller class or DAO (Data Access Object) class captures the behavior we want to provide for the Customer
entity such as CRUD functionality in the form of save
and find
methods following an interface, or in this case, a Scala trait:
trait CustomerDao { def save(customer: Customer): Unit def find(id: Int): Option[Customer] def getAll: List[Customer] }
The implementation of the CustomerDao
class relies on the methods of the JPA entity manager that we as Java developers are probably familiar with:
import org.springframework.beans.factory.annotation._ import org.springframework.stereotype._ import org.springframework.transaction.annotation.{Propagation, Transactional} import javax.persistence._ import scala.collection.JavaConversions._ @Repository("customerDao") @Transactional(readOnly = false, propagation = Propagation.REQUIRED) class CustomerDaoImpl extends CustomerDao { @Autowired var entityManager: EntityManager = _ def save(customer: Customer):Unit = customer.id match{ case 0 => entityManager.persist(customer) case _ => entityManager.merge(customer) } def find(id: Int): Option[Customer] = { Option(entityManager.find(classOf[Customer], id)) } def getAll: List[Customer] = { entityManager.createQuery("FROM Customer", classOf[Customer]).getResultList.toList } }
In a similar manner, we can define a Language
trait and its implementation as follows, with the addition of a
getByName
method:
trait LanguageDao { def save(language: Language): Unit def find(id: Int): Option[Language] def getAll: List[Language] def getByName(name : String): List[Language] } @Repository("languageDao") @Transactional(readOnly = false, propagation = Propagation.REQUIRED) class LanguageDaoImpl extends LanguageDao { @Autowired var entityManager: EntityManager = _ def save(language: Language): Unit = language.id match { case 0 => entityManager.persist(language) case _ => entityManager.merge(language) } def find(id: Int): Option[Language] = { Option(entityManager.find(classOf[Language], id)) } def getAll: List[Language] = { entityManager.createQuery("FROM Language", classOf[Language]).getResultList.toList } def getByName(name : String): List[Language] = { entityManager.createQuery("FROM Language WHERE name = :name", classOf[Language]).setParameter("name", name).getResultList.toList } }
Before we can execute the project, we still have a couple of steps to follow: first we need a test class, we can therefore create a CustomerTest
class following the ScalaTest
syntax, as we have seen earlier in Chapter 4, Testing Tools:
import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner import org.scalatest.FunSuite import org.springframework.context.support. lassPathXmlApplicationContext @RunWith(classOf[JUnitRunner]) class CustomerTest extends FunSuite { val ctx = new ClassPathXmlApplicationContext("application-context.xml") test("There are 13 Customers in the derby DB") { val customerDao = ctx.getBean(classOf[CustomerDao]) val customers = customerDao.getAll assert(customers.size === 13) println(customerDao .find(3) .getOrElse("No customer found with id 3")) } test("Persisting 3 new languages") { val languageDao = ctx.getBean(classOf[LanguageDao]) languageDao.save(new Language("English")) languageDao.save(new Language("French")) languageDao.save(new Language("Swedish")) val languages = languageDao.getAll assert(languages.size === 3) assert(languageDao.getByName("French").size ===1) } }
Last but not least, we have to define some configuration, both a META-INF/persistence.xml
file required by JPA that we can put under src/main/resources/
and a Spring application-context.xml
where all beans are wired and the database connection is defined. The persistence.xml
file will look as simple as follows:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" 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"> <persistence-unit name="JpaScala" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> </persistence-unit> </persistence>
The application-context.xml
file, directly available under src/main/resources/
, is a bit more elaborate and is given as follows:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <tx:annotation-driven transaction-manager="transactionManager"/> <context:component-scan base-package="se.sfjd"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" p:driverClassName="org.apache.derby.jdbc.ClientDriver" p:url="jdbc:derby://localhost:1527/sample" p:username="app" p:password="app"/> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitName" value="JpaScala"/> <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/> <property name="jpaDialect"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> </property> <property name="dataSource" ref="dataSource"/> <property name="jpaPropertyMap"> <map> <entry key="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/> <entry key="hibernate.connection.charSet" value="UTF-8"/> <entry key="hibernate.hbm2ddl.auto" value="update"/> <entry key="hibernate.show.sql" value="true"/> </map> </property> </bean> <bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> <property name="dataSource" ref="dataSource"/> </bean> </beans>
Before running the test, we need to make sure the database server is up and running; this was explained in Chapter 2, Code Integration, while using the NetBeans IDE.
Now we can execute the example either by right-clicking on the CustomerTest
class and navigating to Debug As | Scala JUnit Test or from the command prompt by entering the following command:
> sbt test 3 = Nano Apple [info] CustomerTest: [info] - There are 13 Customers in the derby DB [info] - Persisting 3 new languages [info] Passed: : Total 2, Failed 0, Errors 0, Passed 2, Skipped 0 [success] Total time: 3 s