Relational database systems still dominate persistence systems in web applications today. For the vast majority of developers, having a flexible and intuitive interface to that relational database is key to their development.
Record is all about providing a persistence interface that allows you to plug in the technologies you want to work with, and the wider Scala community has several very sophisticated abstractions on top of the popular Java JDBC interfaces. One of these projects that already has a Record integration is called Squeryl (http://squeryl.org/).
Squeryl was designed to be a type-safe abstraction that allows you to compose state- ments that are checked at compile time. This allows you to be 100 percent confident that the SQL statements you write to access your data won’t fail because of a runtime syntax error. Squeryl provides a declarative DSL for query construction that’s intuitive and that allows for a high degree of code reuse. As it stands, the Record abstraction doesn’t interrupt the Squeryl DSL or any of the querying tools; it simply wraps the entity definition and provides the rich contextual fields where you need them for building your web applications.
In order to get started with the Squeryl-Record module, simply add the following dependency to your project configuration:
val squeryl = "net.liftweb" %% "lift-squeryl-record" % liftVersion
Once the dependency is added, don’t forget to call reload and then update in the SBT console, so that the appropriate JARs are downloaded. The Squeryl classes will also be transitively resolved and downloaded in addition to the Lift module.
11.2.1 Connecting and querying with Squeryl
Squeryl uses JDBC to talk to the database, and at a base level it simply requires an active java.sql.Connection to function. Conveniently, Lift has a set of abstractions
267 Record for relational databases
on these common database interactions, and Squeryl can make use of them without any glue code.
In order to connect to the database, you must specify connection settings in exactly the same way you would for Mapper. As this was discussed at some length in chapter 11 (section 11.1.1), the following only includes a brief recap and assumes that you have a connection string set up. Specifically, you’ll need some code that looks similar to this in your Boot class to obtain a connection:
if (!DB.jndiJdbcConnAvailable_?){
DB.defineConnectionManager(DefaultConnectionIdentifier, Database) LiftRules.unloadHooks.append(() =>Database.closeAllConnections_!()) }
NOTE If you want transactional semantics over the whole request cycle with Squeryl, you need to do the same thing as with Mapper: add the S.add- Around(DB.buildLoanWrapper) call into your application Boot class.
With the connection in place, there is one final thing your Boot class must feature in order to tell Squeryl what type of database you’d like to talk to:
import net.liftweb.squerylrecord.SquerylRecord import org.squeryl.adapters.PostgreSqlAdapter SquerylRecord.init(() => new PostgreSqlAdapter)
Simply pass the SquerylRecord.init method a function that yields the correct data- base adapter, and you should be good to go. Squeryl provides its own dialect drivers to convert its own statement syntax into the appropriate SQL. Table 11.2 lists the other possible driver options.
One of the other significant differences between Squeryl and Mapper is that Squeryl advocates manual schema upgrades and alterations; its makers believe conducting automated migrations is too risky. With this in mind, however, Squeryl does provide an initial schema creation tool, and the ability to output the current data definition language (DDL) defined by the Squeryl entities. It’s not really feasible to have Squeryl attempt to create the database every time your application loads, because it would
Table 11.2 Alternative Squeryl database dialect adapters
Database Driver class
IBM DB2 SquerylRecord.init(() => new DB2Adapter)
H2 SquerylRecord.init(() => new H2Adapter)
Microsoft SQL Server SquerylRecord.init(() => new MSSQLServer)
MySQL SquerylRecord.init(() => new MySQLAdapter)
Oracle SquerylRecord.init(() => new OracleAdapter)
throw exceptions about the tables that already exist, but if you want to use Squeryl to create your schema on a one-time basis, you can make this call:
import net.liftweb.mapper.{DB, DefaultConnectionIdentifier}
DB.use(DefaultConnectionIdentifier){ connection => Bookstore.create }
Because it’s not a good idea to have this happening upon every application boot-up, it can be nice to log the DDL that Squeryl will attempt to use so you can either make the necessary alterations or disregard it as appropriate. In order to do that, simply add the following to your Boot class:
if(Props.devMode)
DB.use(DefaultConnectionIdentifier){ connection => Bookstore printDdl }
In both these commands, the DB.use block is required because it ensures that the Squeryl operation is conducted in the scope of the database connection.
Now that you have Squeryl-Record configured and ready, let’s reconstruct the bookstore example from chapter 10, including the relationships and queries, to use Squeryl. This will demonstrate some of the powerful features Squeryl and Record bring to the party.
11.2.2 A bookstore with Squeryl
The model for the bookstore will remain exactly the same as in chapter 10, with the entity relationship being as it was defined in figure 10.1. This requires you to imple- ment three Squeryl entities: Publisher, Book, and Author.
Unlike Mapper, Squeryl defines a central Schema subtype that defines the table-to- class mapping. The following listing defines an example.
import org.squeryl.Schema
object Bookstore extends Schema {
val authors = table[Author]("authors") val books = table[Book]("books")
val publishers = table[Publisher]("publishers") }
This Schema subtype calls the table method and has a type parameter of the imple- menting class. It’s this very method that connects the class to the table definition. The first parameter of the table method allows you to define a specific name for that table, which is useful if you’re working with a legacy schema or are very particular about how your tables are named.
This won’t compile yet because the specified entities don’t yet exist. Let’s start by creating the Publisher entity and consider how that differs from the Mapper version defined in listing 10.1. The next listing shows the Squeryl-Record implementation of the basic Publisher entity.
Listing 11.2 Defining a Squeryl schema
269 Record for relational databases
import net.liftweb.record.{MetaRecord, Record}
import net.liftweb.record.field.{LongField, LongTypedField, StringField}
import net.liftweb.squerylrecord.KeyedRecord import net.liftweb.squerylrecord.RecordTypeMode._
import org.squeryl.Query
import org.squeryl.annotations.Column class Publisher private ()
extends Record[Publisher]
with KeyedRecord[Long]{
def meta = Publisher
@Column(name="id") val idField = new LongField(this, 1) val name = new StringField(this, "") }
object Publisher extends Publisher with MetaRecord[Publisher]
In this Publisher entity, you can first see that the constructor is made private and the class extends both Record and a special Squeryl-Record type called KeyedRecord. The class constructor is marked private so that new Records can’t be constructed by way of the new Publisher method, which could interfere with the way in which Record introspects the field values. It’s generally a good idea to mark the constructor private, which will then give you compile-time failures if you try to use the new key- word; Record instances should always be created using the Publisher.createRecord method to avoid these issues. In addition to the class definition, note that, like the Mapper implementation, Squeryl-Record has both an instance and a companion, or meta object.
Next, you can see two examples of implementing fields with Record B. Squeryl- Record has a slight nuance in that KeyedRecord already implements an id method, so you need to implement idField and provide the @Column annotation from the Sque- ryl distribution so that the idField correctly uses the ID column on the appropriate database table.
In order to complete the picture, let’s define the remaining two entities and take a look at their relationships. The following listing shows the Author and Book entities.
import net.liftweb.record.{MetaRecord, Record}
import net.liftweb.squerylrecord.KeyedRecord import net.liftweb.squerylrecord.RecordTypeMode._
import net.liftweb.record.field._
import org.squeryl.annotations.Column class Book private ()
extends Record[Book]
with KeyedRecord[Long] { def meta = Book
Listing 11.3 Squeryl-Record implementation of the Publisher entity
Listing 11.4 Squeryl-Record implementation for the Author and Book entities Fields for
record
B
@Column(name="id")
val idField = new LongField(this, 100) val publisherId = new LongField(this, 0) val authorId = new LongField(this, 0) val title = new StringField(this, "") val publishedInYear = new IntField(this, 1990) }
object Book extends Book with MetaRecord[Book]
class Author private () extends Record[Author]
with KeyedRecord[Long] { def meta = Author
@Column(name="id")
val idField = new LongField(this, 100) val name = new StringField(this, "") val age = new OptionalIntField(this) val birthday = new OptionalDateTimeField(this) }
object Author extends Author with MetaRecord[Author]
These two entities follow a very similar pattern to the Publisher entity by implement- ing the Record and KeyedRecord traits. Moreover, both records include some addi- tional fields to contain the appropriate data attributes to model that particular entity
B and C.
SQUERYL CRUD STATEMENTS
With the entities defined, you can now start to play with the create, read, update, and delete syntax that Squeryl supplies. Remember that Record is a thin abstraction, so the statement syntax is used wholesale from the Squeryl library. The following sections explore some of this functionality, but Squeryl is an ORM in its own right and there is much functionality that can’t be covered here. I strongly recommend you check out the Squeryl documentation (http://squeryl.org/introduction.html) for more specific information on what is possible.
The Publisher entity is quite straightforward, so let’s start by inserting some data into it using the SBT console:
scala> import sample.model.squeryl._
import sample.model.squeryl._
scala> import net.liftweb.mapper.{DB,DefaultConnectionIdentifier}
import net.liftweb.mapper.{DB, DefaultConnectionIdentifier}
scala> import Bookstore._
import Bookstore._
scala> new bootstrap.liftweb.Boot().boot
scala>DB.use(DefaultConnectionIdentifier){ connection =>
| publishers.insert(
| Publisher.createRecord.name("Manning")) | }
res10: sample.model.squeryl.Publisher = sample.model.squeryl.Publisher@9 Book record
B
Author fields
C
Insert to DB
B
271 Record for relational databases
First, this code imports the types that are needed to interact with the Squeryl entities and, in addition, the Mapper database connection abstractions so the statements can connect to the backend database. But the real line of interest here is B. publishers refers to the value defined on the Bookstore Schema, so this line literally says “insert this passed object into the publishers table.” The parameter in this case is an instance of Publisher that has been created with the Publisher.createRecord method to obtain a fresh (unsaved) instance to which a name has been applied.
With Squeryl, it’s also possible to do batch insertions right from within your code, as shown:
publishers.insert(List(
Publisher.createRecord.name("Manning"), Publisher.createRecord.name("Penguin"), Publisher.createRecord.name("Bloomsbury") ))
As it stands, this operation will throw an exception if the save operation fails, so remember to provide exception handling where appropriate.
At this point, we strongly recommend taking some time to play around with Sque- ryl by inserting various bits of data into the database from within the REPL. This will give you a good feel for how to construct records and for the interplay between Record and Squeryl types. In addition, you’ll need to have some data in the tables to test out the querying syntax!
SQUERYL QUERYING STATEMENTS
Now that you have the entities set up and some data in your tables, let’s take a quick tour around the querying syntax that Squeryl provides. The query DSL in Squeryl models itself on SQL. Unlike many ORM systems that attempt to ignore the fact that they’re interacting with a relational database, Squeryl embraces this real- ity wholeheartedly.
The next listing shows several examples of query operations with the Squeryl- Record entities defined in the previous listings.
import org.squeryl.RecordTypeMode._
from(Bookstore.publishers)(p =>
where(p.id === 1) select(p)).single import Bookstore._
publishers.where(_.id === 1).single publishers.toList
authors.where(_.age.is >= 20) from(publishers, books)((p,b) =>
where(b.title.is like "%in Action"
and p.name.is === "Manning") select(b,p)) Listing 11.5 Various Squeryl query expressions
Explicit query
B
Import schema and query
C
Operator examples
D
Type-safe joins
E
This listing shows you a small selection of Squeryl syntax. The first thing you must do in order to use the Squeryl query DSL is import the implicit conversions supplied by the library. The common import is shown at the top of the listing; without this import, you’ll receive a whole set of errors from the compiler telling you that methods don’t exist and so forth.
Next in the listing are several different examples of explicit queries using the Squeryl DSL. The first statement would be equivalent to this SQL:
SELECT * FROM publishers WHERE id = 1
The from() method takes tables that are defined in the Schema subtype; Bookstore in this instance from listing 11.2. This statement is a bit on the verbose side B, though, and it can thankfully be condensed somewhat.
Scala allows you to import object members into a particular scope, and as you’re likely to execute these queries somewhere where you’d like access to that information, you can call importBookstore._ to save constantly having to prefix Bookstore to the table call. Here you can see the same query again, but without the from() component;
it operates directly on the schema table. Also note the second example that has the where() clause removed C; this is, as you might expect, equivalent to running a SELECT*FROMtable and converting it into a List[Publisher]. You should be able to see how the predicate you pass to where() is translated into the correct SQL under the hood by Squeryl
Another type of predicate is defined for greater than or equal to D. There are a whole set of optional predicates and combinations, so check the Squeryl documenta- tion for more specific information (http://squeryl.org/selects.html).
Finally, listing 11.5 defines an ad hoc multi-table query E, much like you might do with SQL joins, but in this case you can see that the like method has been used to define a wildcard that looks for book titles ending with “in Action” and where the pub- lisher name is “Manning.”
As you can see, this is a very different approach to interacting with SQL data stor- age than is used by Mapper or other popular Java ORM systems, such as Hibernate.
The real advantage of using Squeryl over something like Hibernate is that you can be sure that the query will execute if the code compiles: it has complete type-safety. The prospect of having a query engine that won’t arbitrarily blow up on you at runtime is an attractive one, and with Record’s rich field system you can also gain many of the conveniences you’re used to from Mapper.
The next section moves away from traditional SQL data stores and takes a look at the emerging world of NoSQL and semi-structured data. These new systems are typi- cally designed to be specialized storage systems for specific types of problems, but some of the products in the marketplace, such as MongoDB, are proving to be popu- lar for solving more general problems because they fit more idiomatically with the way developers think of objects and data structures.
273 Record for NoSQL stores