Overview:
This article looks at Lazyrecords, a Google Code framework for accessing data repositories using functional programming. Lazyrecords yields faster performance via functional programming style.
I will try to keep the content simple and straight by presenting the basic CRUD operations using a simple Unit tests that involves storing, retrieving, updating and deleting Book entities.
You can learn more about the framework here.
The sample code for this article can be found here.
Project Structure:
org.fazlan.lazyrecords
├── pom.xml
└── src
├── main
│ └── java
│ └── org
│ └── fazlan
│ └── lazyrecords
│ └── book
│ └── Books.java
└── test
└── java
└── org
└── fazlan
└── lazyrecords
├── book
│ ├── BooksRecordTest.java
│ └── H2BooksRecordTest.java
└── util
└── DataSourceObjectMother.java
Step 1: Creating the Project
mvn archetype:generate -DartifactId=org.fazlan.lazyrecords -DgroupId=org.fazlan.lazyrecords
-Dversion=1.0-SNAPSHOT -DinteractiveMode=false
Step 2: Updating the Maven dependencies
<?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>org.fazlan.lazyrecords</groupId>
<artifactId>lazyrecords</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<h2.version>1.3.159</h2.version>
<lazyrecords.version>272</lazyrecords.version>
<testng.version>6.8</testng.version>
<totallylazy.version>1165</totallylazy.version>
<hamcrest.version>1.3.RC2</hamcrest.version>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.lazyrecords</groupId>
<artifactId>lazyrecords</artifactId>
<version>${lazyrecords.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.totallylazy</groupId>
<artifactId>totallylazy</artifactId>
<version>${totallylazy.version}</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Step 3: Creating the Definition and the Keywords for the Book Entity
The definition is represented in the form of an Interface with the fields as Keywords as shown,
package org.fazlan.lazyrecords.book;
import com.googlecode.lazyrecords.Definition;
import com.googlecode.lazyrecords.Keyword;
import java.math.BigDecimal;
import java.net.URI;
import java.util.UUID;
import static com.googlecode.lazyrecords.Grammar.definition;
import static com.googlecode.lazyrecords.Grammar.keyword;
public interface Books extends Definition {
Books BOOKS = definition(Books.class);
Keyword<URI> isbn = keyword("isbn", URI.class);
Keyword<String> title = keyword("title", String.class);
Keyword<Boolean> inPrint = keyword("inPrint", Boolean.class);
Keyword<UUID> uuid = keyword("uuid", UUID.class);
Keyword<BigDecimal> rrp = keyword("rrp", BigDecimal.class);
}
Step 4: Configuring the Connection, Creating the Records and Setting-up Initial Data.
The Framework allows you to access different types of data repositories such as JDBC, XML, Lucene and In-Memory. During this example, we will be accessing an In-Memory H2 database.
. . .
import static org.fazlan.lazyrecords.util.DataSourceObjectMother.getH2Connection;
@Test
public class BooksRecordTest {
. . .
protected Records records;
protected Connection connection;
@BeforeMethod
public void beforeEachMethodSetupRecords() throws Exception {
this.connection = getH2Connection();
createRecords();
setupData();
}
@AfterMethod
public void afterEachMethodCleanUp() throws Exception {
close(records);
safeClose(connection);
}
. . .
}
Creating the Records to Use the Newly Created Connection to the H2 Database
@Test
public class BooksRecordTest {
. . .
private void createRecords() throws SQLException {
records = DataSourceObjectMother.createRecords(connection);
}
. . .
}
Records acts as the interface between your application and the data repository ( think of it as the Session in Hibernate). The following code is to initialising the Records with some basic configuration.
public class DataSourceObjectMother {
. . .
public static Records createRecords(Connection connection) throws SQLException {
SqlGrammar grammar = new AnsiSqlGrammar();
SqlRecords sqlRecords = sqlRecords(connection, grammar);
return new SchemaGeneratingRecords(sqlRecords, new SqlSchema(sqlRecords, grammar));
}
private static SqlRecords sqlRecords(Connection connection, SqlGrammar grammar) {
return new SqlRecords(
connection, new SqlMappings(), grammar,
loggers(new MemoryLogger(), new PrintStreamLogger(new PrintStream(streams(System.out, new ByteArrayOutputStream()))))
);
}
}
We tell the framework to create a database schema based on the Books Definition and set the default SQL grammar.
You can read more about these configuration here.
Setting-up the Initial Data ( Inserting Book Records to the Database)
. . .
import static org.fazlan.lazyrecords.book.Books.*;
. . .
@Test
public class BooksRecordTest {
public static final URI zenIsbn = uri("urn:isbn:0099322617");
public static final URI godelEsherBach = uri("urn:isbn:0140289208");
public static final URI cleanCode = uri("urn:isbn:0132350882");
public static final UUID zenUuid = randomUUID();
. . .
private void setupData() {
setupBooks();
}
private void setupBooks() {
records.remove(BOOKS);
records.add(BOOKS,
addBook(zenIsbn, "Zen And The Art Of Motorcycle Maintenance", true, zenUuid, new BigDecimal("9.95")),
addBook(godelEsherBach, "Godel, Escher, Bach: An Eternal Golden Braid", false, randomUUID(), new BigDecimal("20.00")),
addBook(cleanCode, "Clean Code: A Handbook of Agile Software Craftsmanship", true, randomUUID(), new BigDecimal("34.99")));
}
private Record addBook(URI bookisbn, String booktitle, boolean bookinPrint, UUID bookuuid, BigDecimal bookrrp) {
return Grammar.record(BOOKS.isbn, bookisbn, BOOKS.title, booktitle, BOOKS.inPrint, bookinPrint, BOOKS.uuid, bookuuid, BOOKS.rrp, bookrrp);
}
}
Step 5: Retrieving the Book Entries via the Rerods
Following tests shows how to retrieve Book entities based on a certain criteria.
@Test
public class BooksRecordTest {
. . .
@Test
public void itReturnsAllTheBooks() throws Exception {
//when
Sequence<Record> allBooks = records.get(BOOKS).filter(all());
//then
assertThat(allBooks.size(), is(3));
}
@Test
public void itReturnsAllThePaginatedBooksSortedByTitleInAscendingOrder() throws Exception {
//given
int currentPage = 1;
int numberOfRecordsPerPage = 2;
//when
Sequence<URI> allBooks = records.get(BOOKS).filter(all()).sortBy(ascending(title)).drop(currentPage - 1).take(numberOfRecordsPerPage).map(isbn);
//then
assertThat(allBooks, hasExactly(cleanCode, godelEsherBach));
}
@Test
public void itReturnsTheCleanCodeBookWhenSearchedByItsISBN() {
//when
Record theCleanCodeBook = records.get(BOOKS).filter(where(isbn, Grammar.is(cleanCode))).head();
//then
assertThat(theCleanCodeBook.get(isbn), Is.is(equalTo(cleanCode)));
assertThat(theCleanCodeBook.get(title), Is.is("Clean Code: A Handbook of Agile Software Craftsmanship"));
assertThat(theCleanCodeBook.get(inPrint), Is.is(true));
assertThat(theCleanCodeBook.get(rrp).setScale(DECIMALS_TO_TWO), Is.is(equalTo(new BigDecimal("34.99").setScale(DECIMALS_TO_TWO))));
}
@Test
public void itReturnsTheBookWithZenISBNWhenSearchedByTitleAndInPrintColumns() {
//when
Record theZenBook = records.get(BOOKS).filter(where(title, Grammar.contains("Zen"))).head();
//then
assertThat(theZenBook.get(isbn), Is.is(equalTo(zenIsbn)));
assertThat(theZenBook.get(title), Is.is("Zen And The Art Of Motorcycle Maintenance"));
assertThat(theZenBook.get(inPrint), Is.is(true));
assertThat(theZenBook.get(rrp).setScale(DECIMALS_TO_TWO), Is.is(equalTo(new BigDecimal("9.95").setScale(DECIMALS_TO_TWO))));
}
}
Step 6: Updating a Book Entry via the Records
Following tests shows how to update a Book entity based on a certain criteria.
@Test
public class BooksRecordTest {
. . .
@Test
public void itUpdatesTheBookTitleWhenGivenISBN() {
//given
String newZenTitle = "Zen And The Art Of Motorcycle Maintenance - II";
//when
records.put(BOOKS, update(using(isbn), record(isbn, zenIsbn, title, newZenTitle)));
//then
Record theZenBook = records.get(BOOKS).filter(where(title, Grammar.is(newZenTitle))).head();
assertThat(theZenBook.get(isbn), Is.is(equalTo(zenIsbn)));
assertThat(theZenBook.get(title), Is.is(newZenTitle));
assertThat(theZenBook.get(inPrint), Is.is(true));
assertThat(theZenBook.get(rrp).setScale(DECIMALS_TO_TWO), Is.is(equalTo(new BigDecimal("9.95").setScale(DECIMALS_TO_TWO))));
}
. . .
}
Step 7: Deleting a Book Entry via the Records
Following tests shows how to delete a Book entity based on a certain criteria.
@Test
public class BooksRecordTest {
. . .
@Test
public void itDeletesTheBookWhenGivenItsISBN() {
//given
URI isbnToDelete = zenIsbn;
//when
records.remove(BOOKS, where(isbn, Grammar.is(isbnToDelete)));
//then
assertThat(records.get(BOOKS).filter(all()).size(), is(2));
assertThat(records.get(BOOKS).filter(where(isbn, Grammar.is(isbnToDelete))).isEmpty(), CoreMatchers.is(true));
}
. . .
}
Summary:
This was a brief article on how to make use of Lazyrecords as a data source access framework in your development environment.
Sample code can be found here.