BeanKeeper

Practical Object Persistence

Tutorial chapters:

Instantiating the Store

All you have to learn to start using the library is the Store class. Before using the Store, you should instantiate it, then use it to access your database. There are two constructors to use. The first takes a JDBC Driver class, and the database url, and that's it: Store myStore = new Store("org.postgres.Driver","jdbc:postgres:mydb"); The second one takes a DataSource. Usually Web servers and other servers which publish their database through JNDI will make you use this: Store myStore = new Store((DataSource) ctx.lookup("jdbc/myds")); That's it, no configuration required, you are ready to use the library!

Saving your first object

That's easy, take for example you have the following classes:


        package com.acme.bookstore;
        public class Book
        {
           private String title;
           private Author author;

           ...setters, getters...
        }

        package com.acme.bookstore;
        public class Author
        {
           private String firstName;
           private String lastName;
           private Date birthDate;

           ...setters, getters...
        }
        
So, you have an instance of a Book, which of course has an Author given. To save it you do: store.save(book);

Yes, you do not have to write "create table" scripts, do not have to implement or extend other interfaces or classes to do this. This is enough. And if you later add a member attribute or two, the library will also alter the already existing tables for you. The table names created try to be human-readable, you should check it out, if you are interested.

Note also, that if the book object refers to an author, then the author object is also saved in the database. So the save is transitive, in that it saves all objects which are referenced and not yet contained in the database. Already contained objects are not saved again.

Removing an object

I think, I don't have to explain much.

store.remove(book);

If another object refers to this object, the reference will be set to null, or in the case of Collection types or Map, the relevant entry will automatically cease to exist.

Selecting an object from the Store

You can find objects in the Store easily using the find methods. An easy demonstration:

List books = store.find("find book");

What is strange in this statement:

Looks good? Let's take it a step further. We want to select specific books, how do you do that? Very simple:

List books = store.find("find book where title='Java for dummies'");

Almost same as SQL, nothing new here. But what if I want to select by author's firstname. No problem, write:

List books = store.find("find book where book.author.firstname='Neal'");

You can use almost the same syntax with a query statement as with SQL. You can use parenthesis, you can create expressions using "and", "or", "not" binary operators. You can use "=","<>", "<", ">", "like" operators between attributes of an object.

find book where book.title like 'Snow%' and (book.author.firstname='Neal' or book.author.lastname<>'Smith')

Sometimes it is handy, if you can "rename" some classes, so you can use them multiple times in a single statement. For example if I want to select all books by the same author as my "Snow Crash" titled book: (Note: I will only give the select statement here)

find book where book.author=snow(book).author and snow.title='Snow Crash'

A few things to note:

You can of course order you results too, just like in sql, basically the same syntax too: find book order by title asc The above statement lists all books ordered by title, in ascending order (which is also the default if you ommit the direction). You are not limited to ordering by the attributes of the selected class however: find book order by book.author.firstName The above orders by the book's author's firstname, as you probably guessed. Be however careful when ordering by attributes not returned by the query, because these attributes will be checked when determining distinctivity. Two potential results are distinct if the returned objects or any of the order by attributes are distinct.

Now, if you came this far in the tutorial, and made yourself familiar with the save(), remove(), and find() methods, you are ready to use the library. Continue only if you are curious.

Historical search

With this feature, you can access your database's earlier states. The persistence layer basically keeps versioning your objects, and all previous objects are accessible through historical searching based on dates. Example: find book at ? The query will return the same objects as it would when it would be run at the date specified following the at keyword. Of course the query can be more complex: find book where book.author.firstName='Neal' order by book.author.lastName at ? The contract of the at keyword stays the same, it returns the exact objects as it would on the date specified.

Polymorphism

What is polymorphism? Polymorphism is when you select for a Writing class, and expect it to return not just Writing type objects, but Article objects, Book objects, etc. Also, if you insert a Book object which is an instance of Writing class, you except it to be selected when selecting for Writing type objects. So basically you want the persistence layer to behave like the Java language itself. Ok, you may now say, that all this is promising, but how do I enable it and configure it, so the persistence layer knows what classes are subclasses of other classes, which classes to return on a polymorph enabled query. The answer is: you don't need to configure anything. All this is automatic. Let's see an example:


        package com.acme.bookstore;
        public class Writing
        {
           private String title;

           ...setters, getters...
        }
           
        package com.acme.bookstore;
        public class Book extends Writing
        {
           private String isbn;

           ...setters, getters...
        }
        

Now let's consider the following code:


        store.insert(new Book("Title","ISBN-1-2-3-4"));
        List result = store.find(
           "find writing where title='Title'");
        

Would you expect the result to include our inserted book too? If yes, that's polymorphism (well, a part of it anyhow). If not, you probably shouldn't be coding in an object-oriented language. :)

View selects

Sometimes, in an interactive application or web-application you need to present the user some result sets, lists or reports which usually span multiple objects (tables). If the attributes and objects of the report are not closely related (span multiple, distant objects), then assembling the report can cause a few selects for each row, which can add up to a few hundred selects a screen. Considering that the information is only selected for presentation it is not important to combine the information into objects, so here comes the view operator. The view operator can select multiple attributes into one result set, but the result set will consist of Maps rather than specific objects. Let's consider the following code:


        store.find("view book.title, 
        book.author.lastname,
        catalog.borrower.name 
        where catalog.book=book");
        

The above code creates a small report, which consists of a book's title, the author's lastname and the current person's lastname who has the book currently. To assemble all these information with objects, one had to use multiple selects per row.

The resulting maps' keys will be the names of the attributes selected. The above example will return a result set with Maps which will have the following keys: title, lastname, name. If you select multiple attributes with the same name, you have to name them explicitely to avoid conflicting attribute names:


        view book1(book).title title1, 
        book2(book).title title2
        where book1.isbn=book2.isbn and book1<>book2
        

The above select is a simple query which tries to report books with conflicting ISBNs. The Maps return contain the following keys: title1, title2.

Lists, Maps and the special operator "contains"

The persistence framework can save Lists and Maps, but there are a few restrictions:

I think these restrictions generally won't affect the usability of these constructs, but your mileage may vary. So how do you use these in selecting expressions? This is a job for: the "contains" operator. Let's take an example:


        package com.acme.bookstore;
        public class Book
        {
           private String title;
           private List authors;

           ...setters, getters...
        }
        

Let's do the previous select (selecting for author's firstname) in this case. (Only the select statement is shown, you know, you should call the store.find() method with this)

find book where book.authors contains author and author.firstname='Neal'

A little note is in place here: You can not negate the contains operator, it will throw an exception. Let's take another example. What if our Book implementation contains a Map, with the keys giving some position on the author, for example "mainauthor", "correcter", etc.:


        package com.acme.bookstore;
        public class Book
        {
           private String title;
           private Map authors;

           ...setters, getters...
        }
        

You could do:

find book where book.authors['mainauthor']=author and author.firstname='Neal'

But you can write it also:

find book where book.authors['mainauthor'](author).firstname='Neal'

Note, that after selecting the "mainauthor" entry from the "authors" map, the class of the entry is given (with the same syntax as with aliasing). This is because this query language is statically typed, so to reference an attribute you must declare which class you expect that entry to be.

Dates, and custom beans as parameters

As said before, you can check for equality between object instances too. But how do you give an object instance to the select expression? The same as in SQL, you use the question mark:

store.find("find book where book=?",new Object[] { b });

The above expression may make not much sense, but demonstrates, that if you have an instance of a Book named "b", you can use it as a parameter just fine. The same goes for dates:

store.find("find book where book.publishdate > ?",new Object[] { yesterday });

Transactions

To use transactions, one may use the TransactionTracker object. You can get it with:

TransactionTracker tt = store.getTransactonTracker();

If you want to get Transactions, there are two ways:

Transaction tx = tt.getTransaction(TransactionTracker.TX_REQUIRED);

Or:

Transaction tx = tt.getTransaction(TransactionTracker.TX_NEW);

The first means, you want only the current transaction (the one associated with current Thread). If there is no current transaction, the tracker allocates one for you. The latter (TX_NEW) means that no matter if there is a current transaction going on, you need a fresh one (meaning you probably want to commit the following code independently of the calling code).


        Transaction tx = tt.getTransaction(
              TransactionTracker.TX_REQUIRED);
        tx.begin();

        ...save, remove, find...

        tx.commit();
        

Note: You should always call begin() at the beginning of a transaction, and always call either commit() or rollback() at the end. You should watch out for exceptions! The correct code should be something like:


        Transaction tx = tt.getTransaction(
              TransactionTracker.TX_REQUIRED);
        tx.begin();
        try
        {
           ...save, remove, find...
        } catch ( Exception e ) {
           // handling
           tx.markRollbackOnly();
        } finally {
           tx.commit();
        }
        

If you want to have manual transaction demarcation in your code (as opposed to using Spring, or managing transactions from the container), you can use AOP, which enables you to write something like this:


        @Tx
        public ...method...
        {
           ...save, remove, find...
        }
        

In this case AOP could wrap the method in the try-finally block required transparently, without much boilerplate code.

Top