| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This chapter is a tutorial writing a simple application using BeanKeeper, with complete source code listing and build help. Java knowledge is of course assumed, but no SQL knowledge is required. The tutorial will try to simulate a real-world program lifecycle, starting with minimal code, and gradually adding more features as we go.
To demonstrate the usage of BeanKeeper, we'll use HSQLDB as a database server. This is a compact database engine, written in Java, which can be easily configured to create the database in the filesystem, so we don't have to install a full-blown database server.
To compile and build these examples, you'll need Java SDK 1.4 or newer, and Ant.
All source code in this chapter is included in the distribution, in the
directory doc/examples/.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
We will create a simple contact information application, which will keep track of our friends, and their contact information. Maybe not all aspects of this application will be practical from a programmer's point of view, but keep in mind, that this example is just that, an example.
First, we'll have to create the appropriate directory structure for our little project. Create an empty directory for the project. All directories referred to in this chapter will be relative to this project directory. Create the following directory structure under the project directory:
.
+src
+com
+acme
+contacts
+lib
hsqldb.jar
java-cup-11-runtime.jar
log4j-1.2.8.jar
beankeeper-2.5.0.jar
+build
|
The src/com/acme/contacts tree will be occupied by our Java sources,
while the lib directory contains necessary libraries for the example
to run: The hsqldb.jar file is our database, the other libraries are for
the persistence library, and are included in the distribution under
lib/runtime. The build directory will hold the compiled
classes.
To configure the log4j library, we'll need to create a file named
log4j.properties inside the lib directory. The file
should only contain one line, to at least disable debug logging:
log4j.rootLogger=ERROR |
For more information to configure log4j, see it's documentation at http://logging.apache.org/log4j/docs/.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The next thing we do, is to create our first class for our application, which will represent a Person:
package com.acme.contacts;
public class Person
{
private String firstName;
private String lastName;
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName=firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName=lastName;
}
public String toString()
{
return "[Person: "+lastName+", "+firstName+"]";
}
}
|
Object of this class will be used by the persistence layer to store..well.. people. It is a standard JavaBean, and this is also the recommended way (in Java) to hold and communicate data. Using such beans and this library, it is possible to create a model which can be used within the whole application from the data layer to the presentation layer. The only constraint by the library is, that there always has to be a default constructor, either implicitly (as above), or explicitly if other constructors are also defined. Of course, you do not need to remember this, the library will throw an Exception with the appropriate message to remind you this, in case you forget it.
Put the above source file into the src/com/acme/contacts directory.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Now we have a source file, so we can already compile our project. We will create an ant file, which will compile our sources, and later run our application:
<project name="examples" default="compile" basedir=".">
<path id="all_classpath">
<pathelement location="build" />
<fileset dir="lib" includes="*.jar" />
</path>
<target name="clean">
<delete dir="build"/>
<mkdir dir="build"/>
</target>
<target name="compile">
<javac classpathref="all_classpath" srcdir="src"
debug="on" destdir="build"/>
</target>
</project>
|
To compile the project, you'll need to simply type ant:
demon@ruby:~/prg/examples/demo$ ant
Buildfile: build.xml
compile:
[javac] Compiling 1 source file to /home/demon/prg/examples/demo/build
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
To interact with the persistence library, the main interface is the Store class. This class
holds all necessary functions, such as save(), remove() and find(). To
get a Store object, it's constructor awaits the pointers to a database, either a driver
class and url, or a datasource. To access our database, we create a utility, which will access
a singleton instance of Store:
package com.acme.contacts;
import hu.netmind.beankeeper.Store;
public class StoreUtil
{
private static Store store = null;
public static Store getStore()
{
return store;
}
static
{
store = new Store("org.hsqldb.jdbcDriver","jdbc:hsqldb:file:testdb");
}
}
|
As you see, we import hu.netmind.beankeeper.Store, this is the class the application
will use everywhere to access the persistence library. This utility class will always return
a valid Store object (if the static initialization didn't fail), when calling
StoreUtil.getStore(), which is a static method.
Of course, in a live project, such utility class will be more complex, since it has to read
the database parameters from a configuration file, or get a DataSource through JNDI.
Anyway, this will be sufficient for this little example, even if the database is hardcoded.
Our main contact information handler will be a command line program, which will store, list and
search our contact database, which currently only holds person objects, but we will get to that later.
First, let's create the "shell" of our command line program, which will list all persons
in our database:
package com.acme.contacts;
import hu.netmind.beankeeper.Store;
public class contacts
{
public static void main(String argv[])
{
listPersons();
}
private void listPersons()
{
List persons = StoreUtil.getStore().find("find person");
System.out.println("Persons: "+persons);
}
}
|
Well, that does not seem to be complicated, I hope. Let's go through the listPersons method. The
first line is what queries the database for any person objects it might have. Of course, initially there
ought to be none, but the important part is, that we receive a standard List of Persons
which we print out in the following line. To run this program, add a task to the ant file:
<target name="list" depends="compile">
<java classname="com.acme.contacts.contacts"
classpathref="all_classpath" fork="yes"/>
</target>
|
Let's try it out:
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
[javac] Compiling 1 source file to /home/demon/prg/examples/demo/build
list:
[java] Persons: []
BUILD SUCCESSFUL
Total time: 6 seconds
demon@ruby:~/prg/examples/demo$
|
The line of the list task tells us, that our program worked, and told us, what we of course already know, that the list of persons currently in the database is an empty list.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Let's implement adding a Person to the program, so we can see something more interesting.
To do this, we add another method to contacts.java:
private static void createPerson(String firstName, String lastName)
{
Person person = new Person();
person.setFirstName(firstName);
person.setLastName(lastName);
StoreUtil.getStore().save(person);
}
|
The method takes a firstname and lastname as a parameter, populates a Person object with it,
and then simply calls save() on the Store object with it. To call this method,
we modify the main method of the command line interface, so the method can be invoked:
public static void main(String argv[])
{
if ( argv.length == 0 )
listPersons();
else if ( "create".equals(argv[0]) )
createPerson(argv[1],argv[2]);
}
|
If called with empty parameters, the program still executes the list method, but when called with the
first parameter create, it will call the create method with the next two command line parameters
as the first and last names of the Person. This code does not check, that those parameters
exist, but that is not the point now. Let's modify the ant script, so we can create peoples with it.
To do this, we will add the create task:
<target name="create" depends="compile">
<input message="Firstname" addproperty="firstname"/>
<input message="Lastname" addproperty="lastname"/>
<java classname="com.acme.contacts.contacts"
classpathref="all_classpath" fork="yes">
<arg line="create ${firstname} ${lastname}"/>
</java>
</target>
|
Well, it's interface won't be pretty, but enough for demonstration purposes. The task first gets the firstname and lastname, then calls the program with these parameters. The output should be something like (after issuing a list too):
demon@ruby:~/prg/examples/demo$ ant create
Buildfile: build.xml
compile:
[javac] Compiling 1 source file to /home/demon/prg/examples/demo/build
create:
[input] Firstname
John
[input] Lastname
Smith
BUILD SUCCESSFUL
Total time: 12 seconds
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
list:
[java] Persons: [[Person: Smith, John]]
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
You can add as many persons to the database now as you like, and it wasn't hard, was it? Before we created the first person, there was nothing in the database, no tables, no rows, nothing. Let's look a bit deeper what happened, and why and how John Smith is now stored in the database.
When the code executed the getStore().save(person) instruction, the persistence library first checked the
database whether the Person class is even known. To check this, the persistence library uses a helper
table, which stores all used classes. But this time, there was no such table, so an empty one was created,
and of course the new class was inserted. Now, the library tries again to store the given person, and checks
whether there is a table to store person objects anyway. Most likely it did not find the appropriate table,
so the library used reflection to analize the class, and created a matching table. It most likely used
the table name 'person', and all attributes were named just like in the java class. There are times however, when the
library can not do this, for example when the database does not support long enough names, or the class'
or attributes' names are reserved words. In these cases the library tries to pick a name close to the original.
After the table is created, the library registers the object, the object receives an identifier, and is
inserted into the table. Once these tasks are all done, the storing of the next person the library may encounter is not so complicated,
because the library skips immediately to the insert part (as all previous tasks are required only once).
Every time the Store is instantiated, the first time a class is referenced (saved, removed or selected),
the appropriate table's existence is always checked. Not only that, but also the table's schema is checked,
and the library tries to compensate for changes in the class file. For example if a class file contains an attribute
which was not there last time (it was added to the class), the library will alter the table to contain that
attribute. If an attribute was removed, the library removes the appropriate column, and if the type of an attribute
was changed, the column is dropped and re-added with the new type.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Enough of the theory, let's upgrade our program to actually hold some kind of contact information, for example address. An address is composed of many components, but let's just pick the most common ones:
package com.acme.contacts;
public class Address
{
private String country;
private String city;
private int zip;
public String getCountry()
{
return country;
}
public void setCountry(String country)
{
this.country=country;
}
public String getCity()
{
return city;
}
public void setCity(String city)
{
this.city=city;
}
public int getZip()
{
return zip;
}
public void setZip(int zip)
{
this.zip=zip;
}
public String toString()
{
return " - "+country+"/"+city+"("+zip+")";
}
}
|
Modify the Person class to contain a single Address, and also to include it in
it's string representation:
package com.acme.contacts;
public class Person
{
private String firstName;
private String lastName;
private Address address;
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName=firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName=lastName;
}
public Address getAddress()
{
return address;
}
public void setAddress(Address address)
{
this.address=address;
}
public String toString()
{
return "[Person: "+lastName+", "+firstName+address+"]";
}
}
|
We modify our main program too, so when creating a person, we can add the address too:
public static void main(String argv[])
{
if ( argv.length == 0 )
listPersons();
else if ( "create".equals(argv[0]) )
createPerson(argv[1],argv[2],argv[3],argv[4],Integer.valueOf(argv[5]).intValue());
}
private static void createPerson(String firstName, String lastName, String country, String city, int zip)
{
Address address = new Address();
address.setCountry(country);
address.setCity(city);
address.setZip(zip);
Person person = new Person();
person.setFirstName(firstName);
person.setLastName(lastName);
person.setAddress(address);
StoreUtil.getStore().save(person);
}
|
Before you create a new person with an address, let's check, what the list task says about our current database.
It should be interesting, because we added a field to the already saved Person class, but of course we did
not have an address the time we inserted the first person:
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
[javac] Compiling 3 source files to /home/demon/prg/examples/demo/build
list:
[java] Persons: [[Person: Smith, Johnnull]]
BUILD SUCCESSFUL
Total time: 6 seconds
demon@ruby:~/prg/examples/demo$
|
As you see, the address was null. This is also what we expected. Now let's insert a new person, with an address now. Before that, we first have to correct the ant task to include the new parameters:
<target name="create" depends="compile">
<input message="Firstname" addproperty="firstname"/>
<input message="Lastname" addproperty="lastname"/>
<input message="Country" addproperty="country"/>
<input message="City" addproperty="city"/>
<input message="Zip" addproperty="zip"/>
<java classname="com.acme.contacts.contacts"
classpathref="all_classpath" fork="yes">
<arg line="create ${firstname} ${lastname} ${country} ${city} ${zip}"/>
</java>
</target>
|
Now create a new person:
demon@ruby:~/prg/examples/demo$ ant create
Buildfile: build.xml
compile:
create:
[input] Firstname
Jane
[input] Lastname
Doe
[input] Country
Neverland
[input] City
Walkabout
[input] Zip
5
BUILD SUCCESSFUL
Total time: 18 seconds
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
list:
[java] Persons: [[Person: Doe, Jane - Neverland/Walkabout(5)], [Person: Smith, Johnnull]]
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
To summarize, we extended the Person class with another attribute, which referenced an Address
object. What happened is, when we saved Jane Doe, the library detected, that a new attribute was present in
the Person class, extended the already existing table to match. Then it also detected, that there
is a new class that must be saved, and saved it when the referencing object was saved. It is worth to note,
that normally referenced objects are not saved (to put it another way: save() is not recursive),
only if the object does not exist yet, which was the case this time.
The list code was not changed, we only modified our object model and the library adapted.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
As a last exercise, let's pretend that each person can have multiple addresses, for example home,
workplace, parent's, or whatever. That would mean, that instead of a fix address attribute, the
Person class would need a list of addresses. There are two ways to do this: the relational
model way, and the object model way. In the relational way, for example when using relational databases, you
would create a table, which holds all addresses, and then extend the person table with a foreign key
which points to address rows. This is a correct way to express relations most of the time, but the
object model way is more practical this time: Just create a list in the Person class, and
let the library handle the relations.
So, let's modify the Person class to contain a List of addresses, and extend the Address
class to contain the type of address:
package com.acme.contacts;
import java.util.List;
public class Person
{
private String firstName;
private String lastName;
private List addresses;
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName=firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName=lastName;
}
public List getAddresses()
{
return addresses;
}
public void setAddresses(List addresses)
{
this.addresses=addresses;
}
public String toString()
{
return "[Person: "+lastName+", "+firstName+addresses+"]";
}
}
|
As you saw, there is again no need to configure anything, the library will adapt to your object model runtime. Let's see, what our list function tells us now:
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
[javac] Compiling 2 source files to /home/demon/prg/examples/demo/build
list:
[java] Persons: [[Person: Doe, Janenull], [Person: Smith, Johnnull]]
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
Interesting to note, that both persons we inserted until now return null address lists. This is expected,
since we inserted John Smith, when there was no address attribute yet, and Jane Doe when the address
attribute was a single Address object.
Let's extend the main program, so we can add addresses to people. The parameters will stay the same,
but we modify the createPerson method, so it only creates a new person, when there is no person
with the given name. If there is, it merely adds the new address to it. The new method will be:
private static void createPerson(String firstName, String lastName, String country, String city, int zip)
{
Address address = new Address();
address.setCountry(country);
address.setCity(city);
address.setZip(zip);
Person person = (Person) StoreUtil.getStore().findSingle(
"find person where firstname='"+firstName+"' and lastname='"+lastName+"'");
if ( person == null )
{
person = new Person();
person.setFirstName(firstName);
person.setLastName(lastName);
}
if ( person.getAddresses() == null )
person.setAddresses(new Vector());
person.getAddresses().add(address);
StoreUtil.getStore().save(person);
}
|
Now run the create task, and try to add a new address to John Smith:
demon@ruby:~/prg/examples/demo$ ant create
Buildfile: build.xml
compile:
[javac] Compiling 1 source file to /home/demon/prg/pet-projects/netmind-persistence/doc/examples/demo/build
[javac] Note: /home/demon/prg/examples/demo/src/com/acme/contacts/contacts.java uses unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
create:
[input] Firstname
John
[input] Lastname
Smith
[input] Country
Neverland
[input] City
Nowhere
[input] Zip
1111
BUILD SUCCESSFUL
Total time: 17 seconds
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
list:
[java] Persons: [[Person: Doe, Janenull], [Person: Smith, John[ - Neverland/Nowhere(1111)]]]
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
Let's try to add a few other addresses:
demon@ruby:~/prg/examples/demo$ ant create
Buildfile: build.xml
compile:
create:
[input] Firstname
John
[input] Lastname
Smith
[input] Country
Somecountry
[input] City
Somewhere
[input] Zip
22222
BUILD SUCCESSFUL
Total time: 17 seconds
demon@ruby:~/prg/examples/demo$ ant create
Buildfile: build.xml
compile:
create:
[input] Firstname
John
[input] Lastname
Smith
[input] Country
AirstripOne
[input] City
London
[input] Zip
1
BUILD SUCCESSFUL
Total time: 33 seconds
demon@ruby:~/prg/examples/demo$ ant list
Buildfile: build.xml
compile:
list:
[java] Persons: [[Person: Doe, Janenull], [Person: Smith, John[ - AirstripOne/London(1), - Neverland/Nowhere(1111), - Somecountry/Somewhere(22222)]]]
BUILD SUCCESSFUL
Total time: 4 seconds
demon@ruby:~/prg/examples/demo$
|
Well, this is the end of the standalone example. To summarize, we created a simple program to manipulate people and addresses. The example guided through the first step of creating a project directory structure, and creating the first program skeleton, incrementally to the point where the program was completed.
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Robert Brautigam on November, 21 2009 using texi2html 1.78.