Chapter 6. Database Access and the Future of ORM

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.

Integrating an existing ORM – Hibernate and JPA

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.

Making JPA available in Scala

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 types
  • 0L 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.139.169