From 43cedb8b5200e986c776706760076d896976bc9f Mon Sep 17 00:00:00 2001 From: Christian Bauer Date: Tue, 3 May 2005 03:31:55 +0000 Subject: [PATCH] Completed Pt1 git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@6649 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- reference/en/modules/tutorial.xml | 2009 ++++++----------------------- 1 file changed, 362 insertions(+), 1647 deletions(-) diff --git a/reference/en/modules/tutorial.xml b/reference/en/modules/tutorial.xml index 30b751c8ad..2618ddbc81 100644 --- a/reference/en/modules/tutorial.xml +++ b/reference/en/modules/tutorial.xml @@ -7,74 +7,70 @@ 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 further in small steps, until we reach a full fledged J2EE application using - Tomcat and WebWork. + it in small steps to a full fledged J2EE application using Tomcat and WebWork. - This tutorial is intended for new users of Hibernate, but with advanced Java knowledge. - It you work on it and find it difficult to understand, please let us know. + This tutorial is intended for new users of Hibernate but requires Java and + SQL knowledge. It is based on a tutorial by Michael Goegl, http://www.goegl.de/ - - This tutorial was written by Michael Gloegl, he also maintains an online version - at http://www.gloegl.de/5.html - please direct comments and corrections to - michael@hibernate.org. If you need assistance with Hibernate, use the - Hibernate users forum at http://forum.hibernate.org/ - - Part 1 - The first Hibernate Application + - First we'll create a simple console-based Hibernate Application. We use an + 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. - Let's assume we want to have a small application that can store events we want to - attend and information about the hosts of these events. + 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. - The first thing we do is set up our development directory and put all the + 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's download page. Extract the archive and place all libraries - found in /lib/ into into the /lib/ directory of your new development workdir. - It should look like this: + Hibernate website. Extract the package and place all required libraries + found in /lib into into the /lib directory + of your new development working directory. It should look like this: ++lib + antlr.jar + cglib-full.jar + asm.jar + asm-attrs.jars + commons-collections.jar + commons-logging.jar + hibernate3.jar + jta.jar + dom4j.jar + log4j.jar ]]> This is the minimum set of required libraries (note that we also copied - hibernate3.jar, the main archive) for Hibernate. See the Readme.txt file - in the /lib/ directory of the Hibernate distribution for more information - about required and optional third-party libraries. + hibernate3.jar, the main archive) for Hibernate. See the README.txt file + in the lib/ 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.) + + + + Next we create a class that represents the event we want to store in database. The first class - Next we create a class that represents the events we want to store in - the database. This is just a simple JavaBean class with some simple - properties. Let's have a look at the code: + Our first persistent class is a simple JavaBean class with some properties: - - The id property holds a unique identifier value for a particular. - Event instance - all our persistent entity classes (there are less important dependent - classes as well) will need such an identifer property if we want to use the full feature - set of Hibernate. In fact, most applications (esp. web applications) need to identify - particular objects, 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. + The id 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 identifer property if we want to use the full feature set of Hibernate. In fact, + most applications (esp. web applications) need to distinquish 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. - The no-argument constructor is a requirement for all persistent classes as Hibernate - has to instanciate objects for you, using Java Reflection. The constructor can be + 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. + efficient data retrieval without bytecode instrumentation. - We place this Java source file in a directory called src in our + Place this Java source file in a directory called src in the development folder. The directory should now look like this: - - +src - +de - +gloegl - +road2hibernate - Event.java]]> + ++src + Event.java]]> - In the next step, we tell Hibernate about this persisten class. + In the next step, we tell Hibernate about this persistent class. @@ -159,7 +152,7 @@ public class Event { The mapping file - Hibernate needs to know how to load and store objects of our entity class. + 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. @@ -182,65 +175,66 @@ public class Event { 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 see - the defaults, as well as some comments. Also note that Hibernate will not - load the DTD file from the web, as the URL might suggest, but first looks - into the classpath of your application. The DTD file is included in hibernate3.jar + 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 hibernate3.jar as well as in the src/ directory of the Hibernate distribution. - We will ommit the DTD declaration in future examples to shorten the code. + We will ommit the DTD declaration in future examples to shorten the code. It is + of course not optional. - Between the two hibernate-mapping tags, we include a + Between the two hibernate-mapping tags, include a class 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 our SQL database: + such a mapping, to a table in the SQL database: - - - + + + ]]> - What we have done so far is telling Hibernate how to persist and load objects - of our class Event to the table EVENTS. Now - we continue with a mapping of the unique identifier property. In addition, as we - don't want to care about handling this identifier, we configure Hibernate's - identifier generation strategy: + So far we told Hibernate how to persist and load object of class Event + to the table EVENTS, 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: - - - - - + + + + + ]]> The id element is the declaration of the identifer property, name="id" declares the name of the Java property - - Hibernate will use getId() and setId() to - access it. The column attribute tells Hibernate which column of the + Hibernate will use the getter and setter methods to access the property. + The column attribute tells Hibernate which column of the EVENTS table we use for this primary key. The nested - generator element specifies the identifier generation strategy - - in this case we use increment, which is a very simple in-memory - number generation method useful mostly for testing (and tutorials). Hibernate also + generator element specifies the identifier generation strategy, + in this case we used increment, 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. + identifiers (or any strategy you have written an extension for). - Finally we have to include declarations for the persistent properties in + 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: @@ -248,13 +242,13 @@ public class Event { - - - - - - + + + + + + + ]]> @@ -274,36 +268,34 @@ public class Event { - The next interesting thing is that the title property lacks + The next interesting thing is that the title mapping also lacks a type attribute. The types we declare and use in the mapping - files are not, like you might expect, Java data types. They are also not SQL + files are not, as you might expect, Java data types. They are also not SQL database types. These types are so called Hibernate mapping types, 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 mapping attribute is not present. 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 date property. Hibernate can't know if - the property will map to a SQL date, timestamp + the type 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 date property. Hibernate can't + know if the property will map to a SQL date, timestamp or time column. We declare that we want to preserve full date and time information by mapping the property with a timestamp. This mapping file should be saved as Event.hbm.xml, right in - the directory where our Event Java source class is located. + the directory next to the Event Java class source file. The naming of mapping files can be arbitrary, however the hbm.xml - became convention in the Hibernate developer community. The directory structure + suffix became convention in the Hibernate developer community. The directory structure should now look like this: - - +src - +de - +gloegl - +road2hibernate - Event.java - Event.hbm.xml]]> + ++src + Event.java + Event.hbm.xml]]> We continue with the main configuration of Hibernate. @@ -315,34 +307,32 @@ public class Event { Hibernate configuration - As we now have the persistent class and the 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 Database, can be downloaded from - the HSQL DB website. Actually, you only need the hsqldb.jar - from the /lib/ directory of the download. Place this file in the lib directory - of the development folder. + 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 hsqldb.jar + from this download. Place this file in the lib/ directory of the + development folder. - Create a directory called data in the development - directory - this is where HSQL DB will store its data files. + Create a directory called data in the root of the development directory - + this is where HSQL DB will store its data files. - Hibernate will connect to this database, so it needs connection information. The - connections will be made through a JDBC connection pool, which we also have to - configure. The Hibernate distribution contains several open source connection pools, - but we have decided to use the buil-in connection pool for this tutorial. Note that + 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. - We can use a simple hibernate.properties file, a slightly - more sophisticated hibernate.cfg.xml file, or even complete - programmatic setup to configure Hibernate. Most users prefer the XML configuration - file: + For Hibernate's configuration, we can use a simple hibernate.properties file, a + slightly more sophisticated hibernate.cfg.xml file, or even complete + programmatic setup. Most users prefer the XML configuration file: @@ -356,7 +346,7 @@ public class Event { org.hsqldb.jdbcDriver - jdbc:hsqldb:data/test + jdbc:hsqldb:data/tutorial sa @@ -372,7 +362,7 @@ public class Event { create - + @@ -383,7 +373,7 @@ public class Event { Hibernate's SessionFactory - a global factory responsible for a particular database. If you have several databases, use several <session-factory> configurations, usually in - several configuration files. + several configuration files (for easier startup). @@ -392,61 +382,126 @@ public class Event { element specifies the particular SQL variant Hibernate generates. The hbm2ddl.auto 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 for tuning, through - an Ant task. Finally, we point Hibernate to our mapping file(s). - - - - - - Building - - - For convenience, we create a batch file in our development directory that - contains all commands necessary for compilation of the application. Under - Windows, this would look like this: + off (by removing the config option) or redirected to a file with the help of + the SchemaExport Ant task. Finally, we add the mapping file(s) + for persistent classes. - - Place this file called build.bat in the development directory - and create the build subdirectory. If you are using Linux, you - can create an equivalent shell script. We'll migrate to an Ant build later. + 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 + hibernate.cfg.xml in the root of the classpath, on startup. - - Setup and helpers + + Building with Ant + + + We'll now build the tutorial with Ant. You will need to have Ant installed - get + it from the Ant download page. + How to install Ant will not be covered here. Please refer to the + Ant manual. After you + have installed Ant, we can start to create the buildfile. It will be called + build.xml and placed directly in the development directory. + + + + Fixing Ant + + 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 ANT_HOME/lib or remove the + ANT_HOME/lib/ant-junit.jar plugin stub. + + + + + A basic build file looks like this: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + This will tell Ant to add all files in the lib directory ending with .jar + 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: + + + 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 ]]> + + + + + Startup and helpers It's time to load and store some Event objects, but first we have to complete the setup with some infrastructure code. We have to startup - Hibernate (this means creating a SessoinFactory and store it - somewhere for easy access) and we will need some convenience methods for accessing - Hibernate Session's. A particular Session - represents a single-threaded unit of work, the SessionFactory - is a thread-safe global object, instantiated once. + Hibernate. This startup includes building a global SessionFactory + object and to store it somewhere for easy access in application code. + A SessionFactory can open up new Session's. + A Session represents a single-threaded unit of work, the + SessionFactory is a thread-safe global object, instantiated once. - We'll create a HibernateUtil helper class that takes care - of startup and makes Session handling convenient. This helper - classes will use the so called ThreadLocal Session pattern to - keep the current unit of work associated with the current thread. Let\s have a look - at it: + We'll create a HibernateUtil helper class which takes care + of startup and makes Session handling convenient. The so called + ThreadLocal Session pattern is useful here, we keep the current + unit of work associated with the current thread. Let's have a look at the implementation: - This class does not only produce the global SessionFactory in - it's static initializer (called once by the JVM when the class is loaded), but also - has a ThreadLocal variable that holds the + its static initializer (called once by the JVM when the class is loaded), but also + has a ThreadLocal variable to hold the Session for the current thread. No matter when you call HibernateUtil.currentSession(), it will always return the same Hibernate unit of work in the same thread. A call to HibernateUtil.closeSession() @@ -494,7 +549,12 @@ public class HibernateUtil { Make sure you understand the Java concept of a thread-local variables before you use this helper. A more powerful HibernateUtil helper can be found in CaveatEmptor on http://caveatemptor.hibernate.org/ - as well as - in the book "Hibernate in Action". + in the book "Hibernate in Action". Note that this class is not necessary if you deploy + Hibernate in a J2EE application server: a Session will be + automatically bound to the current JTA transaction and you can look up the + SessionFactory through JNDI. If you use JBoss AS, Hibernate can + be deployed as a managed system service and will automatically bind the + SessionFactory to a JNDI name. @@ -503,81 +563,31 @@ public class HibernateUtil { ++lib + ++src + Event.java + Event.hbm.xml + HibernateUtil.java + hibernate.cfg.xml ++data +build.xml]]> - Now compile everything by calling build.bat in the development - directory. Run the application by executin in the development directory (all in one line): - - - - - - This should produce the following output: - - - - - - Let's place this line in a batch file, to save time. Put it in the development directory - as run.bat and add %1 %2 %3 %4 %5 at the end of the line. + 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 Lo4j: copy log4j.properties + from the Hibernate distribution (it's in the etc/ directory) to + your src directory, next to hibernate.cfg.xml. + 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. - We didn't see any errors in the run output but we still want to see what Hibernate is - doing during startup and want to get rid of the log4j warnings. We have to configure - log4j to solve both issues. Put a file called log4j.properties into - your src directory, next to hibernate.cfg.xml: + The tutorial infrastructure is complete - and we are ready to do some real work with + Hibernate. - - - - In addition, the following line has to be added to build.bat: - - - - - - This configuration tells log4j to write all INFO level log output to the console. - - - - Recompile the application by calling build.bat again, and rerun - it - you should now see detailed Hibernate startup info. Have a look and verify that - your Hibernate configuration options are all set correctly. - - @@ -588,1453 +598,158 @@ log4j.appender.A1.layout.ConversionPattern=%-5p - %m%n]]> EventManager class with a main() method: - We read some arguments from the command line, and if the first argument is - "store", we take the second argument as the title of the event, create a new - Date and pass both to the store method: + "store", we create and store a new Event: - We create a new Event object, and hand it over to Hibernate. - Hibernate now takes care of creating the SQL, and sending INSERTs - to the database. Let's spent a second on the Session and - Transaction-handling code. TODO: continue here + Hibernate now takes care of the SQL and executes INSERTs + on the database. Let's have a look at the Session and + Transaction-handling code before we run this. - - - - - The method hsqlCleanup listed below performs some necessary shutdown code telling HsqlDB to remove all its lock-files and flush its logs. This is not a direct hibernate requirement, so this would not be necessary when using a "real" database. + A Session is a single unit of work. You might be surprised that we + have an additional API, Transaction. 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. + Seperating 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 Application Transaction, usually encapsulating + several short database transactions. For now we'll keep things simple and assume a + one-to-one granularity between a Session and Transaction. - + What does Transaction.begin() and commit() do? Where + is the rollback() in case something goes wrong? The Hibernate + Transaction API is actually optional, but we use it for convenience + and portability. If you'd handle the database transaction yourself (e.g. by calling + session.connection.commit()), you'd bind the code to a particular + deployment environment, in this direct unmanaged JDBC. By setting the factory for + Transaction in your Hibernate configuration you can deploy your + persistence layer anywhere. Have a look at for more information + about transaction handling and demarcation. We also skipped any error handling and + rollback in this example. + + + + To run this first routine we have to add a callable target to the Ant build file: + + + + + + + +]]> + + + The value of the action argument is set on the command line when + calling the target: + + + ant run -Daction=store]]> + + + 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: + + + + + + This is the INSERT 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 log4j.properties. + + + + Now we'd like to list stored events as well, so we add an option to the main method: + + + + - Please not that all the transaction and session handling code in these examples is extremely unclean, mainly for shortness. Please don't use that in a production app. For a more detailed explanation on how to handle transactions properly see . - - - If we now run the application with run.bat store Party an Event object will be created an persisted to the database. - - - But now we want to list our stored events, so we modify the main method some more: - - - - When the first argument is "list", we call listEvents() and print all Events contained in the returned list. listEvents() is where the interesting stuff happens: + We also add a new listEvents() method: + - - What we do here is using a HQL (Hibernate Query Language) query to load all existing Event objects from the database. Hibernate will generate the appropriate SQL, send it to the database and populate Event objects with the data. You can create more complex querys with HQL of course, which we will see in later chapters. - - - So in this chapter we learned how to setup Hibernate, how to create a mapping for our classes, and how to store and retrieve objects using Hibernate. - - - That's it for the first chapter, in the next part we will replace our ugly build.bat with an ant-based build system. - - - - - Part 2 - Building with Ant - - In this short chapter we will replace the ugly build.bat file we created with a nice little Ant build file. You will need to have Ant installed - get it from the Ant download page. How to install Ant will not be covered here. Please refer to the Ant manual. After you have installed Ant, we can start to create the buildfile. It will be called build.xml and placed directly in the development directory and will replace the build.bat (which you can delete). - - - A basic build file - - A basic build file looks like this: - - - - - - - - ]]> - - The project tags surround the whole buildfile. There are two attributes, the name attribute which gives a name for the project being built, and the default attribute which specifies the default target which will be run if we launch Ant without specifying a target. - - - Inside of the project tags, we have to give at least one target block, where we can tell Ant what to do - in this case, ant will do just nothing. You can now run the build by running ant on the command line inside the development directory. - - - You should get an output like this: - - - - This tells us Ant did run successfully and which build file was used. The default target was run, which is why the compile: part of the output shows us the compile target was executed. We can however give Ant an explicit target to run by calling ant compile from the command line, which will run the compile target. - - - So now we want Ant to actually compile our classes. So we insert the javac task inside the target elements: - - - - - - - - ]]> - - This will tell Ant to launch the java compiler and compile everything it can find under the the src directory and place the generated class files in the bin directory. However if we now run Ant, we will get a lot of compile errors, because the compiler can not find the hibernate classes. So we have to tell the compiler about the classpath to use, just as we did in the old build.bat: - - - - - - - - - - - - - - - ]]> - - This will tell Ant to find all files in the lib directory with .jar as file ending and add them to the classpath used for compilation. If you now run Ant, you should get an output like this: - - ant -Buildfile: build.xml - -compile: - [javac] Compiling 2 source files to C:\hibernateTutorial\part2\bin - -BUILD SUCCESSFUL -Total time: 1 second ]]> - - - Dependant targets - - Great, so now we got ant to compile our two java files. This however still leaves the log4j.properties and the mapping file, which are not copied to the bin directory. We will take care of this now by adding an additional target: - - - - - - - - - - - - - - - - - - - - - - - ]]> - - So this tells ant when it executes the copy-resources target to copy everything it can find in the src directory or in any directories below to the bin directory but exclude all java files anywhere under the src directory (this is what the ** in front of the / means). - - - So if you run Ant now, you will see ... actually nothing. Ant will execute the compile target and not touch our copy-resources target. So what we need to do now is to tell ant it has to execute copy-resources before the compile target - this is what the depends attribute of the target element is for: - - - - - - - - - - - - - - - - - - - - - - - ]]> - - So if you now execute ant you should see output like this: - - - - So Ant has now executed both targets and copied our resources over to the bin directory. You will notice that ant does not print anything under the compile target. Ant notices that no source files have changed and does not compile them again. The same will happen for copy-resources - ant will not copy our files again unless we change them, or remove them from the bin directory. - - - - Using Properties - - So now we have a nice little build script in place. We could well go on from here. You will notice however that our directory names are spread all over the build file. Should we ever want to change them, we would have to change them all over the build file. We will solve this problem by using Ant property declarations: - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - So we can define our properties using the property tag, and can insert them anywhere in the build file using the name we declared for the property surrounded with ${}. Notice the ${basedir} property we use in the property declarations - this is a property predefined by Ant, which contains the path of the directory where Ant is executed. - - - In the next chapter, we will create associations between our classes using java collections. - - - - - Part 3 - Mapping Associations - - As we now have mapped a single object, we are now going to add various object associations. For a starter, we will add users to our application, and store a list of participating users with every event. In addition, we will give each user the possibility to watch various events for updates. Every user will have the standard personal data, including a list of email addresses. - - - Mapping the User class - - For the beginning, our User class will be very simple: - - - - And the mapping in User.hbm.xml: - - - - - - - - - - - - - - - - -]]> - - The hibernate.cfg.xml needs to be adjusted as well to add the new resource: - - - - - - - - org.hsqldb.jdbcDriver - jdbc:hsqldb:data/test - sa - - org.hibernate.dialect.HSQLDialect - true - - org.hibernate.transaction.JDBCTransactionFactory - - - org.hibernate.cache.HashtableCacheProvider - - update - - - - - - -]]> - - - An unidirectional Set-based association - - So far this is only basic hibernate usage. But now we will add the collection of favorite events to the User class. For this we can use a simple java collection - a Set in this case, because the collection will not contain duplicate elements and the ordering is not relevant for us. - - - So our User class now looks like this: - - - - Now we need to tell hibernate about the association, so we adjust the User.hbm.xmlmapping document: - - - - - - - - - - - - - - - - - - - - - ]]> - - As you can see, we tell Hibernate about the set property called favouriteEvents. The set element tells Hibernate that the collection property is a Set. We have to consider what kind of association we have: Every User my have multiple favorite events, but every Event may be a favorite of multiple users. So we have a many-to-many association here, which we tell Hibernate using the many-to-many tag. For many to many associations, we need an association table where hibernate can store the associations. The table name can be configured using the table attribute of the set element. The association table needs at least two columns, one for every side of the association. The column name for the User side can be configured using the key element. The column name for the Event side is configured using the column attribute of the many-to-many element. - - - So the relational model used by hibernate now looks like this: - - | *EVENT_UID | | | - | DATE | | *USER_UID | <--> | *UID | - | EVENTTITLE | |__________________| | AGE | - |_____________| | FIRSTNAME | - | LASTNAME | - |_____________| - ]]> - - - Modifying the association - - As we now have mapped the association, modifying it in EventManager is very easy: - - - - After loading an User and an Event with Hibernate, we can simply modify the collection using the normal collection methods. As you can see, there is no explicit call to session.update() or session.save(), Hibernate automatically detects the collection has been modified and needs to be saved. - - - Sometimes however, we will have a User or an Event loaded in a different session. This is of course possible to: - - - - This time, we need an explicit call to update - Hibernate can't know if the object actually changed since it was loaded in the previous session. So if we have an object from an earlier session, we must update it explicitly. If the object gets changed during session lifecycle we can rely on Hibernates automatic dirty checking. - - - Since Hibernate 2.1 there is a third way - the object can be reassociated with the new session using session.lock(object, LockMode.NONE): - - + + return result; +}]]> + + + What we do here is use an HQL (Hibernate Query Language) query to load all existing + Event objects from the database. Hibernate will generate the + appropriate SQL, send it to the database and populate Event objects + with the data. You can create more complex queries with HQL, of course. + + + + If you now call Ant with -Daction=list, 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 hbm2ddl.auto 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 store action a few + times. Automatic schema generation and export is mostly useful in unit testing. + + - - Collections of Values - - Often you will want to map collections of simple value types - like a collections of Integers or a collection of Strings. We will do this for our User class with a collection of Strings representing email addresses. So we add another Set to our class: - - - - Next we will add the mapping of the Set to our User.hbm.xmlmapping document: - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - As you can see, the new set mapping looks a lot like the last one. The difference is the element part, which tells Hibernate that the collection does not contain an association with a mapped class, but a collection of elements of type String. Once again, the table attribute of the set element determines the table name. The key element determines the column name in the USER_EMAILS table which establishes the relation to the USERS table. The column attribute in the element element determines the column name where the String values will be actually stored. - - - So now our relational model looks like this: - - | *EVENT_UID | | | | *ID | - | DATE | | *USER_UID | <--> | *UID | <--> | USER_UID | - | EVENTTITLE | |__________________| | AGE | | EMAIL | - |_____________| | FIRSTNAME | |_____________| - | LASTNAME | - |_____________| - ]]> - - - Using value collections - Using value collections works the same way as we have already seen: - - - As you see, you can use the mapped collection just like every java collection. Hibernates automatic dirty detection will do the rest of the job. For objects from another session - or disconnected objects, as we will call them from now - the same as above aplies. Explicitly update them, or reassociate them before updating using session.lock(object, LockMode.NONE). - - - - Bidirectional associations using Sets - - Next we are going to map a bidirectional association - the User class will contain a list of events where the user participates, and the Event class will contain a list of participating users. So first we adjust our classes: - - - - The mapping for a bidirectional association looks very much like a unidirectional one, except the set elements are mapped for both classes: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - As you see, this are normal set mappings in both mapping documents. Notice that the column names in key and many-to-many are swapped in both mapping documents. The most important addition here is the inverse="true" attribute in the set element of the User mapping. - - - What this means is the other side - the Event class - will manage the relation. So when only the Set in the User class is changed, this will not get perstisted. Also when using explicit update for detatched objects, you need to update the one not marked as inverse. Let's see an example: - - - - Using bidirectional mappings - - At first it is important to know that we are still responsible for keeping our associations properly set up on the java side - that means if we add an Event to the eventsJoined Set of an User object, we also have to add this User object to the participatingUsers Set in the Event object. So we will add some convenience methods to the Event class: - - - - Notice that the get and set methods for participatingUsers are now protected - this allows classes in the same package and subclasses to still access the methods, but prevents everybody else from messing around with the collections directly. We should do the same to the getEventsJoined() and setEventsJoined() methods in the User class. - - - Now using the association in EventManager is very easy: - - - - In the next chapter we will integrate Hibernate with Tomcat and WebWork to create a better test environment - as you will notice when you look at the code, the EventManager class is really ugly now. - - - - Part 4 - Tomcat and WebWork - - In this chapter we will finally get rid of our ugly command-line-based test application and integrate Tomcat, WebWork and Hibernate to create a little web-based application. - - - The first step for you is to install Tomcat - you can download it here. You can find documentation and install instructions for Tomcat here. - - - Restructuring the development directory - At first, we will restructure our working directory, to keep the parts of our application nicely separated: - - - We moved our classes and mappings to the data subdirectory of road2hibernate. Of course we need to adjust our package declaration in the java files and the classnames in the mapping files. - - - In addition, we created various new directories: - - - - - src/de/gloegl/road2hibernate/actions will contain the source code for our WebWork actions. - - - - - config will contain the config files which will later be placed in WEB-INF/config of our web app. - - - - - static-web will contain all static content of our application, like html and image files. - - - - - views will contain our view files which contain the html later displayed to the user. - - - - - As we will use WebWork for our application and Velocity as template engine for the views, you will need to get WebWork from here. You will need the webwork-2.1.7.jar from the WebWork download, and all jar files in the lib/core folder of the WebWork distribution. Place all of them into the lib directory of the development dir. - - - - Configuring WebWork - - Now we will configure our application to use WebWork. At first we need a web.xml which we will place in the config directory: - - - - Eventmanager - - - webwork - - com.opensymphony.webwork.dispatcher.ServletDispatcher - - 1 - - - - velocity - - com.opensymphony.webwork.views.velocity.WebWorkVelocityServlet - - 10 - - - - webwork - *.action - - - - velocity - *.vm - - - - index.html - - ]]> - - This files contains all the servlets and servlet mappings which WebWork needs to run. - - - In addition, we will need a xwork.xml file, which is placed directly in the src dir: - - - - - - - - - - - ]]> - - This file contains only the declaration of the packages where WebWork will look for its actions. - - - - Updating the build process - - Now we will make our build process place all the files together and generate the appropriate structure for the web application. We update our build.xml: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ... ]]> - - Running this target will create a directory called war, which will contain the web application just like it will later be structured in the war file. After running this target, the war directory will look like this: - - ]]> - - Now we will add Tomcat specific tasks to our build file, so we can directly install our application into Tomcat using Ant. You need to adjust the example to your environment. Modify the build.xml again: - - - - .... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ... -]]> - - So now startup Tomcat and run ant install from the commandline - this will install the application to Tomcat. Now you should be able to access the eventmanager application by pointing your browser to http://localhost:8080/eventmanager/ - however you will only get a directory listing produced by Tomcat. We will now have to create some content for the application to display. - - - - A first WebWork action - - Our first WebWork action will be very simple - it will do nothing so far but forwarding to a view. We create /src/de/gloegl/road2hibernate/actions/EventList.java: - - - - The class extends ActionSupport but does not override any methods. So this will rely on the default behavior of ActionSupport: The action will forward to the view defined as the SUCCESS view. This is what we still have to do in the xwork.xml file: - - - - - - - - - - - /WEB-INF/views/eventlist.vm - - - ]]> - - This defines an action called eventlist which will be handled by our EventList class. In addition, the success view gets defined. We now have to write the eventlist.vm file, which will be placed in the views folder - Ant will copy it to the correct location. - - - - Event List - - -

Successful

- - ]]>
- - As you see, this is just a simple HTML file - we will modify it to include dynamic content later on. Now run ant reload. Now you should be able to test the action by pointing your browser to http://localhost:8080/eventmanager/eventlist.action - you should see the "Success" headline in your browser. - -
- - The "Open Session in View" pattern - - When using such features as lazy loading, Hibernate needs an open session. To do this easily in a web application we will use a simple pattern. We will create a servlet filter which will manage our session and close it at the end of the web request. Also we improve our HibernateUtil class to include transaction handling per-thread: - - - - We can now use this methods to do all Session and Transaction handling in a Servlet Filter called SessionManager: - - - - Let's look at what is done here: The HibernateUtil class will store a Hibernate session in a ThreadLocal variable. A ThreadLocal will contain an instance of a variable once for every thread. So if there are two parallel Threads executing, the ThreadLocal will contain two session. In SessionManager, the doFilter method will be invoked on every request. It will let the request go on by calling chain.doFilter(). After the request processing finished and chain.doFilter() returns, it retrieves the Session for the current thread from the ThreadLocal and closes it. It also commits the transaction - if the transaction was already committed or rolled back, HibernateUtil will simply do nothing. - - - Our Actions can invoke the static currentSession() method of the HibernateUtil class to get a Session - if it is invoked multiple times during one thread, the same session will be used every time. - - - The HibernateUtil.rollbackTransaction() method can be invoked by our Actions to have the transaction rolled back. - - - Also in the destroy() method of the filter, we once again do our hsqldb-housekeeping work. - - - We have to modify the classpath in our build.xml compile target to get this to work: - - - - - - - - - - - - - - ]]> - - Finally, we have to configure the filter in the web.xml: - - - - - Eventmanager - - - sessionmanager - de.gloegl.road2hibernate.util.SessionManager - - - - sessionmanager - action - - - - action - webwork.dispatcher.ServletDispatcher - 1 - - - - velocity - webwork.view.velocity.WebWorkVelocityServlet - - - - action - *.action - - - - velocity - *.vm - - - - index.html - -]]> - - - Accessing Hibernate from the action - - So now we have set up our supporting framework, we can finally start to use Hibernate for loading data in our action: - - - - We simply retrieve the session from our HibernateUtil, and load the list of Events using session.find(). Then we assign it to an attribute of our action. Now we can use this attribute from our eventlist.vmview: - - - - Event List - - -

Events

-
    - #foreach( $event in $events ) -
  • $event.title
  • - #end -
- -]]>
- - Using the velocity template language, we simply iterate over the events, and print their titles. Now that our infrastructure is in place, you see how easy it is to create actions and views. If you now call http://localhost:8080/eventmanager/eventlist.action, you will see most likely nothing - because we have no events in the database. So we will create another action to create events. - - - But at first we will finally create an index.html file, where we link our actions - place it in the static-web subdirectory of the project: - - - - Hibernate Event Manager - - - -

Hibernate Event Manager

- - - -]]>
- - Next, we will create the NewEvent action: - - - - As you see, this action has two doXXX() methods. The reason is we will use WebWorks ability to invoke different methods of the action class. Lets have a look at the xwork.xml first: - - - - - - - - - - - - /WEB-INF/views/eventlist.vm - - - - index.html - /WEB-INF/views/newEventForm.vm - - -]]> - - So we have now a new action called newevent. This action has two results, the success result (which is returned by the execute() method) and the input result (which is returned by the doEnter() method). If you look at the index.html you will notice the link to "newevent!enter.action" - this tells WebWork to invoke the enter() method in the action. The submit-target of our form however will just link to "newevent.action", invoking the default execute() method. - - - Finally we need to code the newEventForm.vm view file we specified, again in the views directory: - - - - Event List - - -

New Event

-
- - - - - - - - -
Title:
-
- -]]>
- - If you now go to http://localhost:8080/eventmanager now, you should get an index page from where you can create new Events and list them. - - - Thats it for this tutorial, if you want to dive in deeper into Hibernate, read on for the reference manual. - -
- -
- \ No newline at end of file