1205 lines
53 KiB
XML
1205 lines
53 KiB
XML
<chapter id="tutorial">
|
|
<title>Introduction to Hibernate</title>
|
|
|
|
<sect1 id="tutorial-intro">
|
|
<title>Preface</title>
|
|
|
|
<para>
|
|
This chapter is an introductory tutorial for new users of Hibernate. We start
|
|
with a simple command line application using an in-memory database and develop
|
|
it in easy to understand steps.
|
|
</para>
|
|
|
|
<para>
|
|
This tutorial is intended for new users of Hibernate but requires Java and
|
|
SQL knowledge. It is based on a tutorial by Michael Gloegl, the third-party
|
|
libraries we name are for JDK 1.4 and 5.0. You might need others for JDK 1.3.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-firstapp">
|
|
<title>Part 1 - The first Hibernate Application</title>
|
|
|
|
<para>
|
|
First, we'll create a simple console-based Hibernate application. We use an
|
|
in-memory database (HSQL DB), so we do not have to install any database server.
|
|
</para>
|
|
|
|
<para>
|
|
Let's assume we need a small database application that can store events we want to
|
|
attend, and information about the hosts of these events.
|
|
</para>
|
|
|
|
<para>
|
|
The first thing we do, is set up our development directory and put all the
|
|
Java libraries we need into it. Download the Hibernate distribution from the
|
|
Hibernate website. Extract the package and place all required libraries
|
|
found in <literal>/lib</literal> into into the <literal>/lib</literal> directory
|
|
of your new development working directory. It should look like this:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
antlr.jar
|
|
cglib-full.jar
|
|
asm.jar
|
|
asm-attrs.jars
|
|
commons-collections.jar
|
|
commons-logging.jar
|
|
ehcache.jar
|
|
hibernate3.jar
|
|
jta.jar
|
|
dom4j.jar
|
|
log4j.jar ]]></programlisting>
|
|
|
|
<para>
|
|
This is the minimum set of required libraries (note that we also copied
|
|
hibernate3.jar, the main archive) for Hibernate. See the <literal>README.txt</literal> file
|
|
in the <literal>lib/</literal> directory of the Hibernate distribution for more information
|
|
about required and optional third-party libraries. (Actually, Log4j is not
|
|
required but preferred by many developers.)
|
|
</para>
|
|
|
|
<para>
|
|
Next we create a class that represents the event we want to store in database.
|
|
</para>
|
|
|
|
<sect2 id="tutorial-firstapp-firstclass">
|
|
<title>The first class</title>
|
|
|
|
<para>
|
|
Our first persistent class is a simple JavaBean class with some properties:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import java.util.Date;
|
|
|
|
public class Event {
|
|
private Long id;
|
|
|
|
private String title;
|
|
private Date date;
|
|
|
|
Event() {}
|
|
|
|
public Long getId() {
|
|
return id;
|
|
}
|
|
|
|
private void setId(Long id) {
|
|
this.id = id;
|
|
}
|
|
|
|
public Date getDate() {
|
|
return date;
|
|
}
|
|
|
|
public void setDate(Date date) {
|
|
this.date = date;
|
|
}
|
|
|
|
public String getTitle() {
|
|
return title;
|
|
}
|
|
|
|
public void setTitle(String title) {
|
|
this.title = title;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
You can see that this class uses standard JavaBean naming conventions for property
|
|
getter and setter methods, as well as private visibility for the fields. This is
|
|
a recommended design - but not required. Hibernate can also access fields directly,
|
|
the benefit of accessor methods is robustness for refactoring.
|
|
</para>
|
|
|
|
<para>
|
|
The <literal>id</literal> property holds a unique identifier value for a particular event.
|
|
All persistent entity classes (there are less important dependent classes as well) will need
|
|
such an identifier property if we want to use the full feature set of Hibernate. In fact,
|
|
most applications (esp. web applications) need to distinguish objects by identifier, so you
|
|
should consider this a feature rather than a limitation. However, we usually don't manipulate
|
|
the identity of an object, hence the setter method should be private. Only Hibernate will assign
|
|
identifiers when an object is saved. You can see that Hibernate can access public, private,
|
|
and protected accessor methods, as well as (public, private, protected) fields directly. The
|
|
choice is up to you and you can match it to fit your application design.
|
|
</para>
|
|
|
|
<para>
|
|
The no-argument constructor is a requirement for all persistent classes; Hibernate
|
|
has to create objects for you, using Java Reflection. The constructor can be
|
|
private, however, package visibility is required for runtime proxy generation and
|
|
efficient data retrieval without bytecode instrumentation.
|
|
</para>
|
|
|
|
<para>
|
|
Place this Java source file in a directory called <literal>src</literal> in the
|
|
development folder. The directory should now look like this:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java]]></programlisting>
|
|
|
|
<para>
|
|
In the next step, we tell Hibernate about this persistent class.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-mapping">
|
|
<title>The mapping file</title>
|
|
|
|
<para>
|
|
Hibernate needs to know how to load and store objects of the persistent class.
|
|
This is where the Hibernate mapping file comes into play. The mapping file
|
|
tells Hibernate what table in the database it has to access, and what columns
|
|
in that table it should use.
|
|
</para>
|
|
|
|
<para>
|
|
The basic structure of a mapping file looks like this:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<?xml version="1.0"?>
|
|
<!DOCTYPE hibernate-mapping PUBLIC
|
|
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
|
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
|
|
|
|
<hibernate-mapping>
|
|
[...]
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Note that the Hibernate DTD is very sophisticated. You can use it for
|
|
auto-completion of XML mapping elements and attributes in your editor or
|
|
IDE. You also should open up the DTD file in your text editor - it's the
|
|
easiest way to get an overview of all elements and attributes and to see
|
|
the defaults, as well as some comments. Note that Hibernate will not
|
|
load the DTD file from the web, but first look it up from the classpath
|
|
of the application. The DTD file is included in <literal>hibernate3.jar</literal>
|
|
as well as in the <literal>src/</literal> directory of the Hibernate distribution.
|
|
</para>
|
|
|
|
<para>
|
|
We will omit the DTD declaration in future examples to shorten the code. It is
|
|
of course not optional.
|
|
</para>
|
|
|
|
<para>
|
|
Between the two <literal>hibernate-mapping</literal> tags, include a
|
|
<literal>class</literal> element. All persistent entity classes (again, there
|
|
might be dependent classes later on, which are not first-class entities) need
|
|
such a mapping, to a table in the SQL database:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
So far we told Hibernate how to persist and load object of class <literal>Event</literal>
|
|
to the table <literal>EVENTS</literal>, each instance represented by a row in that table.
|
|
Now we continue with a mapping of the unique identifier property to the tables primary key.
|
|
In addition, as we don't want to care about handling this identifier, we configure Hibernate's
|
|
identifier generation strategy for a surrogate primary key column:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
<id name="id" column="EVENT_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
The <literal>id</literal> element is the declaration of the identifer property,
|
|
<literal>name="id"</literal> declares the name of the Java property -
|
|
Hibernate will use the getter and setter methods to access the property.
|
|
The column attribute tells Hibernate which column of the
|
|
<literal>EVENTS</literal> table we use for this primary key. The nested
|
|
<literal>generator</literal> element specifies the identifier generation strategy,
|
|
in this case we used <literal>increment</literal>, which is a very simple in-memory
|
|
number increment method useful mostly for testing (and tutorials). Hibernate also
|
|
supports database generated, globally unique, as well as application assigned
|
|
identifiers (or any strategy you have written an extension for).
|
|
</para>
|
|
|
|
<para>
|
|
Finally we include declarations for the persistent properties of the class in
|
|
the mapping file. By default, no properties of the class are considered
|
|
persistent:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
<id name="id" column="EVENT_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="date" type="timestamp" column="EVENT_DATE"/>
|
|
<property name="title"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Just as with the <literal>id</literal> element, the <literal>name</literal>
|
|
attribute of the <literal>property</literal> element tells Hibernate which getter
|
|
and setter methods to use.
|
|
</para>
|
|
|
|
<para>
|
|
Why does the <literal>date</literal> property mapping include the
|
|
<literal>column</literal> attribute, but the <literal>title</literal>
|
|
doesn't? Without the <literal>column</literal> attribute Hibernate
|
|
by default uses the property name as the column name. This works fine for
|
|
<literal>title</literal>. However, <literal>date</literal> is a reserved
|
|
keyword in most database, so we better map it to a different name.
|
|
</para>
|
|
|
|
<para>
|
|
The next interesting thing is that the <literal>title</literal> mapping also lacks
|
|
a <literal>type</literal> attribute. The types we declare and use in the mapping
|
|
files are not, as you might expect, Java data types. They are also not SQL
|
|
database types. These types are so called <emphasis>Hibernate mapping types</emphasis>,
|
|
converters which can translate from Java to SQL data types and vice versa. Again,
|
|
Hibernate will try to determine the correct conversion and mapping type itself if
|
|
the <literal>type</literal> attribute is not present in the mapping. In some cases this
|
|
automatic detection (using Reflection on the Java class) might not have the default you
|
|
expect or need. This is the case with the <literal>date</literal> property. Hibernate can't
|
|
know if the property will map to a SQL <literal>date</literal>, <literal>timestamp</literal>
|
|
or <literal>time</literal> column. We declare that we want to preserve full date
|
|
and time information by mapping the property with a <literal>timestamp</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
This mapping file should be saved as <literal>Event.hbm.xml</literal>, right in
|
|
the directory next to the <literal>Event</literal> Java class source file.
|
|
The naming of mapping files can be arbitrary, however the <literal>hbm.xml</literal>
|
|
suffix became convention in the Hibernate developer community. The directory structure
|
|
should now look like this:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java
|
|
Event.hbm.xml]]></programlisting>
|
|
|
|
<para>
|
|
We continue with the main configuration of Hibernate.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-configuration">
|
|
<title>Hibernate configuration</title>
|
|
|
|
<para>
|
|
We now have a persistent class and its mapping file in place. It is time to configure
|
|
Hibernate. Before we do this, we will need a database. HSQL DB, a java-based in-memory SQL DBMS,
|
|
can be downloaded from the HSQL DB website. Actually, you only need the <literal>hsqldb.jar</literal>
|
|
from this download. Place this file in the <literal>lib/</literal> directory of the
|
|
development folder.
|
|
</para>
|
|
|
|
<para>
|
|
Create a directory called <literal>data</literal> in the root of the development directory -
|
|
this is where HSQL DB will store its data files.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate is the layer in your application which connects to this database, so it needs
|
|
connection information. The connections are made through a JDBC connection pool, which we
|
|
also have to configure. The Hibernate distribution contains several open source JDBC connection
|
|
pooling tools, but will use the Hibernate built-in connection pool for this tutorial. Note that
|
|
you have to copy the required library into your classpath and use different
|
|
connection pooling settings if you want to use a production-quality third party
|
|
JDBC pooling software.
|
|
</para>
|
|
|
|
<para>
|
|
For Hibernate's configuration, we can use a simple <literal>hibernate.properties</literal> file, a
|
|
slightly more sophisticated <literal>hibernate.cfg.xml</literal> file, or even complete
|
|
programmatic setup. Most users prefer the XML configuration file:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<?xml version='1.0' encoding='utf-8'?>
|
|
<!DOCTYPE hibernate-configuration PUBLIC
|
|
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
|
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
|
|
|
<hibernate-configuration>
|
|
|
|
<session-factory>
|
|
|
|
<!-- Database connection settings -->
|
|
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
|
|
<property name="connection.url">jdbc:hsqldb:data/tutorial</property>
|
|
<property name="connection.username">sa</property>
|
|
<property name="connection.password"></property>
|
|
|
|
<!-- JDBC connection pool (use the built-in) -->
|
|
<property name="connection.pool_size">1</property>
|
|
|
|
<!-- SQL dialect -->
|
|
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
|
|
|
|
<!-- Echo all executed SQL to stdout -->
|
|
<property name="show_sql">true</property>
|
|
|
|
<!-- Drop and re-create the database schema on startup -->
|
|
<property name="hbm2ddl.auto">create</property>
|
|
|
|
<mapping resource="Event.hbm.xml"/>
|
|
|
|
</session-factory>
|
|
|
|
</hibernate-configuration>]]></programlisting>
|
|
|
|
<para>
|
|
Note that this XML configuration uses a different DTD. We configure
|
|
Hibernate's <literal>SessionFactory</literal> - a global factory responsible
|
|
for a particular database. If you have several databases, use several
|
|
<literal><session-factory></literal> configurations, usually in
|
|
several configuration files (for easier startup).
|
|
</para>
|
|
|
|
<para>
|
|
The first four <literal>property</literal> elements contain the necessary
|
|
configuration for the JDBC connection. The dialect <literal>property</literal>
|
|
element specifies the particular SQL variant Hibernate generates.
|
|
The <literal>hbm2ddl.auto</literal> option turns on automatic generation of
|
|
database schemas - directly into the database. This can of course also be turned
|
|
off (by removing the config option) or redirected to a file with the help of
|
|
the <literal>SchemaExport</literal> Ant task. Finally, we add the mapping file(s)
|
|
for persistent classes.
|
|
</para>
|
|
|
|
<para>
|
|
Copy this file into the source directory, so it will end up in the
|
|
root of the classpath. Hibernate automatically looks for a file called
|
|
<literal>hibernate.cfg.xml</literal> in the root of the classpath, on startup.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-ant">
|
|
<title>Building with Ant</title>
|
|
|
|
<para>
|
|
We'll now build the tutorial with Ant. You will need to have Ant installed - get
|
|
it from the <ulink url="http://ant.apache.org/bindownload.cgi">Ant download page</ulink>.
|
|
How to install Ant will not be covered here. Please refer to the
|
|
<ulink url="http://ant.apache.org/manual/index.html">Ant manual</ulink>. After you
|
|
have installed Ant, we can start to create the buildfile. It will be called
|
|
<literal>build.xml</literal> and placed directly in the development directory.
|
|
</para>
|
|
|
|
<note>
|
|
<title>Fixing Ant</title>
|
|
<para>
|
|
Note that the Ant distribution is by default broken (as described in the Ant FAQ) and
|
|
has to be fixed by you, for example, if you'd like to use JUnit from inside your
|
|
build file. To make the JUnit task work (we won't need it in this tutorial), either copy
|
|
junit.jar to <literal>ANT_HOME/lib</literal> or remove the
|
|
<literal>ANT_HOME/lib/ant-junit.jar</literal> plugin stub.
|
|
</para>
|
|
</note>
|
|
|
|
<para>
|
|
A basic build file looks like this:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<project name="hibernate-tutorial" default="compile">
|
|
|
|
<property name="sourcedir" value="${basedir}/src"/>
|
|
<property name="targetdir" value="${basedir}/bin"/>
|
|
<property name="librarydir" value="${basedir}/lib"/>
|
|
|
|
<path id="libraries">
|
|
<fileset dir="${librarydir}">
|
|
<include name="*.jar"/>
|
|
</fileset>
|
|
</path>
|
|
|
|
<target name="clean">
|
|
<delete dir="${targetdir}"/>
|
|
<mkdir dir="${targetdir}"/>
|
|
</target>
|
|
|
|
<target name="compile" depends="clean, copy-resources">
|
|
<javac srcdir="${sourcedir}"
|
|
destdir="${targetdir}"
|
|
classpathref="libraries"/>
|
|
</target>
|
|
|
|
<target name="copy-resources">
|
|
<copy todir="${targetdir}">
|
|
<fileset dir="${sourcedir}">
|
|
<exclude name="**/*.java"/>
|
|
</fileset>
|
|
</copy>
|
|
</target>
|
|
|
|
</project>]]></programlisting>
|
|
|
|
<para>
|
|
This will tell Ant to add all files in the lib directory ending with <literal>.jar</literal>
|
|
to the classpath used for compilation. It will also copy all non-Java source files to the
|
|
target directory, e.g. configuration and Hibernate mapping files. If you now run Ant, you
|
|
should get this output:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant
|
|
Buildfile: build.xml
|
|
|
|
copy-resources:
|
|
[copy] Copying 2 files to C:\hibernateTutorial\bin
|
|
|
|
compile:
|
|
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
|
|
|
|
BUILD SUCCESSFUL
|
|
Total time: 1 second ]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-helpers">
|
|
<title>Startup and helpers</title>
|
|
|
|
<para>
|
|
It's time to load and store some <literal>Event</literal> objects, but first
|
|
we have to complete the setup with some infrastructure code. We have to startup
|
|
Hibernate. This startup includes building a global <literal>SessionFactory</literal>
|
|
object and to store it somewhere for easy access in application code.
|
|
A <literal>SessionFactory</literal> can open up new <literal>Session</literal>'s.
|
|
A <literal>Session</literal> represents a single-threaded unit of work, the
|
|
<literal>SessionFactory</literal> is a thread-safe global object, instantiated once.
|
|
</para>
|
|
|
|
<para>
|
|
We'll create a <literal>HibernateUtil</literal> helper class which takes care
|
|
of startup and makes <literal>Session</literal> handling convenient. The so called
|
|
<emphasis>ThreadLocal Session</emphasis> pattern is useful here, we keep the current
|
|
unit of work associated with the current thread. Let's have a look at the implementation:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import org.hibernate.*;
|
|
import org.hibernate.cfg.*;
|
|
|
|
public class HibernateUtil {
|
|
|
|
public static final SessionFactory sessionFactory;
|
|
|
|
static {
|
|
try {
|
|
// Create the SessionFactory from hibernate.cfg.xml
|
|
sessionFactory = new Configuration().configure().buildSessionFactory();
|
|
} catch (Throwable ex) {
|
|
// Make sure you log the exception, as it might be swallowed
|
|
System.err.println("Initial SessionFactory creation failed." + ex);
|
|
throw new ExceptionInInitializerError(ex);
|
|
}
|
|
}
|
|
|
|
public static final ThreadLocal session = new ThreadLocal();
|
|
|
|
public static Session currentSession() throws HibernateException {
|
|
Session s = (Session) session.get();
|
|
// Open a new Session, if this thread has none yet
|
|
if (s == null) {
|
|
s = sessionFactory.openSession();
|
|
// Store it in the ThreadLocal variable
|
|
session.set(s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
public static void closeSession() throws HibernateException {
|
|
Session s = (Session) session.get();
|
|
if (s != null)
|
|
s.close();
|
|
session.set(null);
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
This class does not only produce the global <literal>SessionFactory</literal> in
|
|
its static initializer (called once by the JVM when the class is loaded), but also
|
|
has a <literal>ThreadLocal</literal> variable to hold the
|
|
<literal>Session</literal> for the current thread. No matter when you call
|
|
<literal>HibernateUtil.currentSession()</literal>, it will always return the same
|
|
Hibernate unit of work in the same thread. A call to <literal>HibernateUtil.closeSession()</literal>
|
|
ends the unit of work currently associated with the thread.
|
|
</para>
|
|
|
|
<para>
|
|
Make sure you understand the Java concept of a thread-local variables before you
|
|
use this helper. A more powerful <literal>HibernateUtil</literal> helper can be found
|
|
in <literal>CaveatEmptor</literal> on http://caveatemptor.hibernate.org/ - as well as
|
|
in the book "Hibernate in Action". Note that this class is not necessary if you deploy
|
|
Hibernate in a J2EE application server: a <literal>Session</literal> will be
|
|
automatically bound to the current JTA transaction and you can look up the
|
|
<literal>SessionFactory</literal> through JNDI. If you use JBoss AS, Hibernate can
|
|
be deployed as a managed system service and will automatically bind the
|
|
<literal>SessionFactory</literal> to a JNDI name.
|
|
</para>
|
|
|
|
<para>
|
|
Place <literal>HibernateUtil.java</literal> in the development source directory, next
|
|
to <literal>Event.java</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java
|
|
Event.hbm.xml
|
|
HibernateUtil.java
|
|
hibernate.cfg.xml
|
|
+data
|
|
build.xml]]></programlisting>
|
|
|
|
<para>
|
|
This should again compile without problems. We finally need to configure a logging
|
|
system - Hibernate uses commons logging and leaves you the choice between Log4j and
|
|
JDK 1.4 logging. Most developers prefer Log4j: copy <literal>log4j.properties</literal>
|
|
from the Hibernate distribution (it's in the <literal>etc/</literal> directory) to
|
|
your <literal>src</literal> directory, next to <literal>hibernate.cfg.xml</literal>.
|
|
Have a look at the example configuration and change the settings if you like to have
|
|
more verbose output. By default, only Hibernate startup message are shown on stdout.
|
|
</para>
|
|
|
|
<para>
|
|
The tutorial infrastructure is complete - and we are ready to do some real work with
|
|
Hibernate.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-workingpersistence">
|
|
<title>Loading and storing objects</title>
|
|
|
|
<para>
|
|
Finally, we can use Hibernate to load and store objects. We write an
|
|
<literal>EventManager</literal> class with a <literal>main()</literal> method:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import org.hibernate.Transaction;
|
|
import org.hibernate.Session;
|
|
|
|
import java.util.Date;
|
|
|
|
public class EventManager {
|
|
|
|
public static void main(String[] args) {
|
|
EventManager mgr = new EventManager();
|
|
|
|
if (args[0].equals("store")) {
|
|
mgr.createAndStoreEvent("My Event", new Date());
|
|
}
|
|
|
|
HibernateUtil.sessionFactory.close();
|
|
}
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
We read some arguments from the command line, and if the first argument is
|
|
"store", we create and store a new Event:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private void createAndStoreEvent(String title, Date theDate) {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Event theEvent = new Event();
|
|
theEvent.setTitle(title);
|
|
theEvent.setDate(theDate);
|
|
|
|
session.save(theEvent);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
We create a new <literal>Event</literal> object, and hand it over to Hibernate.
|
|
Hibernate now takes care of the SQL and executes <literal>INSERT</literal>s
|
|
on the database. Let's have a look at the <literal>Session</literal> and
|
|
<literal>Transaction</literal>-handling code before we run this.
|
|
</para>
|
|
|
|
<para>
|
|
A <literal>Session</literal> is a single unit of work. You might be surprised that we
|
|
have an additional API, <literal>Transaction</literal>. This implies that a unit of work
|
|
can be "longer" than a single database transaction - imagine a unit of work that spans
|
|
several Http request/response cycles (e.g. a wizard dialog) in a web application.
|
|
Separating database transactions from "unit of work from the application
|
|
user's point of view" is one of Hibernates basic design concepts. We call a long
|
|
unit of work <emphasis>Application Transaction</emphasis>, usually encapsulating
|
|
several short database transactions. For now we'll keep things simple and assume a
|
|
one-to-one granularity between a <literal>Session</literal> and <literal>Transaction</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
What does <literal>Transaction.begin()</literal> and <literal>commit()</literal> do? Where
|
|
is the <literal>rollback()</literal> in case something goes wrong? The Hibernate
|
|
<literal>Transaction</literal> API is actually optional, but we use it for convenience
|
|
and portability. If you'd handle the database transaction yourself (e.g. by calling
|
|
<literal>session.connection.commit()</literal>), you'd bind the code to a particular
|
|
deployment environment, in this direct unmanaged JDBC. By setting the factory for
|
|
<literal>Transaction</literal> in your Hibernate configuration you can deploy your
|
|
persistence layer anywhere. Have a look at <xref linkend="transactions"/> for more information
|
|
about transaction handling and demarcation. We also skipped any error handling and
|
|
rollback in this example.
|
|
</para>
|
|
|
|
<para>
|
|
To run this first routine we have to add a callable target to the Ant build file:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<target name="run" depends="compile">
|
|
<java fork="true" classname="EventManager" classpathref="libraries">
|
|
<classpath path="${targetdir}"/>
|
|
<arg value="${action}"/>
|
|
</java>
|
|
</target>]]></programlisting>
|
|
|
|
<para>
|
|
The value of the <literal>action</literal> argument is set on the command line when
|
|
calling the target:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant run -Daction=store]]></programlisting>
|
|
|
|
<para>
|
|
You should see, after compilation, Hibernate starting up and, depending on your
|
|
configuration, lots of log output. At the end you will find the following line:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)]]></programlisting>
|
|
|
|
<para>
|
|
This is the <literal>INSERT</literal> executed by Hibernate, the question marks
|
|
represent JDBC bind parameters. To see the values bound as arguments, or to reduce
|
|
the verbosity of the log, check your <literal>log4j.properties</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Now we'd like to list stored events as well, so we add an option to the main method:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[if (args[0].equals("store")) {
|
|
mgr.createAndStoreEvent("My Event", new Date());
|
|
}
|
|
else if (args[0].equals("list")) {
|
|
List events = mgr.listEvents();
|
|
for (int i = 0; i < events.size(); i++) {
|
|
Event theEvent = (Event) events.get(i);
|
|
System.out.println("Event: " + theEvent.getTitle() +
|
|
" Time: " + theEvent.getDate());
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
We also add a new <literal>listEvents() method</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private List listEvents() {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
List result = session.createQuery("from Event").list();
|
|
|
|
tx.commit();
|
|
session.close();
|
|
|
|
return result;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
What we do here is use an HQL (Hibernate Query Language) query to load all existing
|
|
<literal>Event</literal> objects from the database. Hibernate will generate the
|
|
appropriate SQL, send it to the database and populate <literal>Event</literal> objects
|
|
with the data. You can create more complex queries with HQL, of course.
|
|
</para>
|
|
|
|
<para>
|
|
If you now call Ant with <literal>-Daction=list</literal>, you should see the events
|
|
you have stored so far. You might be surprised that this doesn't work, at least if you
|
|
followed this tutorial step by step - the result will always be empty. The reason for
|
|
this is the <literal>hbm2ddl.auto</literal> switch in the Hibernate configuration: Hibernate
|
|
will re-create the database on every run. Disable it by removing the option, and you will
|
|
see results in your list after you called the <literal>store</literal> action a few
|
|
times. Of course the first <literal>store</literal> you do has to have the option turned on,
|
|
so you get the initial tables created. Automatic schema generation and export is mostly useful
|
|
in unit testing.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-associations">
|
|
<title>Part 2 - Mapping associations</title>
|
|
|
|
<para>
|
|
We mapped a persistent entity class to a table. Let's build on this and add some class associations.
|
|
First we'll add people to our application, and store a list of events they participate in.
|
|
</para>
|
|
|
|
<sect2 id="tutorial-associations-mappinguser">
|
|
<title>Mapping the Person class</title>
|
|
|
|
<para>
|
|
The first cut of the <literal>Person</literal> class is simple:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Person {
|
|
|
|
private Long id;
|
|
private int age;
|
|
private String firstname;
|
|
private String lastname;
|
|
|
|
Person() {}
|
|
|
|
// Accessor methods for all properties, private setter for 'id'
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Create a new mapping file called <literal>Person.hbm.xml</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Person" table="PERSON">
|
|
<id name="id" column="PERSON_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="age"/>
|
|
<property name="firstname"/>
|
|
<property name="lastname"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Finally, add the new mapping to Hibernate's configuration:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[ <mapping resource="Event.hbm.xml"/>
|
|
<mapping resource="Person.hbm.xml"/>
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
We'll now create an association between these two entities. Obviously, persons
|
|
can participate in events, and events have participants. The design questions
|
|
we have to deal with are: directionality, multiplicity, and collection
|
|
behavior.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-unidirset">
|
|
<title>A unidirectional Set-based association</title>
|
|
|
|
<para>
|
|
We'll add a collection of events to the <literal>Person</literal> class. That way we can
|
|
easily navigate to the events for a particular person, without executing an explicit query -
|
|
by calling <literal>aPerson.getEvents()</literal>. We use a Java collection, a <literal>Set</literal>,
|
|
because the collection will not contain duplicate elements and the ordering is not relevant for us.
|
|
</para>
|
|
|
|
<para>
|
|
So far we designed a unidirectional, many-valued associations, implemented with a <literal>Set</literal>.
|
|
Let's write the code for this in the Java classes and then map it:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Person {
|
|
|
|
private Set events = new HashSet();
|
|
|
|
public Set getEvents() {
|
|
return events;
|
|
}
|
|
|
|
public void setEvents(Set events) {
|
|
this.events = events;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Before we map this association, think about the other side. Clearly, we could just keep this
|
|
unidirectional. Or, we could create another collection on the <literal>Event</literal>, if we
|
|
want to be able to navigate it bi-directional, i.e. <literal>anEvent.getParticipants()</literal>.
|
|
This is a design choice left to you, but what is clear from this discussion is the multiplicity
|
|
of the association: "many" valued on both sides, we call this a <emphasis>many-to-many</emphasis>
|
|
association. Hence, we use Hibernate's many-to-many mapping:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Person" table="PERSON">
|
|
<id name="id" column="PERSON_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="age"/>
|
|
<property name="firstname"/>
|
|
<property name="lastname"/>
|
|
|
|
<set name="events" table="PERSON_EVENT">
|
|
<key column="PERSON_ID"/>
|
|
<many-to-many column="EVENT_ID" class="Event"/>
|
|
</set>
|
|
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate supports all kinds of collection mappings, a <literal><set></literal> being most
|
|
common. For a many-to-many association (or <emphasis>n:m</emphasis> entity relationship), an
|
|
association table is needed. Each row in this table represents a link between a person and an event.
|
|
The table name is configured with the <literal>table</literal> attribute of the <literal>set</literal>
|
|
element. The identifier column name in the association, for the person's side, is defined with the
|
|
<literal><key></literal> element, the column name for the event's side with the
|
|
<literal>column</literal> attribute of the <literal><many-to-many></literal>. You also
|
|
have to tell Hibernate the class of the objects in your collection (correct: the class on the
|
|
other side of the collection of references).
|
|
</para>
|
|
|
|
<para>
|
|
The database schema for this mapping is therefore:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
_____________ __________________
|
|
| | | | _____________
|
|
| EVENTS | | PERSON_EVENT | | |
|
|
|_____________| |__________________| | PERSON |
|
|
| | | | |_____________|
|
|
| *EVENT_ID | <--> | *EVENT_ID | | |
|
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
|
|
| TITLE | |__________________| | AGE |
|
|
|_____________| | FIRSTNAME |
|
|
| LASTNAME |
|
|
|_____________|
|
|
]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-working">
|
|
<title>Working the association</title>
|
|
|
|
<para>
|
|
Let's bring some people and events together in a new method in <literal>EventManager</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private void addPersonToEvent(Long personId, Long eventId) {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Person aPerson = (Person) session.load(Person.class, personId);
|
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
|
|
|
aPerson.getEvents().add(anEvent);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
After loading a <literal>Person</literal> and an <literal>Event</literal>, simply
|
|
modify the collection using the normal collection methods. As you can see, there is no explicit call
|
|
to <literal>update()</literal> or <literal>save()</literal>, Hibernate automatically
|
|
detects the collection has been modified and needs to be saved. This is called <emphasis>automatic
|
|
dirty checking</emphasis>, and you can also try it by modifying the name or the date property of
|
|
any of your objects. As long as they are in <emphasis>persistent</emphasis> state, that is, bound
|
|
to a particular Hibernate <literal>Session</literal> (i.e. they have been just loaded or saved in
|
|
a unit of work), Hibernate monitors any changes and executes SQL in a write-behind fashion. The
|
|
process of synchronizing the memory state with the database, usually only at the end of a unit of
|
|
work, is called <emphasis>flushing</emphasis>.
|
|
</para>
|
|
|
|
<para>
|
|
You might of course load person and event in different units of work. Or you modify an object
|
|
outside of a <literal>Session</literal>, when it is not in persistent state (if it was persistent
|
|
before, we call this state <emphasis>detached</emphasis>). In (not very realistic) code, this might
|
|
look as follows:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[ private void addPersonToEvent(Long personId, Long eventId) {
|
|
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Person aPerson = (Person) session.load(Person.class, personId);
|
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
|
|
aPerson.getEvents().add(anEvent); // aPerson is detached
|
|
|
|
Session session2 = HibernateUtil.currentSession();
|
|
Transaction tx2 = session.beginTransaction();
|
|
|
|
session2.update(aPerson); // Reattachment of aPerson
|
|
|
|
tx2.commit();
|
|
HibernateUtil.closeSession();
|
|
}
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
The call to <literal>update</literal> makes a detached object persistent again, you could
|
|
say it binds it to a new unit of work, so any modifications you made to it while detached
|
|
can be saved to the database.
|
|
</para>
|
|
|
|
<para>
|
|
Well, this is not much use in our current situation, but it's an important concept you can
|
|
design into your own application. For now, complete this exercise by adding a new action
|
|
to the <literal>EventManager</literal>'s main method and call it from the command line. If
|
|
you need the identifiers of a person and an event - the <literal>save()</literal> method
|
|
returns it.
|
|
</para>
|
|
|
|
<para>
|
|
This was an example of an association between two equally important classes, two entities.
|
|
As mentioned earlier, there are other classes and types in a typical model, usually "less
|
|
important". Some you have already seen, like an <literal>int</literal> or a <literal>String</literal>.
|
|
We call these classes <emphasis>value types</emphasis>, and their instances <emphasis>depend</emphasis>
|
|
on a particular entity. Instances of these types don't have their own identity, nor are they
|
|
shared between entities (two persons don't reference the same <literal>firstname</literal>
|
|
object, even if they have the same first name). Of course, value types can not only be found in
|
|
the JDK (in fact, in a Hibernate application all JDK classes are considered value types), but
|
|
you can also write dependent classes yourself, <literal>Address</literal> or <literal>MonetaryAmount</literal>,
|
|
for example.
|
|
</para>
|
|
|
|
<para>
|
|
You can also design a collection of value types. This is conceptually very different from a
|
|
collection of references to other entities, but looks almost the same in Java.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-valuecollections">
|
|
<title>Collection of values</title>
|
|
|
|
<para>
|
|
We add a collection of value typed objects to the <literal>Person</literal> entity. We want to
|
|
store email addresses, so the type we use is <literal>String</literal>, and the collection is
|
|
again a <literal>Set</literal>:
|
|
</para>
|
|
<programlisting><![CDATA[private Set emailAddresses = new HashSet();
|
|
|
|
public Set getEmailAddresses() {
|
|
return emailAddresses;
|
|
}
|
|
|
|
public void setEmailAddresses(Set emailAddresses) {
|
|
this.emailAddresses = emailAddresses;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
The mapping of this <literal>Set</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
|
|
<key column="PERSON_ID"/>
|
|
<element type="string" column="EMAIL_ADDR"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
The difference compared with the earlier mapping is the <literal>element</literal> part, which tells Hibernate that the collection
|
|
does not contain references to another entity, but a collection of elements of type
|
|
<literal>String</literal> (the lowercase name tells you it's a Hibernate mapping type/converter).
|
|
Once again, the <literal>table</literal> attribute of the <literal>set</literal> element determines
|
|
the table name for the collection. The <literal>key</literal> element defines the foreign-key column
|
|
name in the collection table. The <literal>column</literal> attribute in the <literal>element</literal>
|
|
element defines the column name where the <literal>String</literal> values will actually be stored.
|
|
</para>
|
|
|
|
<para>
|
|
Have a look at the updated schema:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
_____________ __________________
|
|
| | | | _____________
|
|
| EVENTS | | PERSON_EVENT | | | ___________________
|
|
|_____________| |__________________| | PERSON | | |
|
|
| | | | |_____________| | PERSON_EMAIL_ADDR |
|
|
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
|
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
|
|
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|
|
|_____________| | FIRSTNAME | |___________________|
|
|
| LASTNAME |
|
|
|_____________|
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
You can see that the primary key of the collection table is in fact a composite key,
|
|
using both columns. This also implies that there can't be duplicate email addresses
|
|
per person, which is exactly the semantics we need for a set in Java.
|
|
</para>
|
|
|
|
<para>
|
|
You can now try and add elements to this collection, just like we did before by
|
|
linking persons and events. It's the same code in Java.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-bidirectional">
|
|
<title>Bi-directional associations</title>
|
|
|
|
<para>
|
|
Next we are going to map a bi-directional association - making the association between
|
|
person and event work from both sides in Java. Of course, the database schema doesn't
|
|
change, we still have many-to-many multiplicity. A relational database is more flexible
|
|
than a network programming language, so it doesn't need anything like a navigation
|
|
direction - data can be viewed and retrieved in any possible way.
|
|
</para>
|
|
|
|
<para>
|
|
First, add a collection of participants to the <literal>Event</literal> Event class:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private Set participants = new HashSet();
|
|
|
|
public Set getParticipants() {
|
|
return participants;
|
|
}
|
|
|
|
public void setParticipants(Set participants) {
|
|
this.participants = participants;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Now map this side of the association too, in <literal>Event.hbm.xml</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="participants" table="PERSON_EVENT" inverse="true">
|
|
<key column="EVENT_ID"/>
|
|
<many-to-many column="PERSON_ID" class="Person"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
As you see, these are normal <literal>set</literal> mappings in both mapping documents.
|
|
Notice that the column names in <literal>key</literal> and <literal>many-to-many</literal> are
|
|
swapped in both mapping documents. The most important addition here is the
|
|
<literal>inverse="true"</literal> attribute in the <literal>set</literal> element of the
|
|
<literal>Event</literal>'s collection mapping.
|
|
</para>
|
|
|
|
<para>
|
|
What this means is that Hibernate should take the other side - the <literal>Person</literal> class -
|
|
when it needs to find out information about the link between the two. This will be a lot easier to
|
|
understand once you see how the bi-directional link between our two entities is created .
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-usingbidir">
|
|
<title>Working bi-directional links</title>
|
|
|
|
<para>
|
|
First, keep in mind that Hibernate does not affect normal Java semantics. How did we create a
|
|
link between a <literal>Person</literal> and an <literal>Event</literal> in the unidirectional
|
|
example? We added an instance of <literal>Event</literal> to the collection of event references,
|
|
of an instance of <literal>Person</literal>. So, obviously, if we want to make this link working
|
|
bi-directional, we have to do the same on the other side - adding a <literal>Person</literal>
|
|
reference to the collection in an <literal>Event</literal>. This "setting the link on both sides"
|
|
is absolutely necessary and you should never forget doing it.
|
|
</para>
|
|
|
|
<para>
|
|
Many developers program defensive and create a link management methods to
|
|
correctly set both sides, e.g. in <literal>Person</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[protected Set getEvents() {
|
|
return events;
|
|
}
|
|
|
|
protected void setEvents(Set events) {
|
|
this.events = events;
|
|
}
|
|
|
|
public void addToEvent(Event event) {
|
|
this.getEvents().add(event);
|
|
event.getParticipants().add(this);
|
|
}
|
|
|
|
public void removeFromEvent(Event event) {
|
|
this.getEvents().remove(event);
|
|
event.getParticipants().remove(this);
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Notice that the get and set methods for the collection are now protected - this allows classes in the
|
|
same package and subclasses to still access the methods, but prevents everybody else from messing
|
|
with the collections directly (well, almost). You should probably do the same with the collection
|
|
on the other side.
|
|
</para>
|
|
|
|
<para>
|
|
What about the <literal>inverse</literal> mapping attribute? For you, and for Java, a bi-directional
|
|
link is simply a matter of setting the references on both sides correctly. Hibernate however doesn't
|
|
have enough information to correctly arrange SQL <literal>INSERT</literal> and <literal>UPDATE</literal>
|
|
statements (to avoid constraint violations), and needs some help to handle bi-directional associations
|
|
properly. Making one side of the association <literal>inverse</literal> tells Hibernate to basically
|
|
ignore it, to consider it a <emphasis>mirror</emphasis> of the other side. That's all that is necessary
|
|
for Hibernate to work out all of the issues when transformation a directional navigation model to
|
|
a SQL database schema. The rules you have to remember are straightforward: All bi-directional associations
|
|
need one side as <literal>inverse</literal>. In a one-to-many association it has to be the many-side,
|
|
in many-to-many association you can pick either side, there is no difference.
|
|
</para>
|
|
<!--
|
|
<para>
|
|
In the next section we integrate Hibernate with Tomcat and WebWork - the <literal>EventManager</literal>
|
|
doesn't scale anymore with our growing application.
|
|
</para>
|
|
-->
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-summary">
|
|
<title>Summary</title>
|
|
|
|
<para>
|
|
This tutorial covered the basics of writing a simple standalone Hibernate application.
|
|
</para>
|
|
|
|
<para>
|
|
If you already feel confident with Hibernate, continue browsing through the reference
|
|
documentation table of contents for topics you find interesting - most asked are
|
|
transactional processing (<xref linkend="transactions"/>), fetch
|
|
performance (<xref linkend="performance"/>), or the usage of the API (<xref linkend="objectstate"/>)
|
|
and the query features (<xref linkend="objectstate-querying"/>).
|
|
</para>
|
|
|
|
<para>
|
|
Don't forget to check the Hibernate website for more (specialized) tutorials.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |