Lightweight R/O Mapping
Pages: 1, 2, 3, 4, 5, 6, 7
Accordingly, the getter methods are equipped with
@ParameterAssociation annotations to connect the
properties of the JavaBean to the parameters in an update, delete,
or insert call. This annotation applies to the getter
methods only, because Amber uses the getter methods to
retrieve the values to fill in the parameters. Because of JDBC, it
is necessary to index the parameters. This can be redundant,
depending on the underlying database and whether stored procedures
are used or not, but for completeness and to follow the JDBC API
they need to be provided. The annotation property
isAutoIdentity will be discussed when we get to
writing data into the database.
It is necessary to provide a no-argument constructor in order for objects of this class to be constructed automatically (via reflection). In the class above, there is a no-argument constructor available, since we did not provide any other, but we need to be careful when we add additional constructors to the class, because then we would need to provide one explicitly for Amber.
The result is a JavaBean that shows exactly from where in the database its content is taken and to where it is written. There is no need for an external specification file. Note that we could actually set up any class in this fashion, not just JavaBeans.
You may wonder: why use annotations? Why not use an implicit association via the property names, since we are dealing with JavaBeans anyway? This is done in order to retain one degree of freedom in our design. In other words, we Java guys do not want to be dependent on how the ER model designers name their table columns. If you are used to working with tables, you might not agree with this, but when you are using stored procedures, you have to deal with joined tables and views that sometimes have to use distinguished names.
Amber's Connectors and JDBC
Before we go ahead with reading and writing, we need to
establish a connection to the database. Amber uses a
Connector to access the database. In short, this is
the combination of a database driver and a corresponding connection
URL. A ConnectorFactory is used to manage the
available connections in an application. To initialize a simple
connection to an SQL server using a native type-4 driver, one would
use the following code. We assume the server name to be
localhost, the database name to be jedi, the login to be
use, and finally, the password to be theforce. For brevity, I will
omit all exception-handling in the code below.
String driverClassName =
"com.microsoft.jdbc.sqlserver.SQLServerDriver";
String url =
"jdbc:microsoft:sqlserver://" +
"localhost;databasename=jedi;" +
"user=use;pwd=theforce";
Amber's Connector is associated with a String, alias
under which it remains accessible from the
ConnectorFactory. Here, we're going to use the alias
starwars.
ConnectorFactory.getInstance().add(
"starwars", driverClassName, url
);
Since a Connector is a lightweight wrapper around a
JDBC connection, we can, in general, do anything with it that we
might originally be doing with such a connection.
Reading
Wrapped around such aConnector is a
BeanReader object, which takes a
Connector and a Class that tells the
reader what type of bean should be read from the database. The
problem of reading a list of Jedi objects is then
reduced to the following lines.
Connector connector =
ConnectorFactory.createConnector( "starwars" );
BeanReader reader =
new BeanReader( Jedi.class, connector );
Collection<Jedi> jediList =
reader.executeCreateBeanList(
"select * from jedi"
);
This code employs a new Java language feature called generics
that is available as of J2SE 5.0. In the line that declares the
Collection, the syntax says that the
Collection called jediList consists
uniformly of objects that are of the type Jedi. The
compiler will issue a warning here, since the reader only knows at
runtime what kind of class it is going to create. Because of type
erasure in the generics implementation of J2SE 5.0, it is not
possible to safely cast the result. And, sadly, it is also not possible
to write the BeanReader class as
BeanReader<Jedi> for the same reason. In short,
using Java reflection and generics don't mix.