SVN layout migration for core/trunk
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@11723 1b8cb986-b30d-0410-93ca-fae66ebed9b2
|
@ -0,0 +1,176 @@
|
||||||
|
THE HIBERNATE DOCUMENTATION
|
||||||
|
christian@hibernate.org
|
||||||
|
|
||||||
|
COPYRIGHT NOTICE: This documentation system and all its source files are
|
||||||
|
licensed under the GNU Lesser Public License (LGPL). Authors and translators
|
||||||
|
retain the copyright of their work. All font and other build files (the DocBook
|
||||||
|
system) are property of their respective copyright holders. Some of the files
|
||||||
|
(especially font files) might require a license from the respective vendor; you
|
||||||
|
are responsible to check and obtain these licenses as necessary before you use
|
||||||
|
and/or distribute these files.
|
||||||
|
|
||||||
|
|
||||||
|
Preface
|
||||||
|
|
||||||
|
The Hibernate documentation is a modular documentation, it uses
|
||||||
|
various XML files (written with the DocBook DTD) and a Java-based
|
||||||
|
build process to generate HTML and PDF output. Use a simple text
|
||||||
|
editor with XML support, such as JEdit, to edit the source files. You
|
||||||
|
will need Java and Ant installed for the output generation. The toolset
|
||||||
|
is Java only and should work on any operating system.
|
||||||
|
|
||||||
|
Note: Always use 4 spaces to indent, no tabstops (code examples will
|
||||||
|
be broken otherwise).
|
||||||
|
|
||||||
|
|
||||||
|
1. How to get it
|
||||||
|
|
||||||
|
Check out a copy of Hibernate from the repository. A regular
|
||||||
|
Hibernate download will not contain the build process for the
|
||||||
|
documentation, only the PDF/HTML output, use the repository!
|
||||||
|
See http://www.hibernate.org/Download/DownloadOverview
|
||||||
|
|
||||||
|
|
||||||
|
2. Working on the original language
|
||||||
|
|
||||||
|
The original and master language is English, hence the "en" subdirectory
|
||||||
|
in /doc/reference/ is authorative. We use "id" and "revision" attributes on
|
||||||
|
XML elements to track changes. Here are the rules, they are mandatory:
|
||||||
|
|
||||||
|
2a. Changing existing content involves an update of the "revision" of the XML
|
||||||
|
element you are working on (e.g. a <sect1>, <sect2> or even a <para>).
|
||||||
|
|
||||||
|
If a <sect1> has a revision="1", you update it to "2" after updating the
|
||||||
|
content in that section.
|
||||||
|
|
||||||
|
You can also add a revision attribute to an element if there is none,
|
||||||
|
start with revision="1". You should not add a revision attribute to each
|
||||||
|
paragraph, try to only add/use revision attributes to sections. You can'
|
||||||
|
t add a revision attribute to elements without an "id" attribute!
|
||||||
|
|
||||||
|
2b. Adding new content involves adding new elements (even new files), such
|
||||||
|
as <sect1>, <para> and so on. Any new element (or its new parent element)
|
||||||
|
needs an "id" attribute if the new content is to be included in the change
|
||||||
|
tracking. If you add a section, give it a unique short text
|
||||||
|
identifer, look at the parent element's identifier for the common prefix.
|
||||||
|
|
||||||
|
2c. Deleting content involves removing old elements. Just remove them and
|
||||||
|
make sure that the parent elements revision is updated, if the removed
|
||||||
|
element did not itself have an identifer and a revision. If you remove an
|
||||||
|
element with its own identifier, everything is fine and no other changes are
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
|
||||||
|
3. Starting a new language
|
||||||
|
|
||||||
|
If you start a translation for a new language, you have to copy
|
||||||
|
the default language (English) and start an initial translation.
|
||||||
|
|
||||||
|
3a. First, duplicate the default language "en" by duplicating the directory
|
||||||
|
/doc/reference/en. For example, a new German translation
|
||||||
|
will be a copy of that directory in /doc/reference/de. We use the ISO
|
||||||
|
codes to name the language subdirectories.
|
||||||
|
|
||||||
|
3b. You also have to add your new language to the language build file,
|
||||||
|
/doc/reference/build.xml. Look for the lines that have a "TRANSLATOR"
|
||||||
|
comment and duplicate them. Change the default "en" to your language
|
||||||
|
code, every language listed here will be included in both the PDF/HTML
|
||||||
|
generation and the revision diff change tracking reports (discussed later).
|
||||||
|
|
||||||
|
|
||||||
|
4. The initial translation
|
||||||
|
|
||||||
|
If you just copied the default language, start translating the DocBook
|
||||||
|
XML modules and illustrations in the new language subdirectory. For
|
||||||
|
example, all modules for German would be in /doc/reference/de/modules
|
||||||
|
and all illustrations in /doc/reference/de/images, note that you also have
|
||||||
|
to translate the master.xml in your language subdirectory.
|
||||||
|
|
||||||
|
The initial translation is straightforward: Translate all modules and
|
||||||
|
all illustrations, but don't add any files, don't add any new XML elements
|
||||||
|
(like a section or a chapter, not even a paragraph). Simply translate
|
||||||
|
sentence by sentence. This is very important.
|
||||||
|
|
||||||
|
Note that every DocBook XML file needs an encoding, specific to a
|
||||||
|
language. Add a line like this at the top of every file, if it doesn't exist:
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
|
||||||
|
You can use UTF-8 or any other character set, please experiment with
|
||||||
|
the builds to see what works for you.
|
||||||
|
|
||||||
|
If you need a new section or paragraph, because your translation requires
|
||||||
|
more explanation, you can add it if you also add an "id" and a "revision"
|
||||||
|
to that new section or paragraph.
|
||||||
|
|
||||||
|
For example, if you add a new <para> element to the existing document,
|
||||||
|
give it an identifier, a short unique string that extends the identifier
|
||||||
|
string of the parent element: <para id="queryhql-projection-specialnote">
|
||||||
|
would be a special paragraph in the <sect1 id="queryhql-projection">
|
||||||
|
section in the chapter <chapter id="queryhql">.
|
||||||
|
|
||||||
|
Never add a new element in a translated version without also adding a new
|
||||||
|
unique identifier value! Also, you have to mark this new element as "only
|
||||||
|
relevant in the translated version". Simply set the "revision" attribute of
|
||||||
|
your new element to "-1". For example, set the previously created
|
||||||
|
paragraph to "only relevant in the translation" by declaring
|
||||||
|
<para id="queryhql-projection-specialnote" revision="-1">.
|
||||||
|
Changes to that paragraph will not be tracked, it is your responsibility to
|
||||||
|
watch out for neccessary updates. Any element with revision="-1" will not be
|
||||||
|
tracked.
|
||||||
|
|
||||||
|
|
||||||
|
5. Updating translated documentation
|
||||||
|
|
||||||
|
Translators get updates by updating their working directory from the
|
||||||
|
repository. As a translator you will get an e-mail from us when translation
|
||||||
|
is required, you can then update your copy. Or, subscribe to the commit
|
||||||
|
mailing list to get all updates automatically.
|
||||||
|
|
||||||
|
The documentation tools can generate a report after you updated
|
||||||
|
from the repository and show you what needs to be translated and/or removed
|
||||||
|
in your local translation copy. To generate that report, run "ant all.revdiff"
|
||||||
|
in the doc/reference/ subdirectory. Click on the generated HTML report
|
||||||
|
file for your language and you will see what has to be updated and/or
|
||||||
|
removed.
|
||||||
|
|
||||||
|
If the report indicates that content in the original has been removed,
|
||||||
|
simply remove the identified XML element from your language modules.
|
||||||
|
|
||||||
|
If the report detects a new revision, open the file that has been updated
|
||||||
|
in your translation, find the identified XML element and update/translate
|
||||||
|
its contents. Important: Make sure you also update the "revision"
|
||||||
|
attribute of that XML element by setting it to the same version as in
|
||||||
|
the original file, hence both the original XML file and your translated
|
||||||
|
file should have the same revision number for all elements. If an
|
||||||
|
XML element in your translation doesn't have a revision, but the original
|
||||||
|
file has, add a new "revision" attribute to your XML element.
|
||||||
|
The HTML report shows the identifiers and revisions for both the original
|
||||||
|
and the translated files, use it to compare.
|
||||||
|
|
||||||
|
Rerun the "ant all.revdiff" report generation as often as you like until
|
||||||
|
no more differences are detected. You should always try to get your
|
||||||
|
copy clean, with all updated revisions and all identified elements
|
||||||
|
synchronzied.
|
||||||
|
|
||||||
|
|
||||||
|
6. Committing a translation
|
||||||
|
|
||||||
|
All translators will be asked to submit their translated versions from
|
||||||
|
time to time. This will be a manual process, you will get an e-mail from
|
||||||
|
the Hibernate team and simply send your language subdirectory as
|
||||||
|
a ZIP file to us. It will then be integrated in the main Hibernate
|
||||||
|
distribution and on the website. Or, you can contact us for commit access
|
||||||
|
to the repository, where you can maintain a translation directly.
|
||||||
|
|
||||||
|
|
||||||
|
7. Generating PDF and HTML output
|
||||||
|
|
||||||
|
The documentation is generated with the target 'ant all.doc'.
|
||||||
|
|
||||||
|
To build the reference docs for a particular language only, use
|
||||||
|
"ant -Dlang=en", for example, and call either lang.all, lang.docpdf,
|
||||||
|
lang.dochtml, or lang.dochtmlsingle for the target of your choice.
|
||||||
|
|
||||||
|
You can also call lang.section-check to track down missing identifiers in
|
||||||
|
a particular language, or you can call lang.revdiff to get a difference
|
||||||
|
report for a particular language, compared with the English reference.
|
|
@ -0,0 +1,65 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-manual</artifactId>
|
||||||
|
<version>3.3.0.beta1</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>hibernate-manual-${translation}</artifactId>
|
||||||
|
<packaging>docbook</packaging>
|
||||||
|
<name>Hibernate Manual (${translation})</name>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jboss.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jboss-docbook-plugin</artifactId>
|
||||||
|
<version>2.0-SNAPSHOT</version>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.redhat</groupId>
|
||||||
|
<artifactId>docbook-xslt</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<configuration>
|
||||||
|
<sourceDocumentName>master.xml</sourceDocumentName>
|
||||||
|
<formats>
|
||||||
|
<format>
|
||||||
|
<formatName>pdf</formatName>
|
||||||
|
<stylesheetResource>/standard/fopdf.xsl</stylesheetResource>
|
||||||
|
<finalName>Hibernate_Reference.pdf</finalName>
|
||||||
|
</format>
|
||||||
|
<format>
|
||||||
|
<formatName>eclipse</formatName>
|
||||||
|
</format>
|
||||||
|
<format>
|
||||||
|
<formatName>html</formatName>
|
||||||
|
<stylesheetResource>/standard/html.xsl</stylesheetResource>
|
||||||
|
<finalName>index.html</finalName>
|
||||||
|
</format>
|
||||||
|
</formats>
|
||||||
|
<options>
|
||||||
|
<xincludeSupported>true</xincludeSupported>
|
||||||
|
<xmlTransformerType>saxon</xmlTransformerType>
|
||||||
|
<!-- needed for uri-resolvers; can be ommitted if using 'current' uri scheme -->
|
||||||
|
<!-- could also locate the docbook dependency and inspect its version... -->
|
||||||
|
<docbookVersion>1.72.0</docbookVersion>
|
||||||
|
</options>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<translation>en-US</translation>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,183 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN"
|
||||||
|
"../support/docbook-dtd/docbookx.dtd"
|
||||||
|
[
|
||||||
|
<!ENTITY tutorial SYSTEM "modules/tutorial.xml">
|
||||||
|
<!ENTITY architecture SYSTEM "modules/architecture.xml">
|
||||||
|
<!ENTITY configuration SYSTEM "modules/configuration.xml">
|
||||||
|
<!ENTITY persistent-classes SYSTEM "modules/persistent_classes.xml">
|
||||||
|
<!ENTITY basic-mapping SYSTEM "modules/basic_mapping.xml">
|
||||||
|
<!ENTITY collection-mapping SYSTEM "modules/collection_mapping.xml">
|
||||||
|
<!ENTITY association-mapping SYSTEM "modules/association_mapping.xml">
|
||||||
|
<!ENTITY component-mapping SYSTEM "modules/component_mapping.xml">
|
||||||
|
<!ENTITY inheritance-mapping SYSTEM "modules/inheritance_mapping.xml">
|
||||||
|
<!ENTITY session-api SYSTEM "modules/session_api.xml">
|
||||||
|
<!ENTITY transactions SYSTEM "modules/transactions.xml">
|
||||||
|
<!ENTITY events SYSTEM "modules/events.xml">
|
||||||
|
<!ENTITY batch SYSTEM "modules/batch.xml">
|
||||||
|
<!ENTITY query-hql SYSTEM "modules/query_hql.xml">
|
||||||
|
<!ENTITY query-criteria SYSTEM "modules/query_criteria.xml">
|
||||||
|
<!ENTITY query-sql SYSTEM "modules/query_sql.xml">
|
||||||
|
<!ENTITY filters SYSTEM "modules/filters.xml">
|
||||||
|
<!ENTITY xml SYSTEM "modules/xml.xml">
|
||||||
|
<!ENTITY performance SYSTEM "modules/performance.xml">
|
||||||
|
<!ENTITY toolset-guide SYSTEM "modules/toolset_guide.xml">
|
||||||
|
<!ENTITY example-parentchild SYSTEM "modules/example_parentchild.xml">
|
||||||
|
<!ENTITY example-weblog SYSTEM "modules/example_weblog.xml">
|
||||||
|
<!ENTITY example-mappings SYSTEM "modules/example_mappings.xml">
|
||||||
|
<!ENTITY best-practices SYSTEM "modules/best_practices.xml">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<book lang="en">
|
||||||
|
|
||||||
|
<bookinfo>
|
||||||
|
<title>HIBERNATE - Relational Persistence for Idiomatic Java</title>
|
||||||
|
<subtitle>Hibernate Reference Documentation</subtitle>
|
||||||
|
<releaseinfo>3.2 cr3</releaseinfo>
|
||||||
|
</bookinfo>
|
||||||
|
|
||||||
|
<toc/>
|
||||||
|
|
||||||
|
<preface id="preface" revision="2">
|
||||||
|
<title>Preface</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Working with object-oriented software and a relational database can be cumbersome
|
||||||
|
and time consuming in today's enterprise environments. Hibernate is an object/relational
|
||||||
|
mapping tool for Java environments. The term object/relational mapping (ORM) refers to
|
||||||
|
the technique of mapping a data representation from an object model to a relational
|
||||||
|
data model with a SQL-based schema.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate not only takes care of the mapping from Java classes to
|
||||||
|
database tables (and from Java data types to SQL data types), but also provides data
|
||||||
|
query and retrieval facilities and can significantly reduce development time otherwise
|
||||||
|
spent with manual data handling in SQL and JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernates goal is to relieve the developer from 95 percent of common data persistence
|
||||||
|
related programming tasks. Hibernate may not be the best solution for data-centric
|
||||||
|
applications that only use stored-procedures to implement the business logic in the
|
||||||
|
database, it is most useful with object-oriented domain models and business logic in
|
||||||
|
the Java-based middle-tier. However, Hibernate can certainly help you to remove or
|
||||||
|
encapsulate vendor-specific SQL code and will help with the common task of result set
|
||||||
|
translation from a tabular representation to a graph of objects.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you are new to Hibernate and Object/Relational Mapping or even Java,
|
||||||
|
please follow these steps:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Read <xref linkend="tutorial"/> for a tutorial with step-by-step
|
||||||
|
instructions. The source code for the tutorial is included in the
|
||||||
|
distribution in the <literal>doc/reference/tutorial/</literal>
|
||||||
|
directory.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Read <xref linkend="architecture"/> to understand the environments where
|
||||||
|
Hibernate can be used.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Have a look at the <literal>eg/</literal> directory in the Hibernate
|
||||||
|
distribution, it contains a simple standalone application. Copy your
|
||||||
|
JDBC driver to the <literal>lib/</literal> directory and edit
|
||||||
|
<literal>etc/hibernate.properties</literal>, specifying correct values for
|
||||||
|
your database. From a command prompt in the distribution directory,
|
||||||
|
type <literal>ant eg</literal> (using Ant), or under Windows, type
|
||||||
|
<literal>build eg</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Use this reference documentation as your primary source of information.
|
||||||
|
Consider reading <emphasis>Hibernate in Action</emphasis>
|
||||||
|
(http://www.manning.com/bauer) if you need more help with application
|
||||||
|
design or if you prefer a step-by-step tutorial. Also visit
|
||||||
|
http://caveatemptor.hibernate.org and download the example application
|
||||||
|
for Hibernate in Action.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
FAQs are answered on the Hibernate website.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Third party demos, examples, and tutorials are linked on the Hibernate
|
||||||
|
website.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The Community Area on the Hibernate website is a good resource for
|
||||||
|
design patterns and various integration solutions (Tomcat, JBoss AS,
|
||||||
|
Struts, EJB, etc.).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you have questions, use the user forum linked on the Hibernate website. We also
|
||||||
|
provide a JIRA issue trackings system for bug reports and feature requests. If you
|
||||||
|
are interested in the development of Hibernate, join the developer mailing list. If
|
||||||
|
you are interested in translating this documentation into your language, contact us
|
||||||
|
on the developer mailing list.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Commercial development support, production support, and training for Hibernate is
|
||||||
|
available through JBoss Inc. (see http://www.hibernate.org/SupportTraining/).
|
||||||
|
Hibernate is a Professional Open Source project and a critical component of the
|
||||||
|
JBoss Enterprise Middleware System (JEMS) suite of products.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</preface>
|
||||||
|
|
||||||
|
&tutorial;
|
||||||
|
|
||||||
|
&architecture;
|
||||||
|
|
||||||
|
&configuration;
|
||||||
|
|
||||||
|
&persistent-classes;
|
||||||
|
|
||||||
|
&basic-mapping;
|
||||||
|
&collection-mapping;
|
||||||
|
&association-mapping;
|
||||||
|
&component-mapping;
|
||||||
|
&inheritance-mapping;
|
||||||
|
|
||||||
|
&session-api;
|
||||||
|
&transactions;
|
||||||
|
&events;
|
||||||
|
&batch;
|
||||||
|
|
||||||
|
&query-hql;
|
||||||
|
&query-criteria;
|
||||||
|
&query-sql;
|
||||||
|
&filters;
|
||||||
|
&xml;
|
||||||
|
|
||||||
|
&performance;
|
||||||
|
|
||||||
|
&toolset-guide;
|
||||||
|
|
||||||
|
&example-parentchild;
|
||||||
|
&example-weblog;
|
||||||
|
&example-mappings;
|
||||||
|
|
||||||
|
&best-practices;
|
||||||
|
|
||||||
|
</book>
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
<chapter id="architecture">
|
||||||
|
|
||||||
|
<title>Architecture</title>
|
||||||
|
|
||||||
|
<sect1 id="architecture-overview" revision="1">
|
||||||
|
<title>Overview</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A (very) high-level view of the Hibernate architecture:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/overview.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/overview.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This diagram shows Hibernate using the database and configuration data to
|
||||||
|
provide persistence services (and persistent objects) to the application.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We would like to show a more detailed view of the runtime architecture.
|
||||||
|
Unfortunately, Hibernate is flexible and supports several approaches. We will
|
||||||
|
show the two extremes. The "lite" architecture has the application
|
||||||
|
provide its own JDBC connections and manage its own transactions. This approach
|
||||||
|
uses a minimal subset of Hibernate's APIs:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/lite.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/lite.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The "full cream" architecture abstracts the application away from the
|
||||||
|
underlying JDBC/JTA APIs and lets Hibernate take care of the details.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/full_cream.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/full_cream.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Heres some definitions of the objects in the diagrams:
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>SessionFactory (<literal>org.hibernate.SessionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
A threadsafe (immutable) cache of compiled mappings for a single database.
|
||||||
|
A factory for <literal>Session</literal> and a client of
|
||||||
|
<literal>ConnectionProvider</literal>. Might hold an optional (second-level)
|
||||||
|
cache of data that is reusable between transactions, at a
|
||||||
|
process- or cluster-level.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Session (<literal>org.hibernate.Session</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
A single-threaded, short-lived object representing a conversation between
|
||||||
|
the application and the persistent store. Wraps a JDBC connection. Factory
|
||||||
|
for <literal>Transaction</literal>. Holds a mandatory (first-level) cache
|
||||||
|
of persistent objects, used when navigating the object graph or looking up
|
||||||
|
objects by identifier.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Persistent objects and collections</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Short-lived, single threaded objects containing persistent state and business
|
||||||
|
function. These might be ordinary JavaBeans/POJOs, the only special thing about
|
||||||
|
them is that they are currently associated with (exactly one)
|
||||||
|
<literal>Session</literal>. As soon as the <literal>Session</literal> is closed,
|
||||||
|
they will be detached and free to use in any application layer (e.g. directly
|
||||||
|
as data transfer objects to and from presentation).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Transient and detached objects and collections</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Instances of persistent classes that are not currently associated with a
|
||||||
|
<literal>Session</literal>. They may have been instantiated by
|
||||||
|
the application and not (yet) persisted or they may have been instantiated by a
|
||||||
|
closed <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Transaction (<literal>org.hibernate.Transaction</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optional) A single-threaded, short-lived object used by the application to
|
||||||
|
specify atomic units of work. Abstracts application from underlying JDBC,
|
||||||
|
JTA or CORBA transaction. A <literal>Session</literal> might span several
|
||||||
|
<literal>Transaction</literal>s in some cases. However, transaction demarcation,
|
||||||
|
either using the underlying API or <literal>Transaction</literal>, is never
|
||||||
|
optional!
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>ConnectionProvider (<literal>org.hibernate.connection.ConnectionProvider</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optional) A factory for (and pool of) JDBC connections. Abstracts application from
|
||||||
|
underlying <literal>Datasource</literal> or <literal>DriverManager</literal>.
|
||||||
|
Not exposed to application, but can be extended/implemented by the developer.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>TransactionFactory (<literal>org.hibernate.TransactionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optional) A factory for <literal>Transaction</literal> instances. Not exposed to the
|
||||||
|
application, but can be extended/implemented by the developer.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><emphasis>Extension Interfaces</emphasis></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate offers many optional extension interfaces you can implement to customize
|
||||||
|
the behavior of your persistence layer. See the API documentation for details.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Given a "lite" architecture, the application bypasses the
|
||||||
|
<literal>Transaction</literal>/<literal>TransactionFactory</literal> and/or
|
||||||
|
<literal>ConnectionProvider</literal> APIs to talk to JTA or JDBC directly.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-states" revision="1">
|
||||||
|
<title>Instance states</title>
|
||||||
|
<para>
|
||||||
|
An instance of a persistent classes may be in one of three different states,
|
||||||
|
which are defined with respect to a <emphasis>persistence context</emphasis>.
|
||||||
|
The Hibernate <literal>Session</literal> object is the persistence context:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>transient</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The instance is not, and has never been associated with
|
||||||
|
any persistence context. It has no persistent identity
|
||||||
|
(primary key value).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>persistent</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The instance is currently associated with a persistence
|
||||||
|
context. It has a persistent identity (primary key value)
|
||||||
|
and, perhaps, a corresponding row in the database. For a
|
||||||
|
particular persistence context, Hibernate
|
||||||
|
<emphasis>guarantees</emphasis> that persistent identity
|
||||||
|
is equivalent to Java identity (in-memory location of the
|
||||||
|
object).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>detached</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The instance was once associated with a persistence
|
||||||
|
context, but that context was closed, or the instance
|
||||||
|
was serialized to another process. It has a persistent
|
||||||
|
identity and, perhaps, a corrsponding row in the database.
|
||||||
|
For detached instances, Hibernate makes no guarantees
|
||||||
|
about the relationship between persistent identity and
|
||||||
|
Java identity.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jmx" revision="1">
|
||||||
|
<title>JMX Integration</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
JMX is the J2EE standard for management of Java components. Hibernate may be managed via
|
||||||
|
a JMX standard service. We provide an MBean implementation in the distribution,
|
||||||
|
<literal>org.hibernate.jmx.HibernateService</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For an example how to deploy Hibernate as a JMX service on the JBoss Application Server,
|
||||||
|
please see the JBoss User Guide. On JBoss AS, you also get these benefits if you deploy
|
||||||
|
using JMX:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Session Management:</emphasis> The Hibernate <literal>Session</literal>'s lifecycle
|
||||||
|
can be automatically bound to the scope of a JTA transaction. This means you no
|
||||||
|
longer have to manually open and close the <literal>Session</literal>, this
|
||||||
|
becomes the job of a JBoss EJB interceptor. You also don't have to worry about
|
||||||
|
transaction demarcation in your code anymore (unless you'd like to write a portable
|
||||||
|
persistence layer of course, use the optional Hibernate <literal>Transaction</literal>
|
||||||
|
API for this). You call the <literal>HibernateContext</literal> to access a
|
||||||
|
<literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>HAR deployment:</emphasis> Usually you deploy the Hibernate JMX service using a JBoss
|
||||||
|
service deployment descriptor (in an EAR and/or SAR file), it supports all the usual
|
||||||
|
configuration options of a Hibernate <literal>SessionFactory</literal>. However, you still
|
||||||
|
have to name all your mapping files in the deployment descriptor. If you decide to use
|
||||||
|
the optional HAR deployment, JBoss will automatically detect all mapping files in your
|
||||||
|
HAR file.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Consult the JBoss AS user guide for more information about these options.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Another feature available as a JMX service are runtime Hibernate statistics. See
|
||||||
|
<xref linkend="configuration-optional-statistics"/>.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jca" revision="1">
|
||||||
|
<title>JCA Support</title>
|
||||||
|
<para>
|
||||||
|
Hibernate may also be configured as a JCA connector. Please see the website for more
|
||||||
|
details. Please note that Hibernate JCA support is still considered experimental.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-current-session" revision="2">
|
||||||
|
<title>Contextual Sessions</title>
|
||||||
|
<para>
|
||||||
|
Most applications using Hibernate need some form of "contextual" sessions, where a given
|
||||||
|
session is in effect throughout the scope of a given context. However, across applications
|
||||||
|
the definition of what constitutes a context is typically different; and different contexts
|
||||||
|
define different scopes to the notion of current. Applications using Hibernate prior
|
||||||
|
to version 3.0 tended to utilize either home-grown <literal>ThreadLocal</literal>-based
|
||||||
|
contextual sessions, helper classes such as <literal>HibernateUtil</literal>, or utilized
|
||||||
|
third-party frameworks (such as Spring or Pico) which provided proxy/interception-based contextual sessions.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Starting with version 3.0.1, Hibernate added the <literal>SessionFactory.getCurrentSession()</literal>
|
||||||
|
method. Initially, this assumed usage of <literal>JTA</literal> transactions, where the
|
||||||
|
<literal>JTA</literal> transaction defined both the scope and context of a current session.
|
||||||
|
The Hibernate team maintains that, given the maturity of the numerous stand-alone
|
||||||
|
<literal>JTA TransactionManager</literal> implementations out there, most (if not all)
|
||||||
|
applications should be using <literal>JTA</literal> transaction management whether or not
|
||||||
|
they are deployed into a <literal>J2EE</literal> container. Based on that, the
|
||||||
|
<literal>JTA</literal>-based contextual sessions is all you should ever need to use.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
However, as of version 3.1, the processing behind
|
||||||
|
<literal>SessionFactory.getCurrentSession()</literal> is now pluggable. To that
|
||||||
|
end, a new extension interface (<literal>org.hibernate.context.CurrentSessionContext</literal>)
|
||||||
|
and a new configuration parameter (<literal>hibernate.current_session_context_class</literal>)
|
||||||
|
have been added to allow pluggability of the scope and context of defining current sessions.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
See the Javadocs for the <literal>org.hibernate.context.CurrentSessionContext</literal>
|
||||||
|
interface for a detailed discussion of its contract. It defines a single method,
|
||||||
|
<literal>currentSession()</literal>, by which the implementation is responsible for
|
||||||
|
tracking the current contextual session. Out-of-the-box, Hibernate comes with three
|
||||||
|
implementations of this interface.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.context.JTASessionContext</literal> - current sessions
|
||||||
|
are tracked and scoped by a <literal>JTA</literal> transaction. The processing
|
||||||
|
here is exactly the same as in the older JTA-only approach. See the Javadocs
|
||||||
|
for details.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.context.ThreadLocalSessionContext</literal> - current
|
||||||
|
sessions are tracked by thread of execution. Again, see the Javadocs for details.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.context.ManagedSessionContext</literal> - current
|
||||||
|
sessions are tracked by thread of execution. However, you are responsible to
|
||||||
|
bind and unbind a <literal>Session</literal> instance with static methods
|
||||||
|
on this class, it does never open, flush, or close a <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The first two implementations provide a "one session - one database transaction" programming
|
||||||
|
model, also known and used as <emphasis>session-per-request</emphasis>. The beginning
|
||||||
|
and end of a Hibernate session is defined by the duration of a database transaction.
|
||||||
|
If you use programatic transaction demarcation in plain JSE without JTA, you are adviced to
|
||||||
|
use the Hibernate <literal>Transaction</literal> API to hide the underlying transaction system
|
||||||
|
from your code. If you use JTA, use the JTA interfaces to demarcate transactions. If you
|
||||||
|
execute in an EJB container that supports CMT, transaction boundaries are defined declaratively
|
||||||
|
and you don't need any transaction or session demarcation operations in your code.
|
||||||
|
Refer to <xref linkend="transactions"/> for more information and code examples.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>hibernate.current_session_context_class</literal> configuration parameter
|
||||||
|
defines which <literal>org.hibernate.context.CurrentSessionContext</literal> implementation
|
||||||
|
should be used. Note that for backwards compatibility, if this config param is not set
|
||||||
|
but a <literal>org.hibernate.transaction.TransactionManagerLookup</literal> is configured,
|
||||||
|
Hibernate will use the <literal>org.hibernate.context.JTASessionContext</literal>.
|
||||||
|
Typically, the value of this parameter would just name the implementation class to
|
||||||
|
use; for the three out-of-the-box implementations, however, there are two corresponding
|
||||||
|
short names, "jta", "thread", and "managed".
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,623 @@
|
||||||
|
<chapter id="associations">
|
||||||
|
|
||||||
|
<title>Association Mappings</title>
|
||||||
|
|
||||||
|
<sect1 id="assoc-intro" revision="1">
|
||||||
|
<title>Introduction</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Association mappings are the often most difficult thing to get right. In
|
||||||
|
this section we'll go through the canonical cases one by one, starting
|
||||||
|
with unidirectional mappings, and then considering the bidirectional cases.
|
||||||
|
We'll use <literal>Person</literal> and <literal>Address</literal> in all
|
||||||
|
the examples.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We'll classify associations by whether or not they map to an intervening
|
||||||
|
join table, and by multiplicity.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nullable foreign keys are not considered good practice in traditional data
|
||||||
|
modelling, so all our examples use not null foreign keys. This is not a
|
||||||
|
requirement of Hibernate, and the mappings will all work if you drop the
|
||||||
|
nullability constraints.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional" revision="1">
|
||||||
|
<title>Unidirectional associations</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-m21">
|
||||||
|
<title>many to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional many-to-one association</emphasis> is the most
|
||||||
|
common kind of unidirectional association.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-121">
|
||||||
|
<title>one to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional one-to-one association on a foreign key</emphasis>
|
||||||
|
is almost identical. The only difference is the column unique constraint.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional one-to-one association on a primary key</emphasis>
|
||||||
|
usually uses a special id generator. (Notice that we've reversed the direction
|
||||||
|
of the association in this example.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-12m">
|
||||||
|
<title>one to many</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional one-to-many association on a foreign key</emphasis>
|
||||||
|
is a very unusual case, and is not really recommended.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses">
|
||||||
|
<key column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
<one-to-many class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key, personId bigint not null )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We think it's better to use a join table for this kind of association.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional-join" revision="1">
|
||||||
|
<title>Unidirectional associations with join tables</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-12m">
|
||||||
|
<title>one to many</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional one-to-many association on a join table</emphasis>
|
||||||
|
is much preferred. Notice that by specifying <literal>unique="true"</literal>,
|
||||||
|
we have changed the multiplicity from many-to-many to one-to-many.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m21">
|
||||||
|
<title>many to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional many-to-one association on a join table</emphasis>
|
||||||
|
is quite common when the association is optional.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId" unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-121">
|
||||||
|
<title>one to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>unidirectional one-to-one association on a join table</emphasis>
|
||||||
|
is extremely unusual, but possible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m2m">
|
||||||
|
<title>many to many</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finally, we have a <emphasis>unidirectional many-to-many association</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional" revision="1">
|
||||||
|
<title>Bidirectional associations</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-m21" revision="2">
|
||||||
|
<title>one to many / many to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>bidirectional many-to-one association</emphasis> is the
|
||||||
|
most common kind of association. (This is the standard parent/child
|
||||||
|
relationship.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<one-to-many class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you use a <literal>List</literal> (or other indexed collection) you need
|
||||||
|
to set the <literal>key</literal> column of the foreign key to <literal>not null</literal>,
|
||||||
|
and let Hibernate manage the association from the collections side to maintain the index
|
||||||
|
of each element (making the other side virtually inverse by setting
|
||||||
|
<literal>update="false"</literal> and <literal>insert="false"</literal>):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id"/>
|
||||||
|
...
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
insert="false"
|
||||||
|
update="false"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id"/>
|
||||||
|
...
|
||||||
|
<list name="people">
|
||||||
|
<key column="addressId" not-null="true"/>
|
||||||
|
<list-index column="peopleIdx"/>
|
||||||
|
<one-to-many class="Person"/>
|
||||||
|
</list>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is important that you define <literal>not-null="true"</literal> on the
|
||||||
|
<literal><key></literal> element of the collection mapping if the
|
||||||
|
underlying foreign key column is <literal>NOT NULL</literal>. Don't only
|
||||||
|
declare <literal>not-null="true"</literal> on a possible nested
|
||||||
|
<literal><column></literal> element, but on the <literal><key></literal>
|
||||||
|
element.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-121">
|
||||||
|
<title>one to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>bidirectional one-to-one association on a foreign key</emphasis>
|
||||||
|
is quite common.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
property-ref="address"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>bidirectional one-to-one association on a primary key</emphasis>
|
||||||
|
uses the special id generator.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="address"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional-join" revision="1">
|
||||||
|
<title>Bidirectional associations with join tables</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-12m">
|
||||||
|
<title>one to many / many to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>bidirectional one-to-many association on a join table</emphasis>.
|
||||||
|
Note that the <literal>inverse="true"</literal> can go on either end of the
|
||||||
|
association, on the collection, or on the join.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses"
|
||||||
|
table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
inverse="true"
|
||||||
|
optional="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-one name="person"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-121">
|
||||||
|
<title>one to one</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <emphasis>bidirectional one-to-one association on a join table</emphasis>
|
||||||
|
is extremely unusual, but possible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true"
|
||||||
|
inverse="true">
|
||||||
|
<key column="addressId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="person"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-m2m" revision="1">
|
||||||
|
<title>many to many</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finally, we have a <emphasis>bidirectional many-to-many association</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true" table="PersonAddress">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-many column="personId"
|
||||||
|
class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-complex">
|
||||||
|
<title>More complex association mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
More complex association joins are <emphasis>extremely</emphasis> rare.
|
||||||
|
Hibernate makes it possible to handle more complex situations using
|
||||||
|
SQL fragments embedded in the mapping document. For example, if a table
|
||||||
|
with historical account information data defines
|
||||||
|
<literal>accountNumber</literal>, <literal>effectiveEndDate</literal>
|
||||||
|
and <literal>effectiveStartDate</literal>columns, mapped as follows:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<properties name="currentAccountKey">
|
||||||
|
<property name="accountNumber" type="string" not-null="true"/>
|
||||||
|
<property name="currentAccount" type="boolean">
|
||||||
|
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
|
||||||
|
</property>
|
||||||
|
</properties>
|
||||||
|
<property name="effectiveEndDate" type="date"/>
|
||||||
|
<property name="effectiveStateDate" type="date" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Then we can map an association to the <emphasis>current</emphasis> instance
|
||||||
|
(the one with null <literal>effectiveEndDate</literal>) using:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="currentAccountInfo"
|
||||||
|
property-ref="currentAccountKey"
|
||||||
|
class="AccountInfo">
|
||||||
|
<column name="accountNumber"/>
|
||||||
|
<formula>'1'</formula>
|
||||||
|
</many-to-one>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In a more complex example, imagine that the association between
|
||||||
|
<literal>Employee</literal> and <literal>Organization</literal> is maintained
|
||||||
|
in an <literal>Employment</literal> table full of historical employment data.
|
||||||
|
Then an association to the employee's <emphasis>most recent</emphasis> employer
|
||||||
|
(the one with the most recent <literal>startDate</literal>) might be mapped this way:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<join>
|
||||||
|
<key column="employeeId"/>
|
||||||
|
<subselect>
|
||||||
|
select employeeId, orgId
|
||||||
|
from Employments
|
||||||
|
group by orgId
|
||||||
|
having startDate = max(startDate)
|
||||||
|
</subselect>
|
||||||
|
<many-to-one name="mostRecentEmployer"
|
||||||
|
class="Organization"
|
||||||
|
column="orgId"/>
|
||||||
|
</join>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can get quite creative with this functionality, but it is usually more practical
|
||||||
|
to handle these kinds of cases using HQL or a criteria query.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,347 @@
|
||||||
|
<chapter id="batch">
|
||||||
|
<title>Batch processing</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A naive approach to inserting 100 000 rows in the database using Hibernate might
|
||||||
|
look like this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
}
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This would fall over with an <literal>OutOfMemoryException</literal> somewhere
|
||||||
|
around the 50 000th row. That's because Hibernate caches all the newly inserted
|
||||||
|
<literal>Customer</literal> instances in the session-level cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In this chapter we'll show you how to avoid this problem. First, however, if you
|
||||||
|
are doing batch processing, it is absolutely critical that you enable the use of
|
||||||
|
JDBC batching, if you intend to achieve reasonable performance. Set the JDBC batch
|
||||||
|
size to a reasonable number (say, 10-50):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
|
||||||
|
|
||||||
|
<para id="disablebatching" revision="1">
|
||||||
|
Note that Hibernate disables insert batching at the JDBC level transparently if you
|
||||||
|
use an <literal>identiy</literal> identifier generator.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You also might like to do this kind of work in a process where interaction with
|
||||||
|
the second-level cache is completely disabled:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
However, this is not absolutely necessary, since we can explicitly set the
|
||||||
|
<literal>CacheMode</literal> to disable interaction with the second-level cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="batch-inserts">
|
||||||
|
<title>Batch inserts</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
When making new objects persistent, you must <literal>flush()</literal> and
|
||||||
|
then <literal>clear()</literal> the session regularly, to control the size of
|
||||||
|
the first-level cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
|
||||||
|
//flush a batch of inserts and release memory:
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-update" >
|
||||||
|
<title>Batch updates</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For retrieving and updating data the same ideas apply. In addition, you need to
|
||||||
|
use <literal>scroll()</literal> to take advantage of server-side cursors for
|
||||||
|
queries that return many rows of data.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||||
|
.setCacheMode(CacheMode.IGNORE)
|
||||||
|
.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
int count=0;
|
||||||
|
while ( customers.next() ) {
|
||||||
|
Customer customer = (Customer) customers.get(0);
|
||||||
|
customer.updateStuff(...);
|
||||||
|
if ( ++count % 20 == 0 ) {
|
||||||
|
//flush a batch of updates and release memory:
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-statelesssession">
|
||||||
|
<title>The StatelessSession interface</title>
|
||||||
|
<para>
|
||||||
|
Alternatively, Hibernate provides a command-oriented API that may be used for
|
||||||
|
streaming data to and from the database in the form of detached objects. A
|
||||||
|
<literal>StatelessSession</literal> has no persistence context associated
|
||||||
|
with it and does not provide many of the higher-level lifecycle semantics.
|
||||||
|
In particular, a stateless session does not implement a first-level cache nor
|
||||||
|
interact with any second-level or query cache. It does not implement
|
||||||
|
transactional write-behind or automatic dirty checking. Operations performed
|
||||||
|
using a stateless session do not ever cascade to associated instances. Collections
|
||||||
|
are ignored by a stateless session. Operations performed via a stateless session
|
||||||
|
bypass Hibernate's event model and interceptors. Stateless sessions are vulnerable
|
||||||
|
to data aliasing effects, due to the lack of a first-level cache. A stateless
|
||||||
|
session is a lower-level abstraction, much closer to the underlying JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[StatelessSession session = sessionFactory.openStatelessSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||||
|
.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
while ( customers.next() ) {
|
||||||
|
Customer customer = (Customer) customers.get(0);
|
||||||
|
customer.updateStuff(...);
|
||||||
|
session.update(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that in this code example, the <literal>Customer</literal> instances returned
|
||||||
|
by the query are immediately detached. They are never associated with any persistence
|
||||||
|
context.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>insert(), update()</literal> and <literal>delete()</literal> operations
|
||||||
|
defined by the <literal>StatelessSession</literal> interface are considered to be
|
||||||
|
direct database row-level operations, which result in immediate execution of a SQL
|
||||||
|
<literal>INSERT, UPDATE</literal> or <literal>DELETE</literal> respectively. Thus,
|
||||||
|
they have very different semantics to the <literal>save(), saveOrUpdate()</literal>
|
||||||
|
and <literal>delete()</literal> operations defined by the <literal>Session</literal>
|
||||||
|
interface.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-direct" revision="3">
|
||||||
|
<title>DML-style operations</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
As already discussed, automatic and transparent object/relational mapping is concerned
|
||||||
|
with the management of object state. This implies that the object state is available
|
||||||
|
in memory, hence manipulating (using the SQL <literal>Data Manipulation Language</literal>
|
||||||
|
(DML) statements: <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>)
|
||||||
|
data directly in the database will not affect in-memory state. However, Hibernate provides methods
|
||||||
|
for bulk SQL-style DML statement execution which are performed through the
|
||||||
|
Hibernate Query Language (<xref linkend="queryhql">HQL</xref>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The pseudo-syntax for <literal>UPDATE</literal> and <literal>DELETE</literal> statements
|
||||||
|
is: <literal>( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?</literal>. Some
|
||||||
|
points to note:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
In the from-clause, the FROM keyword is optional
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
There can only be a single entity named in the from-clause; it can optionally be
|
||||||
|
aliased. If the entity name is aliased, then any property references must
|
||||||
|
be qualified using that alias; if the entity name is not aliased, then it is
|
||||||
|
illegal for any property references to be qualified.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
No <xref linkend="queryhql-joins-forms">joins</xref> (either implicit or explicit)
|
||||||
|
can be specified in a bulk HQL query. Sub-queries may be used in the where-clause;
|
||||||
|
the subqueries, themselves, may contain joins.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The where-clause is also optional.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
As an example, to execute an HQL <literal>UPDATE</literal>, use the
|
||||||
|
<literal>Query.executeUpdate()</literal> method (the method is named for
|
||||||
|
those familiar with JDBC's <literal>PreparedStatement.executeUpdate()</literal>):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
|
||||||
|
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
|
||||||
|
int updatedEntities = s.createQuery( hqlUpdate )
|
||||||
|
.setString( "newName", newName )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
HQL <literal>UPDATE</literal> statements, by default do not effect the
|
||||||
|
<xref linkend="mapping-declaration-version">version</xref>
|
||||||
|
or the <xref linkend="mapping-declaration-timestamp">timestamp</xref> property values
|
||||||
|
for the affected entities; this is in keeping with the EJB3 specification. However,
|
||||||
|
you can force Hibernate to properly reset the <literal>version</literal> or
|
||||||
|
<literal>timestamp</literal> property values through the use of a <literal>versioned update</literal>.
|
||||||
|
This is achieved by adding the <literal>VERSIONED</literal> keyword after the <literal>UPDATE</literal>
|
||||||
|
keyword.
|
||||||
|
</para>
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
|
||||||
|
int updatedEntities = s.createQuery( hqlUpdate )
|
||||||
|
.setString( "newName", newName )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that custom version types (<literal>org.hibernate.usertype.UserVersionType</literal>)
|
||||||
|
are not allowed in conjunction with a <literal>update versioned</literal> statement.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
To execute an HQL <literal>DELETE</literal>, use the same <literal>Query.executeUpdate()</literal>
|
||||||
|
method:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlDelete = "delete Customer c where c.name = :oldName";
|
||||||
|
// or String hqlDelete = "delete Customer where name = :oldName";
|
||||||
|
int deletedEntities = s.createQuery( hqlDelete )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>int</literal> value returned by the <literal>Query.executeUpdate()</literal>
|
||||||
|
method indicate the number of entities effected by the operation. Consider this may or may not
|
||||||
|
correlate to the number of rows effected in the database. An HQL bulk operation might result in
|
||||||
|
multiple actual SQL statements being executed, for joined-subclass, for example. The returned
|
||||||
|
number indicates the number of actual entities affected by the statement. Going back to the
|
||||||
|
example of joined-subclass, a delete against one of the subclasses may actually result
|
||||||
|
in deletes against not just the table to which that subclass is mapped, but also the "root"
|
||||||
|
table and potentially joined-subclass tables further down the inheritence hierarchy.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The pseudo-syntax for <literal>INSERT</literal> statements is:
|
||||||
|
<literal>INSERT INTO EntityName properties_list select_statement</literal>. Some
|
||||||
|
points to note:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Only the INSERT INTO ... SELECT ... form is supported; not the INSERT INTO ... VALUES ... form.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The properties_list is analogous to the <literal>column speficiation</literal>
|
||||||
|
in the SQL <literal>INSERT</literal> statement. For entities involved in mapped
|
||||||
|
inheritence, only properties directly defined on that given class-level can be
|
||||||
|
used in the properties_list. Superclass properties are not allowed; and subclass
|
||||||
|
properties do not make sense. In other words, <literal>INSERT</literal>
|
||||||
|
statements are inherently non-polymorphic.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
select_statement can be any valid HQL select query, with the caveat that the return types
|
||||||
|
must match the types expected by the insert. Currently, this is checked during query
|
||||||
|
compilation rather than allowing the check to relegate to the database. Note however
|
||||||
|
that this might cause problems between Hibernate <literal>Type</literal>s which are
|
||||||
|
<emphasis>equivalent</emphasis> as opposed to <emphasis>equal</emphasis>. This might cause
|
||||||
|
issues with mismatches between a property defined as a <literal>org.hibernate.type.DateType</literal>
|
||||||
|
and a property defined as a <literal>org.hibernate.type.TimestampType</literal>, even though the
|
||||||
|
database might not make a distinction or might be able to handle the conversion.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
For the id property, the insert statement gives you two options. You can either
|
||||||
|
explicitly specify the id property in the properties_list (in which case its value
|
||||||
|
is taken from the corresponding select expression) or omit it from the properties_list
|
||||||
|
(in which case a generated value is used). This later option is only available when
|
||||||
|
using id generators that operate in the database; attempting to use this option with
|
||||||
|
any "in memory" type generators will cause an exception during parsing. Note that
|
||||||
|
for the purposes of this discussion, in-database generators are considered to be
|
||||||
|
<literal>org.hibernate.id.SequenceGenerator</literal> (and its subclasses) and
|
||||||
|
any implementors of <literal>org.hibernate.id.PostInsertIdentifierGenerator</literal>.
|
||||||
|
The most notable exception here is <literal>org.hibernate.id.TableHiLoGenerator</literal>,
|
||||||
|
which cannot be used because it does not expose a selectable way to get its values.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
For properties mapped as either <literal>version</literal> or <literal>timestamp</literal>,
|
||||||
|
the insert statement gives you two options. You can either specify the property in the
|
||||||
|
properties_list (in which case its value is taken from the corresponding select expressions)
|
||||||
|
or omit it from the properties_list (in which case the <literal>seed value</literal> defined
|
||||||
|
by the <literal>org.hibernate.type.VersionType</literal> is used).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An example HQL <literal>INSERT</literal> statement execution:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
|
||||||
|
int createdEntities = s.createQuery( hqlInsert )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,223 @@
|
||||||
|
<chapter id="best-practices" revision="3">
|
||||||
|
<title>Best Practices</title>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>Write fine-grained classes and map them using <literal><component></literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Use an <literal>Address</literal> class to encapsulate <literal>street</literal>,
|
||||||
|
<literal>suburb</literal>, <literal>state</literal>, <literal>postcode</literal>.
|
||||||
|
This encourages code reuse and simplifies refactoring.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Declare identifier properties on persistent classes.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate makes identifier properties optional. There are all sorts of reasons why
|
||||||
|
you should use them. We recommend that identifiers be 'synthetic' (generated, with
|
||||||
|
no business meaning).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identify natural keys.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Identify natural keys for all entities, and map them using
|
||||||
|
<literal><natural-id></literal>. Implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal> to compare the properties that make up the natural key.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Place each class mapping in its own file.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Don't use a single monolithic mapping document. Map <literal>com.eg.Foo</literal> in
|
||||||
|
the file <literal>com/eg/Foo.hbm.xml</literal>. This makes particularly good sense in
|
||||||
|
a team environment.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Load mappings as resources.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Deploy the mappings along with the classes they map.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Consider externalising query strings.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This is a good practice if your queries call non-ANSI-standard SQL functions.
|
||||||
|
Externalising the query strings to mapping files will make the application more
|
||||||
|
portable.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Use bind variables.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
As in JDBC, always replace non-constant values by "?". Never use string manipulation to
|
||||||
|
bind a non-constant value in a query! Even better, consider using named parameters in
|
||||||
|
queries.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Don't manage your own JDBC connections.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate lets the application manage JDBC connections. This approach should be considered
|
||||||
|
a last-resort. If you can't use the built-in connections providers, consider providing your
|
||||||
|
own implementation of <literal>org.hibernate.connection.ConnectionProvider</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Consider using a custom type.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Suppose you have a Java type, say from some library, that needs to be persisted but doesn't
|
||||||
|
provide the accessors needed to map it as a component. You should consider implementing
|
||||||
|
<literal>org.hibernate.UserType</literal>. This approach frees the application
|
||||||
|
code from implementing transformations to / from a Hibernate type.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Use hand-coded JDBC in bottlenecks.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
In performance-critical areas of the system, some kinds of operations might benefit from
|
||||||
|
direct JDBC. But please, wait until you <emphasis>know</emphasis> something is a bottleneck.
|
||||||
|
And don't assume that direct JDBC is necessarily faster. If you need to use direct JDBC, it might
|
||||||
|
be worth opening a Hibernate <literal>Session</literal> and using that JDBC connection. That
|
||||||
|
way you can still use the same transaction strategy and underlying connection provider.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Understand <literal>Session</literal> flushing.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
From time to time the Session synchronizes its persistent state with the database. Performance will
|
||||||
|
be affected if this process occurs too often. You may sometimes minimize unnecessary flushing by
|
||||||
|
disabling automatic flushing or even by changing the order of queries and other operations within a
|
||||||
|
particular transaction.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>In a three tiered architecture, consider using detached objects.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
When using a servlet / session bean architecture, you could pass persistent objects loaded in
|
||||||
|
the session bean to and from the servlet / JSP layer. Use a new session to service each request.
|
||||||
|
Use <literal>Session.merge()</literal> or <literal>Session.saveOrUpdate()</literal> to
|
||||||
|
synchronize objects with the database.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>In a two tiered architecture, consider using long persistence contexts.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Database Transactions have to be as short as possible for best scalability. However, it is often
|
||||||
|
neccessary to implement long running <emphasis>application transactions</emphasis>, a single
|
||||||
|
unit-of-work from the point of view of a user. An application transaction might span several
|
||||||
|
client request/response cycles. It is common to use detached objects to implement application
|
||||||
|
transactions. An alternative, extremely appropriate in two tiered architecture, is to maintain
|
||||||
|
a single open persistence contact (session) for the whole lifecycle of the application transaction
|
||||||
|
and simply disconnect from the JDBC connection at the end of each request and reconnect at the
|
||||||
|
beginning of the subsequent request. Never share a single session across more than one application
|
||||||
|
transaction, or you will be working with stale data.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Don't treat exceptions as recoverable.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
This is more of a necessary practice than a "best" practice. When an exception occurs, roll back
|
||||||
|
the <literal>Transaction</literal> and close the <literal>Session</literal>. If you don't, Hibernate
|
||||||
|
can't guarantee that in-memory state accurately represents persistent state. As a special case of this,
|
||||||
|
do not use <literal>Session.load()</literal> to determine if an instance with the given identifier
|
||||||
|
exists on the database; use <literal>Session.get()</literal> or a query instead.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Prefer lazy fetching for associations.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Use eager fetching sparingly. Use proxies and lazy collections for most associations to classes that
|
||||||
|
are not likely to be completely held in the second-level cache. For associations to cached classes,
|
||||||
|
where there is an a extremely high probability of a cache hit, explicitly disable eager fetching using
|
||||||
|
<literal>lazy="false"</literal>. When an join fetching is appropriate to a particular use
|
||||||
|
case, use a query with a <literal>left join fetch</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Use the <emphasis>open session in view</emphasis> pattern, or a disciplined
|
||||||
|
<emphasis>assembly phase</emphasis> to avoid problems with unfetched data.
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate frees the developer from writing tedious <emphasis>Data Transfer Objects</emphasis> (DTO).
|
||||||
|
In a traditional EJB architecture, DTOs serve dual purposes: first, they work around the problem
|
||||||
|
that entity beans are not serializable; second, they implicitly define an assembly phase where
|
||||||
|
all data to be used by the view is fetched and marshalled into the DTOs before returning control
|
||||||
|
to the presentation tier. Hibernate eliminates the first purpose. However, you will still need
|
||||||
|
an assembly phase (think of your business methods as having a strict contract with the presentation
|
||||||
|
tier about what data is available in the detached objects) unless you are prepared to hold the
|
||||||
|
persistence context (the session) open across the view rendering process. This is not a limitation
|
||||||
|
of Hibernate! It is a fundamental requirement of safe transactional data access.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Consider abstracting your business logic from Hibernate.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hide (Hibernate) data-access code behind an interface. Combine the <emphasis>DAO</emphasis> and
|
||||||
|
<emphasis>Thread Local Session</emphasis> patterns. You can even have some classes persisted by
|
||||||
|
handcoded JDBC, associated to Hibernate via a <literal>UserType</literal>. (This advice is
|
||||||
|
intended for "sufficiently large" applications; it is not appropriate for an application with
|
||||||
|
five tables!)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Don't use exotic association mappings.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Good usecases for a real many-to-many associations are rare. Most of the time you need
|
||||||
|
additional information stored in the "link table". In this case, it is much better to
|
||||||
|
use two one-to-many associations to an intermediate link class. In fact, we think that
|
||||||
|
most associations are one-to-many and many-to-one, you should be careful when using any
|
||||||
|
other association style and ask yourself if it is really neccessary.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Prefer bidirectional associations.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Unidirectional associations are more difficult to query. In a large application, almost
|
||||||
|
all associations must be navigable in both directions in queries.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,402 @@
|
||||||
|
<chapter id="components">
|
||||||
|
<title>Component Mapping</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The notion of a <emphasis>component</emphasis> is re-used in several different contexts,
|
||||||
|
for different purposes, throughout Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="components-dependentobjects" revision="2" >
|
||||||
|
<title>Dependent objects</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A component is a contained object that is persisted as a value type, not an entity
|
||||||
|
reference. The term "component" refers to the object-oriented notion of composition
|
||||||
|
(not to architecture-level components). For example, you might model a person like this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Person {
|
||||||
|
private java.util.Date birthday;
|
||||||
|
private Name name;
|
||||||
|
private String key;
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
private void setKey(String key) {
|
||||||
|
this.key=key;
|
||||||
|
}
|
||||||
|
public java.util.Date getBirthday() {
|
||||||
|
return birthday;
|
||||||
|
}
|
||||||
|
public void setBirthday(java.util.Date birthday) {
|
||||||
|
this.birthday = birthday;
|
||||||
|
}
|
||||||
|
public Name getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public void setName(Name name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
......
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Name {
|
||||||
|
char initial;
|
||||||
|
String first;
|
||||||
|
String last;
|
||||||
|
public String getFirst() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
void setFirst(String first) {
|
||||||
|
this.first = first;
|
||||||
|
}
|
||||||
|
public String getLast() {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
void setLast(String last) {
|
||||||
|
this.last = last;
|
||||||
|
}
|
||||||
|
public char getInitial() {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
void setInitial(char initial) {
|
||||||
|
this.initial = initial;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now <literal>Name</literal> may be persisted as a component of
|
||||||
|
<literal>Person</literal>. Notice that <literal>Name</literal> defines getter
|
||||||
|
and setter methods for its persistent properties, but doesn't need to declare
|
||||||
|
any interfaces or identifier properties.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Our Hibernate mapping would look like:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The person table would have the columns <literal>pid</literal>,
|
||||||
|
<literal>birthday</literal>,
|
||||||
|
<literal>initial</literal>,
|
||||||
|
<literal>first</literal> and
|
||||||
|
<literal>last</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Like all value types, components do not support shared references. In other words, two
|
||||||
|
persons could have the same name, but the two person objects would contain two independent
|
||||||
|
name ojects, only "the same" by value. The null value semantics of a component are
|
||||||
|
<emphasis>ad hoc</emphasis>. When reloading the containing object, Hibernate will assume
|
||||||
|
that if all component columns are null, then the entire component is null. This should
|
||||||
|
be okay for most purposes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The properties of a component may be of any Hibernate type (collections, many-to-one
|
||||||
|
associations, other components, etc). Nested components should <emphasis>not</emphasis>
|
||||||
|
be considered an exotic usage. Hibernate is intended to support a very fine-grained
|
||||||
|
object model.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal><component></literal> element allows a <literal><parent></literal>
|
||||||
|
subelement that maps a property of the component class as a reference back to the
|
||||||
|
containing entity.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name" unique="true">
|
||||||
|
<parent name="namedPerson"/> <!-- reference back to the Person -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-incollections" revision="1">
|
||||||
|
<title>Collections of dependent objects</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Collections of components are supported (eg. an array of type
|
||||||
|
<literal>Name</literal>). Declare your component collection by
|
||||||
|
replacing the <literal><element></literal> tag with a
|
||||||
|
<literal><composite-element></literal> tag.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="someNames" table="some_names" lazy="true">
|
||||||
|
<key column="id"/>
|
||||||
|
<composite-element class="eg.Name"> <!-- class attribute required -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note: if you define a <literal>Set</literal> of composite elements, it is
|
||||||
|
very important to implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal> correctly.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Composite elements may contain components but not collections. If your
|
||||||
|
composite element itself contains
|
||||||
|
components, use the <literal><nested-composite-element></literal>
|
||||||
|
tag. This is a pretty exotic case - a collection of components which
|
||||||
|
themselves have components. By this stage you should be asking yourself
|
||||||
|
if a one-to-many association is more appropriate. Try remodelling the
|
||||||
|
composite element as an entity - but note that even though the Java model
|
||||||
|
is the same, the relational model and persistence semantics are still
|
||||||
|
slightly different.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Please note that a composite element mapping doesn't support null-able properties
|
||||||
|
if you're using a <literal><set></literal>. Hibernate
|
||||||
|
has to use each columns value to identify a record when deleting objects
|
||||||
|
(there is no separate primary key column in the composite element table),
|
||||||
|
which is not possible with null values. You have to either use only
|
||||||
|
not-null properties in a composite-element or choose a
|
||||||
|
<literal><list></literal>, <literal><map></literal>,
|
||||||
|
<literal><bag></literal> or <literal><idbag></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A special case of a composite element is a composite element with a nested
|
||||||
|
<literal><many-to-one></literal> element. A mapping like this allows
|
||||||
|
you to map extra columns of a many-to-many association table to the
|
||||||
|
composite element class. The following is a many-to-many association
|
||||||
|
from <literal>Order</literal> to <literal>Item</literal> where
|
||||||
|
<literal>purchaseDate</literal>, <literal>price</literal> and
|
||||||
|
<literal>quantity</literal> are properties of the association:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.Purchase">
|
||||||
|
<property name="purchaseDate"/>
|
||||||
|
<property name="price"/>
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Of course, there can't be a reference to the purchae on the other side, for
|
||||||
|
bidirectional association navigation. Remember that components are value types and
|
||||||
|
don't allow shared references. A single <literal>Purchase</literal> can be in the
|
||||||
|
set of an <literal>Order</literal>, but it can't be referenced by the <literal>Item</literal>
|
||||||
|
at the same time.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>Even ternary (or quaternary, etc) associations are possible:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.OrderLine">
|
||||||
|
<many-to-one name="purchaseDetails class="eg.Purchase"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Composite elements may appear in queries using the same syntax as
|
||||||
|
associations to other entities.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-asmapindex">
|
||||||
|
<title>Components as Map indices</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal><composite-map-key></literal> element lets you map a
|
||||||
|
component class as the key of a <literal>Map</literal>. Make sure you override
|
||||||
|
<literal>hashCode()</literal> and <literal>equals()</literal> correctly on
|
||||||
|
the component class.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-compositeid" revision="1">
|
||||||
|
<title>Components as composite identifiers</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may use a component as an identifier of an entity class. Your component
|
||||||
|
class must satisfy certain requirements:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It must implement <literal>java.io.Serializable</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
It must re-implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal>, consistently with the database's
|
||||||
|
notion of composite key equality.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Note: in Hibernate3, the second requirement is not an absolutely hard
|
||||||
|
requirement of Hibernate. But do it anyway.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can't use an <literal>IdentifierGenerator</literal> to generate composite keys.
|
||||||
|
Instead the application must assign its own identifiers.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Use the <literal><composite-id></literal> tag (with nested
|
||||||
|
<literal><key-property></literal> elements) in place of the usual
|
||||||
|
<literal><id></literal> declaration. For example, the
|
||||||
|
<literal>OrderLine</literal> class has a primary key that depends upon
|
||||||
|
the (composite) primary key of <literal>Order</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
|
||||||
|
<composite-id name="id" class="OrderLineId">
|
||||||
|
<key-property name="lineId"/>
|
||||||
|
<key-property name="orderId"/>
|
||||||
|
<key-property name="customerId"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="name"/>
|
||||||
|
|
||||||
|
<many-to-one name="order" class="Order"
|
||||||
|
insert="false" update="false">
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>
|
||||||
|
....
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now, any foreign keys referencing the <literal>OrderLine</literal> table are also
|
||||||
|
composite. You must declare this in your mappings for other classes. An association
|
||||||
|
to <literal>OrderLine</literal> would be mapped like this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="orderLine" class="OrderLine">
|
||||||
|
<!-- the "class" attribute is optional, as usual -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(Note that the <literal><column></literal> tag is an alternative to the
|
||||||
|
<literal>column</literal> attribute everywhere.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>many-to-many</literal> association to <literal>OrderLine</literal> also
|
||||||
|
uses the composite foreign key:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="undeliveredOrderLines">
|
||||||
|
<key column name="warehouseId"/>
|
||||||
|
<many-to-many class="OrderLine">
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-many>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The collection of <literal>OrderLine</literal>s in <literal>Order</literal> would
|
||||||
|
use:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="orderLines" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="OrderLine"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(The <literal><one-to-many></literal> element, as usual, declares no columns.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If <literal>OrderLine</literal> itself owns a collection, it also has a composite
|
||||||
|
foreign key.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
....
|
||||||
|
....
|
||||||
|
<list name="deliveryAttempts">
|
||||||
|
<key> <!-- a collection inherits the composite key type -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<list-index column="attemptId" base="1"/>
|
||||||
|
<composite-element class="DeliveryAttempt">
|
||||||
|
...
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-dynamic" revision="1">
|
||||||
|
<title>Dynamic components</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may even map a property of type <literal>Map</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<dynamic-component name="userAttributes">
|
||||||
|
<property name="foo" column="FOO" type="string"/>
|
||||||
|
<property name="bar" column="BAR" type="integer"/>
|
||||||
|
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
|
||||||
|
</dynamic-component>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The semantics of a <literal><dynamic-component></literal> mapping are identical
|
||||||
|
to <literal><component></literal>. The advantage of this kind of mapping is
|
||||||
|
the ability to determine the actual properties of the bean at deployment time, just
|
||||||
|
by editing the mapping document. Runtime manipulation of the mapping document is
|
||||||
|
also possible, using a DOM parser. Even better, you can access (and change) Hibernate's
|
||||||
|
configuration-time metamodel via the <literal>Configuration</literal> object.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,265 @@
|
||||||
|
<chapter id="events">
|
||||||
|
<title>Interceptors and events</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is often useful for the application to react to certain events that occur
|
||||||
|
inside Hibernate. This allows implementation of certain kinds of generic
|
||||||
|
functionality, and extension of Hibernate functionality.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-interceptors" revision="3">
|
||||||
|
<title>Interceptors</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>Interceptor</literal> interface provides callbacks from the session to the
|
||||||
|
application allowing the application to inspect and/or manipulate properties of a
|
||||||
|
persistent object before it is saved, updated, deleted or loaded. One
|
||||||
|
possible use for this is to track auditing information. For example, the following
|
||||||
|
<literal>Interceptor</literal> automatically sets the <literal>createTimestamp</literal>
|
||||||
|
when an <literal>Auditable</literal> is created and updates the
|
||||||
|
<literal>lastUpdateTimestamp</literal> property when an <literal>Auditable</literal> is
|
||||||
|
updated.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may either implement <literal>Interceptor</literal> directly or (better) extend
|
||||||
|
<literal>EmptyInterceptor</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package org.hibernate.test;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.hibernate.EmptyInterceptor;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
public class AuditInterceptor extends EmptyInterceptor {
|
||||||
|
|
||||||
|
private int updates;
|
||||||
|
private int creates;
|
||||||
|
private int loads;
|
||||||
|
|
||||||
|
public void onDelete(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFlushDirty(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] currentState,
|
||||||
|
Object[] previousState,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
updates++;
|
||||||
|
for ( int i=0; i < propertyNames.length; i++ ) {
|
||||||
|
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
currentState[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
loads++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
creates++;
|
||||||
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
||||||
|
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
state[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterTransactionCompletion(Transaction tx) {
|
||||||
|
if ( tx.wasCommitted() ) {
|
||||||
|
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
|
||||||
|
}
|
||||||
|
updates=0;
|
||||||
|
creates=0;
|
||||||
|
loads=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Interceptors come in two flavors: <literal>Session</literal>-scoped and
|
||||||
|
<literal>SessionFactory</literal>-scoped.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>Session</literal>-scoped interceptor is specified
|
||||||
|
when a session is opened using one of the overloaded SessionFactory.openSession()
|
||||||
|
methods accepting an <literal>Interceptor</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>SessionFactory</literal>-scoped interceptor is registered with the <literal>Configuration</literal>
|
||||||
|
object prior to building the <literal>SessionFactory</literal>. In this case, the supplied interceptor
|
||||||
|
will be applied to all sessions opened from that <literal>SessionFactory</literal>; this is true unless
|
||||||
|
a session is opened explicitly specifying the interceptor to use. <literal>SessionFactory</literal>-scoped
|
||||||
|
interceptors must be thread safe, taking care to not store session-specific state since multiple
|
||||||
|
sessions will use this interceptor (potentially) concurrently.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-events" revision="4">
|
||||||
|
<title>Event system</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you have to react to particular events in your persistence layer, you may
|
||||||
|
also use the Hibernate3 <emphasis>event</emphasis> architecture. The event
|
||||||
|
system can be used in addition or as a replacement for interceptors.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Essentially all of the methods of the <literal>Session</literal> interface correlate
|
||||||
|
to an event. You have a <literal>LoadEvent</literal>, a <literal>FlushEvent</literal>, etc
|
||||||
|
(consult the XML configuration-file DTD or the <literal>org.hibernate.event</literal>
|
||||||
|
package for the full list of defined event types). When a request is made of one of
|
||||||
|
these methods, the Hibernate <literal>Session</literal> generates an appropriate
|
||||||
|
event and passes it to the configured event listeners for that type. Out-of-the-box,
|
||||||
|
these listeners implement the same processing in which those methods always resulted.
|
||||||
|
However, you are free to implement a customization of one of the listener interfaces
|
||||||
|
(i.e., the <literal>LoadEvent</literal> is processed by the registered implemenation
|
||||||
|
of the <literal>LoadEventListener</literal> interface), in which case their
|
||||||
|
implementation would be responsible for processing any <literal>load()</literal> requests
|
||||||
|
made of the <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The listeners should be considered effectively singletons; meaning, they are shared between
|
||||||
|
requests, and thus should not save any state as instance variables.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A custom listener should implement the appropriate interface for the event it wants to
|
||||||
|
process and/or extend one of the convenience base classes (or even the default event
|
||||||
|
listeners used by Hibernate out-of-the-box as these are declared non-final for this
|
||||||
|
purpose). Custom listeners can either be registered programmatically through the
|
||||||
|
<literal>Configuration</literal> object, or specified in the Hibernate configuration
|
||||||
|
XML (declarative configuration through the properties file is not supported). Here's an
|
||||||
|
example of a custom load event listener:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
|
||||||
|
// this is the single method defined by the LoadEventListener interface
|
||||||
|
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||||||
|
throws HibernateException {
|
||||||
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||||||
|
throw MySecurityException("Unauthorized access");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You also need a configuration entry telling Hibernate to use the listener in addition
|
||||||
|
to the default listener:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-configuration>
|
||||||
|
<session-factory>
|
||||||
|
...
|
||||||
|
<event type="load">
|
||||||
|
<listener class="com.eg.MyLoadListener"/>
|
||||||
|
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
|
||||||
|
</event>
|
||||||
|
</session-factory>
|
||||||
|
</hibernate-configuration>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Instead, you may register it programmatically:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||||||
|
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
|
||||||
|
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Listeners registered declaratively cannot share instances. If the same class name is
|
||||||
|
used in multiple <literal><listener/></literal> elements, each reference will
|
||||||
|
result in a separate instance of that class. If you need the capability to share
|
||||||
|
listener instances between listener types you must use the programmatic registration
|
||||||
|
approach.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Why implement an interface and define the specific type during configuration? Well, a
|
||||||
|
listener implementation could implement multiple event listener interfaces. Having the
|
||||||
|
type additionally defined during registration makes it easier to turn custom listeners on
|
||||||
|
or off during configuration.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-decl-security" revision="2">
|
||||||
|
<title>Hibernate declarative security</title>
|
||||||
|
<para>
|
||||||
|
Usually, declarative security in Hibernate applications is managed in a session facade
|
||||||
|
layer. Now, Hibernate3 allows certain actions to be permissioned via JACC, and authorized
|
||||||
|
via JAAS. This is optional functionality built on top of the event architecture.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
First, you must configure the appropriate event listeners, to enable the use of JAAS
|
||||||
|
authorization.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||||||
|
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||||||
|
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||||||
|
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that <literal><listener type="..." class="..."/></literal> is just a shorthand
|
||||||
|
for <literal><event type="..."><listener class="..."/></event></literal>
|
||||||
|
when there is exactly one listener for a particular event type.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Next, still in <literal>hibernate.cfg.xml</literal>, bind the permissions to roles:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||||||
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The role names are the roles understood by your JACC provider.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,658 @@
|
||||||
|
<chapter id="example-mappings">
|
||||||
|
<title>Example: Various Mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This chapters shows off some more complex association mappings.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-emp">
|
||||||
|
<title>Employer/Employee</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following model of the relationship between <literal>Employer</literal> and
|
||||||
|
<literal>Employee</literal> uses an actual entity class (<literal>Employment</literal>)
|
||||||
|
to represent the association. This is done because there might be more than one
|
||||||
|
period of employment for the same two parties. Components are used to model monetary
|
||||||
|
values and employee names.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Heres a possible mapping document:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Employer" table="employers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employer_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employment" table="employment_periods">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employment_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="startDate" column="start_date"/>
|
||||||
|
<property name="endDate" column="end_date"/>
|
||||||
|
|
||||||
|
<component name="hourlyRate" class="MonetaryAmount">
|
||||||
|
<property name="amount">
|
||||||
|
<column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
|
||||||
|
</property>
|
||||||
|
<property name="currency" length="12"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<many-to-one name="employer" column="employer_id" not-null="true"/>
|
||||||
|
<many-to-one name="employee" column="employee_id" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employee" table="employees">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employee_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="taxfileNumber"/>
|
||||||
|
<component name="name" class="Name">
|
||||||
|
<property name="firstName"/>
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="lastName"/>
|
||||||
|
</component>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
And heres the table schema generated by <literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table employers (
|
||||||
|
id BIGINT not null,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employment_periods (
|
||||||
|
id BIGINT not null,
|
||||||
|
hourly_rate NUMERIC(12, 2),
|
||||||
|
currency VARCHAR(12),
|
||||||
|
employee_id BIGINT not null,
|
||||||
|
employer_id BIGINT not null,
|
||||||
|
end_date TIMESTAMP,
|
||||||
|
start_date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employees (
|
||||||
|
id BIGINT not null,
|
||||||
|
firstName VARCHAR(255),
|
||||||
|
initial CHAR(1),
|
||||||
|
lastName VARCHAR(255),
|
||||||
|
taxfileNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK0 foreign key (employer_id) references employers
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK1 foreign key (employee_id) references employees
|
||||||
|
create sequence employee_id_seq
|
||||||
|
create sequence employment_id_seq
|
||||||
|
create sequence employer_id_seq]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-authorwork">
|
||||||
|
<title>Author/Work</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Consider the following model of the relationships between <literal>Work</literal>,
|
||||||
|
<literal>Author</literal> and <literal>Person</literal>. We represent the relationship
|
||||||
|
between <literal>Work</literal> and <literal>Author</literal> as a many-to-many
|
||||||
|
association. We choose to represent the relationship between <literal>Author</literal>
|
||||||
|
and <literal>Person</literal> as one-to-one association. Another possibility would be to
|
||||||
|
have <literal>Author</literal> extend <literal>Person</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following mapping document correctly represents these relationships:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Work" table="works" discriminator-value="W">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="type" type="character"/>
|
||||||
|
|
||||||
|
<property name="title"/>
|
||||||
|
<set name="authors" table="author_work">
|
||||||
|
<key column name="work_id"/>
|
||||||
|
<many-to-many class="Author" column name="author_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<subclass name="Book" discriminator-value="B">
|
||||||
|
<property name="text"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Song" discriminator-value="S">
|
||||||
|
<property name="tempo"/>
|
||||||
|
<property name="genre"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Author" table="authors">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<!-- The Author must have the same identifier as the Person -->
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="alias"/>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
|
||||||
|
<set name="works" table="author_work" inverse="true">
|
||||||
|
<key column="author_id"/>
|
||||||
|
<many-to-many class="Work" column="work_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Person" table="persons">
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are four tables in this mapping. <literal>works</literal>,
|
||||||
|
<literal>authors</literal> and <literal>persons</literal> hold work, author
|
||||||
|
and person data respectively. <literal>author_work</literal> is an association
|
||||||
|
table linking authors to works. Heres the table schema, as generated by
|
||||||
|
<literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table works (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
tempo FLOAT,
|
||||||
|
genre VARCHAR(255),
|
||||||
|
text INTEGER,
|
||||||
|
title VARCHAR(255),
|
||||||
|
type CHAR(1) not null,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table author_work (
|
||||||
|
author_id BIGINT not null,
|
||||||
|
work_id BIGINT not null,
|
||||||
|
primary key (work_id, author_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table authors (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
alias VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table persons (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table authors
|
||||||
|
add constraint authorsFK0 foreign key (id) references persons
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK0 foreign key (author_id) references authors
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK1 foreign key (work_id) references works]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-customerorderproduct">
|
||||||
|
<title>Customer/Order/Product</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now consider a model of the relationships between <literal>Customer</literal>,
|
||||||
|
<literal>Order</literal> and <literal>LineItem</literal> and <literal>Product</literal>.
|
||||||
|
There is a one-to-many association between <literal>Customer</literal> and
|
||||||
|
<literal>Order</literal>, but how should we represent <literal>Order</literal> /
|
||||||
|
<literal>LineItem</literal> / <literal>Product</literal>? I've chosen to map
|
||||||
|
<literal>LineItem</literal> as an association class representing the many-to-many
|
||||||
|
association between <literal>Order</literal> and <literal>Product</literal>. In
|
||||||
|
Hibernate, this is called a composite element.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The mapping document:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Customer" table="customers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
<set name="orders" inverse="true">
|
||||||
|
<key column="customer_id"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="orders">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="date"/>
|
||||||
|
<many-to-one name="customer" column="customer_id"/>
|
||||||
|
<list name="lineItems" table="line_items">
|
||||||
|
<key column="order_id"/>
|
||||||
|
<list-index column="line_number"/>
|
||||||
|
<composite-element class="LineItem">
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="product" column="product_id"/>
|
||||||
|
</composite-element>
|
||||||
|
</list>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product" table="products">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="serialNumber"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>customers</literal>, <literal>orders</literal>, <literal>line_items</literal> and
|
||||||
|
<literal>products</literal> hold customer, order, order line item and product data
|
||||||
|
respectively. <literal>line_items</literal> also acts as an association table linking
|
||||||
|
orders with products.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table customers (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table orders (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
customer_id BIGINT,
|
||||||
|
date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table line_items (
|
||||||
|
line_number INTEGER not null,
|
||||||
|
order_id BIGINT not null,
|
||||||
|
product_id BIGINT,
|
||||||
|
quantity INTEGER,
|
||||||
|
primary key (order_id, line_number)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table products (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
serialNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table orders
|
||||||
|
add constraint ordersFK0 foreign key (customer_id) references customers
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK0 foreign key (product_id) references products
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK1 foreign key (order_id) references orders]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="misc">
|
||||||
|
<title>Miscellaneous example mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
These examples are all taken from the Hibernate test suite. You
|
||||||
|
will find many other useful example mappings there. Look in the
|
||||||
|
<literal>test</literal> folder of the Hibernate distribution.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>TODO: put words around this stuff</para>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-typed-onetone">
|
||||||
|
<title>"Typed" one-to-one association</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="name"/>
|
||||||
|
<one-to-one name="address"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'HOME'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
<one-to-one name="mailingAddress"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'MAILING'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address" batch-size="2"
|
||||||
|
check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
|
||||||
|
<composite-id>
|
||||||
|
<key-many-to-one name="person"
|
||||||
|
column="personName"/>
|
||||||
|
<key-property name="type"
|
||||||
|
column="addressType"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="street" type="text"/>
|
||||||
|
<property name="state"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key">
|
||||||
|
<title>Composite key example</title>
|
||||||
|
<programlisting><![CDATA[<class name="Customer">
|
||||||
|
|
||||||
|
<id name="customerId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" not-null="true" length="100"/>
|
||||||
|
<property name="address" not-null="true" length="200"/>
|
||||||
|
|
||||||
|
<list name="orders"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key column="customerId"/>
|
||||||
|
<index column="orderNumber"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="CustomerOrder" lazy="true">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
<synchronize table="Product"/>
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="Order$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="orderDate"
|
||||||
|
type="calendar_date"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property name="total">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity*p.price)
|
||||||
|
from LineItem li, Product p
|
||||||
|
where li.productId = p.productId
|
||||||
|
and li.customerId = customerId
|
||||||
|
and li.orderNumber = orderNumber )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<many-to-one name="customer"
|
||||||
|
column="customerId"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<bag name="lineItems"
|
||||||
|
fetch="join"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key>
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="LineItem"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="LineItem">
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="LineItem$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
<key-property name="productId" length="10"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="quantity"/>
|
||||||
|
|
||||||
|
<many-to-one name="order"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true">
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</many-to-one>
|
||||||
|
|
||||||
|
<many-to-one name="product"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"
|
||||||
|
column="productId"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
|
||||||
|
<id name="productId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="description"
|
||||||
|
not-null="true"
|
||||||
|
length="200"/>
|
||||||
|
<property name="price" length="3"/>
|
||||||
|
<property name="numberAvailable"/>
|
||||||
|
|
||||||
|
<property name="numberOrdered">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity)
|
||||||
|
from LineItem li
|
||||||
|
where li.productId = productId )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key-manytomany">
|
||||||
|
<title>Many-to-many with shared composite key attribute</title>
|
||||||
|
<programlisting><![CDATA[<class name="User" table="`User`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<set name="groups" table="UserGroup">
|
||||||
|
<key>
|
||||||
|
<column name="userName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="Group">
|
||||||
|
<column name="groupName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Group" table="`Group`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="description"/>
|
||||||
|
<set name="users" table="UserGroup" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="groupName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="User">
|
||||||
|
<column name="userName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-content-discrimination">
|
||||||
|
<title>Content based discrimination</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person"
|
||||||
|
discriminator-value="P">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="person_id"
|
||||||
|
unsaved-value="0">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
|
||||||
|
<discriminator
|
||||||
|
type="character">
|
||||||
|
<formula>
|
||||||
|
case
|
||||||
|
when title is not null then 'E'
|
||||||
|
when salesperson is not null then 'C'
|
||||||
|
else 'P'
|
||||||
|
end
|
||||||
|
</formula>
|
||||||
|
</discriminator>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
not-null="true"
|
||||||
|
length="80"/>
|
||||||
|
|
||||||
|
<property name="sex"
|
||||||
|
not-null="true"
|
||||||
|
update="false"/>
|
||||||
|
|
||||||
|
<component name="address">
|
||||||
|
<property name="address"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
<property name="country"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<subclass name="Employee"
|
||||||
|
discriminator-value="E">
|
||||||
|
<property name="title"
|
||||||
|
length="20"/>
|
||||||
|
<property name="salary"/>
|
||||||
|
<many-to-one name="manager"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Customer"
|
||||||
|
discriminator-value="C">
|
||||||
|
<property name="comments"/>
|
||||||
|
<many-to-one name="salesperson"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-association-alternatekeys" revision="2">
|
||||||
|
<title>Associations on alternate keys</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" length="100"/>
|
||||||
|
|
||||||
|
<one-to-one name="address"
|
||||||
|
property-ref="person"
|
||||||
|
cascade="all"
|
||||||
|
fetch="join"/>
|
||||||
|
|
||||||
|
<set name="accounts"
|
||||||
|
inverse="true">
|
||||||
|
<key column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
<one-to-many class="Account"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<property name="userId" length="8"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="address" length="300"/>
|
||||||
|
<property name="zip" length="5"/>
|
||||||
|
<property name="country" length="25"/>
|
||||||
|
<many-to-one name="person" unique="true" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Account">
|
||||||
|
<id name="accountId" length="32">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<many-to-one name="user"
|
||||||
|
column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
|
||||||
|
<property name="type" not-null="true"/>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,361 @@
|
||||||
|
<chapter id="example-parentchild">
|
||||||
|
<title>Example: Parent/Child</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
One of the very first things that new users try to do with Hibernate is to model a parent / child type
|
||||||
|
relationship. There are two different approaches to this. For various reasons the most convenient
|
||||||
|
approach, especially for new users, is to model both <literal>Parent</literal> and <literal>Child</literal>
|
||||||
|
as entity classes with a <literal><one-to-many></literal> association from <literal>Parent</literal>
|
||||||
|
to <literal>Child</literal>. (The alternative approach is to declare the <literal>Child</literal> as a
|
||||||
|
<literal><composite-element></literal>.) Now, it turns out that default semantics of a one to many
|
||||||
|
association (in Hibernate) are much less close to the usual semantics of a parent / child relationship than
|
||||||
|
those of a composite element mapping. We will explain how to use a <emphasis>bidirectional one to many
|
||||||
|
association with cascades</emphasis> to model a parent / child relationship efficiently and elegantly.
|
||||||
|
It's not at all difficult!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-collections">
|
||||||
|
<title>A note about collections</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate collections are considered to be a logical part of their owning entity; never of the
|
||||||
|
contained entities. This is a crucial distinction! It has the following consequences:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
When we remove / add an object from / to a collection, the version number of the collection owner
|
||||||
|
is incremented.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If an object that was removed from a collection is an instance of a value type (eg, a composite
|
||||||
|
element), that object will cease to be persistent and its state will be completely removed from
|
||||||
|
the database. Likewise, adding a value type instance to the collection will cause its state to be
|
||||||
|
immediately persistent.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
On the other hand, if an entity is removed from a collection (a one-to-many or many-to-many
|
||||||
|
association), it will not be deleted, by default. This behaviour is completely consistent - a
|
||||||
|
change to the internal state of another entity should not cause the associated entity to vanish!
|
||||||
|
Likewise, adding an entity to a collection does not cause that entity to become persistent, by
|
||||||
|
default.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Instead, the default behaviour is that adding an entity to a collection merely creates a link between
|
||||||
|
the two entities, while removing it removes the link. This is very appropriate for all sorts of cases.
|
||||||
|
Where it is not appropriate at all is the case of a parent / child relationship, where the life of the
|
||||||
|
child is bound to the lifecycle of the parent.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-bidir">
|
||||||
|
<title>Bidirectional one-to-many</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Suppose we start with a simple <literal><one-to-many></literal> association from
|
||||||
|
<literal>Parent</literal> to <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If we were to execute the following code
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = .....;
|
||||||
|
Child c = new Child();
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate would issue two SQL statements:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>an <literal>INSERT</literal> to create the record for <literal>c</literal></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
an <literal>UPDATE</literal> to create the link from <literal>p</literal> to
|
||||||
|
<literal>c</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This is not only inefficient, but also violates any <literal>NOT NULL</literal> constraint on the
|
||||||
|
<literal>parent_id</literal> column. We can fix the nullability constraint violation by specifying
|
||||||
|
<literal>not-null="true"</literal> in the collection mapping:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id" not-null="true"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
However, this is not the recommended solution.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The underlying cause of this behaviour is that the link (the foreign key <literal>parent_id</literal>)
|
||||||
|
from <literal>p</literal> to <literal>c</literal> is not considered part of the state of the
|
||||||
|
<literal>Child</literal> object and is therefore not created in the <literal>INSERT</literal>. So the
|
||||||
|
solution is to make the link part of the <literal>Child</literal> mapping.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(We also need to add the <literal>parent</literal> property to the <literal>Child</literal> class.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now that the <literal>Child</literal> entity is managing the state of the link, we tell the collection
|
||||||
|
not to update the link. We use the <literal>inverse</literal> attribute.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following code would be used to add a new <literal>Child</literal>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
c.setParent(p);
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
And now, only one SQL <literal>INSERT</literal> would be issued!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
To tighten things up a bit, we could create an <literal>addChild()</literal> method of
|
||||||
|
<literal>Parent</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public void addChild(Child c) {
|
||||||
|
c.setParent(this);
|
||||||
|
children.add(c);
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now, the code to add a <literal>Child</literal> looks like
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-cascades">
|
||||||
|
<title>Cascading lifecycle</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The explicit call to <literal>save()</literal> is still annoying. We will address this by
|
||||||
|
using cascades.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This simplifies the code above to
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Similarly, we don't need to iterate over the children when saving or deleting a <literal>Parent</literal>.
|
||||||
|
The following removes <literal>p</literal> and all its children from the database.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
session.delete(p);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
However, this code
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
c.setParent(null);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
will not remove <literal>c</literal> from the database; it will ony remove the link to <literal>p</literal>
|
||||||
|
(and cause a <literal>NOT NULL</literal> constraint violation, in this case). You need to explicitly
|
||||||
|
<literal>delete()</literal> the <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
session.delete(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now, in our case, a <literal>Child</literal> can't really exist without its parent. So if we remove
|
||||||
|
a <literal>Child</literal> from the collection, we really do want it to be deleted. For this, we must
|
||||||
|
use <literal>cascade="all-delete-orphan"</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note: even though the collection mapping specifies <literal>inverse="true"</literal>, cascades are
|
||||||
|
still processed by iterating the collection elements. So if you require that an object be saved,
|
||||||
|
deleted or updated by cascade, you must add it to the collection. It is not enough to simply call
|
||||||
|
<literal>setParent()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-update">
|
||||||
|
<title>Cascades and <literal>unsaved-value</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Suppose we loaded up a <literal>Parent</literal> in one <literal>Session</literal>, made some changes
|
||||||
|
in a UI action and wish to persist these changes in a new session by calling <literal>update()</literal>.
|
||||||
|
The <literal>Parent</literal> will contain a collection of childen and, since cascading update is enabled,
|
||||||
|
Hibernate needs to know which children are newly instantiated and which represent existing rows in the
|
||||||
|
database. Lets assume that both <literal>Parent</literal> and <literal>Child</literal> have genenerated
|
||||||
|
identifier properties of type <literal>Long</literal>. Hibernate will use the identifier and
|
||||||
|
version/timestamp property value to determine which of the children are new. (See
|
||||||
|
<xref linkend="objectstate-saveorupdate"/>.) <emphasis>In Hibernate3, it is no longer necessary to specify
|
||||||
|
an <literal>unsaved-value</literal> explicitly.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following code will update <literal>parent</literal> and <literal>child</literal> and insert
|
||||||
|
<literal>newChild</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[//parent and child were both loaded in a previous session
|
||||||
|
parent.addChild(child);
|
||||||
|
Child newChild = new Child();
|
||||||
|
parent.addChild(newChild);
|
||||||
|
session.update(parent);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Well, that's all very well for the case of a generated identifier, but what about assigned identifiers
|
||||||
|
and composite identifiers? This is more difficult, since Hibernate can't use the identifier property to
|
||||||
|
distinguish between a newly instantiated object (with an identifier assigned by the user) and an
|
||||||
|
object loaded in a previous session. In this case, Hibernate will either use the timestamp or version
|
||||||
|
property, or will actually query the second-level cache or, worst case, the database, to see if the
|
||||||
|
row exists.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<!-- undocumenting
|
||||||
|
<para>
|
||||||
|
There is one further possibility. The <literal>Interceptor</literal> method named
|
||||||
|
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
||||||
|
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Persistent {
|
||||||
|
private boolean _saved = false;
|
||||||
|
public void onSave() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
public void onLoad() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
public boolean isSaved() {
|
||||||
|
return _saved;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(The <literal>saved</literal> property is non-persistent.)
|
||||||
|
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||||||
|
and <literal>onSave()</literal> as follows.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||||||
|
if (entity instanceof Persistent) {
|
||||||
|
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||||||
|
return false;
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
||||||
|
</para>
|
||||||
|
-->
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-conclusion">
|
||||||
|
<title>Conclusion</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There is quite a bit to digest here and it might look confusing first time around. However, in practice,
|
||||||
|
it all works out very nicely. Most Hibernate applications use the parent / child pattern in many places.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We mentioned an alternative in the first paragraph. None of the above issues exist in the case of
|
||||||
|
<literal><composite-element></literal> mappings, which have exactly the semantics of a parent / child
|
||||||
|
relationship. Unfortunately, there are two big limitations to composite element classes: composite elements
|
||||||
|
may not own collections, and they should not be the child of any entity other than the unique parent.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,430 @@
|
||||||
|
<chapter id="example-weblog">
|
||||||
|
<title>Example: Weblog Application</title>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-classes">
|
||||||
|
<title>Persistent Classes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The persistent classes represent a weblog, and an item posted
|
||||||
|
in a weblog. They are to be modelled as a standard parent/child
|
||||||
|
relationship, but we will use an ordered bag, instead of a set.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Blog {
|
||||||
|
private Long _id;
|
||||||
|
private String _name;
|
||||||
|
private List _items;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public List getItems() {
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setItems(List list) {
|
||||||
|
_items = list;
|
||||||
|
}
|
||||||
|
public void setName(String string) {
|
||||||
|
_name = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class BlogItem {
|
||||||
|
private Long _id;
|
||||||
|
private Calendar _datetime;
|
||||||
|
private String _text;
|
||||||
|
private String _title;
|
||||||
|
private Blog _blog;
|
||||||
|
|
||||||
|
public Blog getBlog() {
|
||||||
|
return _blog;
|
||||||
|
}
|
||||||
|
public Calendar getDatetime() {
|
||||||
|
return _datetime;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public String getText() {
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
public String getTitle() {
|
||||||
|
return _title;
|
||||||
|
}
|
||||||
|
public void setBlog(Blog blog) {
|
||||||
|
_blog = blog;
|
||||||
|
}
|
||||||
|
public void setDatetime(Calendar calendar) {
|
||||||
|
_datetime = calendar;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setText(String string) {
|
||||||
|
_text = string;
|
||||||
|
}
|
||||||
|
public void setTitle(String string) {
|
||||||
|
_title = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-mappings">
|
||||||
|
<title>Hibernate Mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The XML mappings should now be quite straightforward.
|
||||||
|
</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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="Blog"
|
||||||
|
table="BLOGS">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="name"
|
||||||
|
column="NAME"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
|
||||||
|
<bag
|
||||||
|
name="items"
|
||||||
|
inverse="true"
|
||||||
|
order-by="DATE_TIME"
|
||||||
|
cascade="all">
|
||||||
|
|
||||||
|
<key column="BLOG_ID"/>
|
||||||
|
<one-to-many class="BlogItem"/>
|
||||||
|
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="BlogItem"
|
||||||
|
table="BLOG_ITEMS"
|
||||||
|
dynamic-update="true">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ITEM_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="title"
|
||||||
|
column="TITLE"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="text"
|
||||||
|
column="TEXT"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="datetime"
|
||||||
|
column="DATE_TIME"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<many-to-one
|
||||||
|
name="blog"
|
||||||
|
column="BLOG_ID"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-code">
|
||||||
|
<title>Hibernate Code</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following class demonstrates some of the kinds of things
|
||||||
|
we can do with these classes, using Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.Query;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
|
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||||
|
|
||||||
|
public class BlogMain {
|
||||||
|
|
||||||
|
private SessionFactory _sessions;
|
||||||
|
|
||||||
|
public void configure() throws HibernateException {
|
||||||
|
_sessions = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class)
|
||||||
|
.buildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportTables() throws HibernateException {
|
||||||
|
Configuration cfg = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class);
|
||||||
|
new SchemaExport(cfg).create(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog createBlog(String name) throws HibernateException {
|
||||||
|
|
||||||
|
Blog blog = new Blog();
|
||||||
|
blog.setName(name);
|
||||||
|
blog.setItems( new ArrayList() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.persist(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Blog blog, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setBlog(blog);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
blog.getItems().add(item);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Long blogid, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Blog blog = (Blog) session.load(Blog.class, blogid);
|
||||||
|
item.setBlog(blog);
|
||||||
|
blog.getItems().add(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(BlogItem item, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
item.setText(text);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(Long itemid, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
BlogItem item = (BlogItem) session.load(BlogItem.class, itemid);
|
||||||
|
item.setText(text);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listAllBlogNamesAndItemCounts(int max)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"select blog.id, blog.name, count(blogItem) " +
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join blog.items as blogItem " +
|
||||||
|
"group by blog.name, blog.id " +
|
||||||
|
"order by max(blogItem.datetime)"
|
||||||
|
);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog getBlogAndAllItems(Long blogid)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
Blog blog = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join fetch blog.items " +
|
||||||
|
"where blog.id = :blogid"
|
||||||
|
);
|
||||||
|
q.setParameter("blogid", blogid);
|
||||||
|
blog = (Blog) q.uniqueResult();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listBlogsAndRecentItems() throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"inner join blog.items as blogItem " +
|
||||||
|
"where blogItem.datetime > :minDate"
|
||||||
|
);
|
||||||
|
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.roll(Calendar.MONTH, false);
|
||||||
|
q.setCalendar("minDate", cal);
|
||||||
|
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
<chapter id="filters">
|
||||||
|
<title>Filtering data</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 provides an innovative new approach to handling data with "visibility" rules.
|
||||||
|
A <emphasis>Hibernate filter</emphasis> is a global, named, parameterized filter that may be
|
||||||
|
enabled or disabled for a particular Hibernate session.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-filters" revision="1">
|
||||||
|
<title>Hibernate filters</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 adds the ability to pre-define filter criteria and attach those filters at both
|
||||||
|
a class and a collection level. A filter criteria is the ability to define a restriction clause
|
||||||
|
very similiar to the existing "where" attribute available on the class and various collection
|
||||||
|
elements. Except these filter conditions can be parameterized. The application can then make
|
||||||
|
the decision at runtime whether given filters should be enabled and what their parameter
|
||||||
|
values should be. Filters can be used like database views, but parameterized inside the
|
||||||
|
application.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In order to use filters, they must first be defined and then attached to the appropriate
|
||||||
|
mapping elements. To define a filter, use the <literal><filter-def/></literal> element
|
||||||
|
within a <literal><hibernate-mapping/></literal> element:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="myFilter">
|
||||||
|
<filter-param name="myFilterParam" type="string"/>
|
||||||
|
</filter-def>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Then, this filter can be attached to a class:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="myClass" ...>
|
||||||
|
...
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
or, to a collection:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set ...>
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
or, even to both (or multiples of each) at the same time.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The methods on <literal>Session</literal> are: <literal>enableFilter(String filterName)</literal>,
|
||||||
|
<literal>getEnabledFilter(String filterName)</literal>, and <literal>disableFilter(String filterName)</literal>.
|
||||||
|
By default, filters are <emphasis>not</emphasis> enabled for a given session; they must be explcitly
|
||||||
|
enabled through use of the <literal>Session.enabledFilter()</literal> method, which returns an
|
||||||
|
instance of the <literal>Filter</literal> interface. Using the simple filter defined above, this
|
||||||
|
would look like:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that methods on the org.hibernate.Filter interface do allow the method-chaining common to much of Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A full example, using temporal data with an effective record date pattern:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="effectiveDate">
|
||||||
|
<filter-param name="asOfDate" type="date"/>
|
||||||
|
</filter-def>
|
||||||
|
|
||||||
|
<class name="Employee" ...>
|
||||||
|
...
|
||||||
|
<many-to-one name="department" column="dept_id" class="Department"/>
|
||||||
|
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
|
||||||
|
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
|
||||||
|
...
|
||||||
|
<!--
|
||||||
|
Note that this assumes non-terminal records have an eff_end_dt set to
|
||||||
|
a max db date for simplicity-sake
|
||||||
|
-->
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Department" ...>
|
||||||
|
...
|
||||||
|
<set name="employees" lazy="true">
|
||||||
|
<key column="dept_id"/>
|
||||||
|
<one-to-many class="Employee"/>
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Then, in order to ensure that you always get back currently effective records, simply
|
||||||
|
enable the filter on the session prior to retrieving employee data:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = ...;
|
||||||
|
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
|
||||||
|
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
|
||||||
|
.setLong("targetSalary", new Long(1000000))
|
||||||
|
.list();
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In the HQL above, even though we only explicitly mentioned a salary constraint on the results,
|
||||||
|
because of the enabled filter the query will return only currently active employees who have
|
||||||
|
a salary greater than a million dollars.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note: if you plan on using filters with outer joining (either through HQL or load fetching) be
|
||||||
|
careful of the direction of the condition expression. Its safest to set this up for left
|
||||||
|
outer joining; in general, place the parameter first followed by the column name(s) after
|
||||||
|
the operator.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
After being defined a filter might be attached to multiple entities and/or
|
||||||
|
collections each with its own condition. That can be tedious when the
|
||||||
|
conditions are the same each time. Thus <literal><filter-def/></literal>
|
||||||
|
allows defining a default condition, either as an attribute or CDATA:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="myFilter" condition="abc > xyz">...</filter-def>
|
||||||
|
<filter-def name="myOtherFilter">abc=xyz</filter-def>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This default condition will then be used whenever the filter is attached to something
|
||||||
|
without specifying a condition. Note that this means you can give a specific condition
|
||||||
|
as part of the attachment of the filter which overrides the default condition in that
|
||||||
|
particular case.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,491 @@
|
||||||
|
<chapter id="inheritance">
|
||||||
|
<title>Inheritance Mapping</title>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-strategies" revision="3">
|
||||||
|
<title>The Three Strategies</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate supports the three basic inheritance mapping strategies:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
table per class hierarchy
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
table per subclass
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
table per concrete class
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In addition, Hibernate supports a fourth, slightly different kind of
|
||||||
|
polymorphism:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
implicit polymorphism
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is possible to use different mapping strategies for different
|
||||||
|
branches of the same inheritance hierarchy, and then make use of implicit
|
||||||
|
polymorphism to achieve polymorphism across the whole hierarchy. However,
|
||||||
|
Hibernate does not support mixing <literal><subclass></literal>,
|
||||||
|
and <literal><joined-subclass></literal> and
|
||||||
|
<literal><union-subclass></literal> mappings under the same root
|
||||||
|
<literal><class></literal> element. It is possible to mix together
|
||||||
|
the table per hierarchy and table per subclass strategies, under the
|
||||||
|
the same <literal><class></literal> element, by combining the
|
||||||
|
<literal><subclass></literal> and <literal><join></literal>
|
||||||
|
elements (see below).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is possible to define <literal>subclass</literal>, <literal>union-subclass</literal>,
|
||||||
|
and <literal>joined-subclass</literal> mappings in separate mapping documents, directly beneath
|
||||||
|
<literal>hibernate-mapping</literal>. This allows you to extend a class hierachy just by adding
|
||||||
|
a new mapping file. You must specify an <literal>extends</literal> attribute in the subclass mapping,
|
||||||
|
naming a previously mapped superclass. Note: Previously this feature made the ordering of the mapping
|
||||||
|
documents important. Since Hibernate3, the ordering of mapping files does not matter when using the
|
||||||
|
extends keyword. The ordering inside a single mapping file still needs to be defined as superclasses
|
||||||
|
before subclasses.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
<hibernate-mapping>
|
||||||
|
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
|
||||||
|
<property name="name" type="string"/>
|
||||||
|
</subclass>
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperclass" >
|
||||||
|
<title>Table per class hierarchy</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Suppose we have an interface <literal>Payment</literal>, with implementors
|
||||||
|
<literal>CreditCardPayment</literal>, <literal>CashPayment</literal>,
|
||||||
|
<literal>ChequePayment</literal>. The table per hierarchy mapping would
|
||||||
|
look like:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Exactly one table is required. There is one big limitation of this mapping
|
||||||
|
strategy: columns declared by the subclasses, such as <literal>CCTYPE</literal>,
|
||||||
|
may not have <literal>NOT NULL</literal> constraints.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass">
|
||||||
|
<title>Table per subclass</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A table per subclass mapping would look like:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Four tables are required. The three subclass tables have primary
|
||||||
|
key associations to the superclass table (so the relational model
|
||||||
|
is actually a one-to-one association).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
||||||
|
<title>Table per subclass, using a discriminator</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that Hibernate's implementation of table per subclass requires
|
||||||
|
no discriminator column. Other object/relational mappers use a
|
||||||
|
different implementation of table per subclass which requires a type
|
||||||
|
discriminator column in the superclass table. The approach taken by
|
||||||
|
Hibernate is much more difficult to implement but arguably more
|
||||||
|
correct from a relational point of view. If you would like to use
|
||||||
|
a discriminator column with the table per subclass strategy, you
|
||||||
|
may combine the use of <literal><subclass></literal> and
|
||||||
|
<literal><join></literal>, as follow:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
<join table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
<join table="CHEQUE_PAYMENT" fetch="select">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The optional <literal>fetch="select"</literal> declaration tells Hibernate
|
||||||
|
not to fetch the <literal>ChequePayment</literal> subclass data using an
|
||||||
|
outer join when querying the superclass.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
||||||
|
<title>Mixing table per class hierarchy with table per subclass</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may even mix the table per hierarchy and table per subclass strategies
|
||||||
|
using this approach:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For any of these mapping strategies, a polymorphic association to the root
|
||||||
|
<literal>Payment</literal> class is mapped using
|
||||||
|
<literal><many-to-one></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcrete" revision="2">
|
||||||
|
<title>Table per concrete class</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are two ways we could go about mapping the table per concrete class
|
||||||
|
strategy. The first is to use <literal><union-subclass></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Three tables are involved for the subclasses. Each table defines columns for
|
||||||
|
all properties of the class, including inherited properties.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The limitation of this approach is that if a property is mapped on the
|
||||||
|
superclass, the column name must be the same on all subclass tables.
|
||||||
|
(We might relax this in a future release of Hibernate.) The identity
|
||||||
|
generator strategy is not allowed in union subclass inheritance, indeed
|
||||||
|
the primary key seed has to be shared accross all unioned subclasses
|
||||||
|
of a hierarchy.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If your superclass is abstract, map it with <literal>abstract="true"</literal>.
|
||||||
|
Of course, if it is not abstract, an additional table (defaults to
|
||||||
|
<literal>PAYMENT</literal> in the example above) is needed to hold instances
|
||||||
|
of the superclass.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
||||||
|
<title>Table per concrete class, using implicit polymorphism</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An alternative approach is to make use of implicit polymorphism:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CASH_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notice that nowhere do we mention the <literal>Payment</literal> interface
|
||||||
|
explicitly. Also notice that properties of <literal>Payment</literal> are
|
||||||
|
mapped in each of the subclasses. If you want to avoid duplication, consider
|
||||||
|
using XML entities
|
||||||
|
(e.g. <literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
||||||
|
in the <literal>DOCTYPE</literal> declartion and
|
||||||
|
<literal>&allproperties;</literal> in the mapping).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The disadvantage of this approach is that Hibernate does not generate SQL
|
||||||
|
<literal>UNION</literal>s when performing polymorphic queries.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For this mapping strategy, a polymorphic association to <literal>Payment</literal>
|
||||||
|
is usually mapped using <literal><any></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<any name="payment" meta-type="string" id-type="long">
|
||||||
|
<meta-value value="CREDIT" class="CreditCardPayment"/>
|
||||||
|
<meta-value value="CASH" class="CashPayment"/>
|
||||||
|
<meta-value value="CHEQUE" class="ChequePayment"/>
|
||||||
|
<column name="PAYMENT_CLASS"/>
|
||||||
|
<column name="PAYMENT_ID"/>
|
||||||
|
</any>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritace-mixingpolymorphism">
|
||||||
|
<title>Mixing implicit polymorphism with other inheritance mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There is one further thing to notice about this mapping. Since the subclasses
|
||||||
|
are each mapped in their own <literal><class></literal> element (and since
|
||||||
|
<literal>Payment</literal> is just an interface), each of the subclasses could
|
||||||
|
easily be part of another inheritance hierarchy! (And you can still use polymorphic
|
||||||
|
queries against the <literal>Payment</literal> interface.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="CREDIT_CARD" type="string"/>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
|
||||||
|
<subclass name="VisaPayment" discriminator-value="VISA"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
|
||||||
|
<id name="id" type="long" column="TXN_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Once again, we don't mention <literal>Payment</literal> explicitly. If we
|
||||||
|
execute a query against the <literal>Payment</literal> interface - for
|
||||||
|
example, <literal>from Payment</literal> - Hibernate
|
||||||
|
automatically returns instances of <literal>CreditCardPayment</literal>
|
||||||
|
(and its subclasses, since they also implement <literal>Payment</literal>),
|
||||||
|
<literal>CashPayment</literal> and <literal>ChequePayment</literal> but
|
||||||
|
not instances of <literal>NonelectronicTransaction</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-limitations">
|
||||||
|
<title>Limitations</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are certain limitations to the "implicit polymorphism" approach to
|
||||||
|
the table per concrete-class mapping strategy. There are somewhat less
|
||||||
|
restrictive limitations to <literal><union-subclass></literal>
|
||||||
|
mappings.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following table shows the limitations of table per concrete-class
|
||||||
|
mappings, and of implicit polymorphism, in Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Features of inheritance mappings</title>
|
||||||
|
<tgroup cols='8' align='left' colsep='1' rowsep='1'>
|
||||||
|
<colspec colname='c1' colwidth="1*"/>
|
||||||
|
<colspec colname='c2' colwidth="1*"/>
|
||||||
|
<colspec colname='c3' colwidth="1*"/>
|
||||||
|
<colspec colname='c4' colwidth="1*"/>
|
||||||
|
<colspec colname='c5' colwidth="1*"/>
|
||||||
|
<colspec colname='c6' colwidth="1*"/>
|
||||||
|
<colspec colname='c7' colwidth="1*"/>
|
||||||
|
<colspec colname='c8' colwidth="1*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Inheritance strategy</entry>
|
||||||
|
<entry>Polymorphic many-to-one</entry>
|
||||||
|
<entry>Polymorphic one-to-one</entry>
|
||||||
|
<entry>Polymorphic one-to-many</entry>
|
||||||
|
<entry>Polymorphic many-to-many</entry>
|
||||||
|
<entry>Polymorphic <literal>load()/get()</literal></entry>
|
||||||
|
<entry>Polymorphic queries</entry>
|
||||||
|
<entry>Polymorphic joins</entry>
|
||||||
|
<entry>Outer join fetching</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>table per class-hierarchy</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supported</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>table per subclass</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supported</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>table per concrete-class (union-subclass)</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal> (for <literal>inverse="true"</literal> only)</entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supported</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>table per concrete class (implicit polymorphism)</entry>
|
||||||
|
<entry><literal><any></literal></entry>
|
||||||
|
<entry><emphasis>not supported</emphasis></entry>
|
||||||
|
<entry><emphasis>not supported</emphasis></entry>
|
||||||
|
<entry><literal><many-to-any></literal></entry>
|
||||||
|
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><emphasis>not supported</emphasis></entry>
|
||||||
|
<entry><emphasis>not supported</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,531 @@
|
||||||
|
<chapter id="persistent-classes" revision="2">
|
||||||
|
<title>Persistent Classes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Persistent classes are classes in an application that implement the entities
|
||||||
|
of the business problem (e.g. Customer and Order in an E-commerce application).
|
||||||
|
Not all instances of a persistent class are considered to be in the persistent
|
||||||
|
state - an instance may instead be transient or detached.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate works best if these classes follow some simple rules, also known
|
||||||
|
as the Plain Old Java Object (POJO) programming model. However, none of these
|
||||||
|
rules are hard requirements. Indeed, Hibernate3 assumes very little about
|
||||||
|
the nature of your persistent objects. You may express a domain model in other
|
||||||
|
ways: using trees of <literal>Map</literal> instances, for example.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-pojo">
|
||||||
|
<title>A simple POJO example</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Most Java applications require a persistent class representing felines.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Cat {
|
||||||
|
private Long id; // identifier
|
||||||
|
|
||||||
|
private Date birthdate;
|
||||||
|
private Color color;
|
||||||
|
private char sex;
|
||||||
|
private float weight;
|
||||||
|
private int litterId;
|
||||||
|
|
||||||
|
private Cat mother;
|
||||||
|
private Set kittens = new HashSet();
|
||||||
|
|
||||||
|
private void setId(Long id) {
|
||||||
|
this.id=id;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBirthdate(Date date) {
|
||||||
|
birthdate = date;
|
||||||
|
}
|
||||||
|
public Date getBirthdate() {
|
||||||
|
return birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWeight(float weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
void setColor(Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSex(char sex) {
|
||||||
|
this.sex=sex;
|
||||||
|
}
|
||||||
|
public char getSex() {
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLitterId(int id) {
|
||||||
|
this.litterId = id;
|
||||||
|
}
|
||||||
|
public int getLitterId() {
|
||||||
|
return litterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMother(Cat mother) {
|
||||||
|
this.mother = mother;
|
||||||
|
}
|
||||||
|
public Cat getMother() {
|
||||||
|
return mother;
|
||||||
|
}
|
||||||
|
void setKittens(Set kittens) {
|
||||||
|
this.kittens = kittens;
|
||||||
|
}
|
||||||
|
public Set getKittens() {
|
||||||
|
return kittens;
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKitten not needed by Hibernate
|
||||||
|
public void addKitten(Cat kitten) {
|
||||||
|
kitten.setMother(this);
|
||||||
|
kitten.setLitterId( kittens.size() );
|
||||||
|
kittens.add(kitten);
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are four main rules to follow here:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-constructor" revision="1">
|
||||||
|
<title>Implement a no-argument constructor</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> has a no-argument constructor. All persistent classes must
|
||||||
|
have a default constructor (which may be non-public) so that Hibernate can instantiate
|
||||||
|
them using <literal>Constructor.newInstance()</literal>. We strongly recommend having a
|
||||||
|
default constructor with at least <emphasis>package</emphasis> visibility for runtime proxy
|
||||||
|
generation in Hibernate.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-identifier" revision="2">
|
||||||
|
<title>Provide an identifier property (optional)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> has a property called <literal>id</literal>. This property
|
||||||
|
maps to the primary key column of a database table. The property might have been called
|
||||||
|
anything, and its type might have been any primitive type, any primitive "wrapper"
|
||||||
|
type, <literal>java.lang.String</literal> or <literal>java.util.Date</literal>. (If
|
||||||
|
your legacy database table has composite keys, you can even use a user-defined class
|
||||||
|
with properties of these types - see the section on composite identifiers later.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The identifier property is strictly optional. You can leave them off and let Hibernate
|
||||||
|
keep track of object identifiers internally. We do not recommend this, however.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In fact, some functionality is available only to classes which declare an
|
||||||
|
identifier property:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Transitive reattachment for detached objects (cascade update or cascade
|
||||||
|
merge) - see <xref linkend="objectstate-transitive"/>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.saveOrUpdate()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.merge()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We recommend you declare consistently-named identifier properties on persistent
|
||||||
|
classes. We further recommend that you use a nullable (ie. non-primitive) type.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-final">
|
||||||
|
<title>Prefer non-final classes (optional)</title>
|
||||||
|
<para>
|
||||||
|
A central feature of Hibernate, <emphasis>proxies</emphasis>, depends upon the
|
||||||
|
persistent class being either non-final, or the implementation of an interface
|
||||||
|
that declares all public methods.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You can persist <literal>final</literal> classes that do not implement an interface
|
||||||
|
with Hibernate, but you won't be able to use proxies for lazy association fetching -
|
||||||
|
which will limit your options for performance tuning.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
You should also avoid declaring <literal>public final</literal> methods on the
|
||||||
|
non-final classes. If you want to use a class with a <literal>public final</literal>
|
||||||
|
method, you must explicitly disable proxying by setting <literal>lazy="false"</literal>.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-accessors" revision="2">
|
||||||
|
<title>Declare accessors and mutators for persistent fields (optional)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> declares accessor methods for all its persistent fields.
|
||||||
|
Many other ORM tools directly persist instance variables. We believe it is
|
||||||
|
better to provide an indirection between the relational schema and internal
|
||||||
|
data structures of the class. By default, Hibernate persists JavaBeans style
|
||||||
|
properties, and recognizes method names of the form <literal>getFoo</literal>,
|
||||||
|
<literal>isFoo</literal> and <literal>setFoo</literal>. You may switch to direct
|
||||||
|
field access for particular properties, if needed.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Properties need <emphasis>not</emphasis> be declared public - Hibernate can
|
||||||
|
persist a property with a default, <literal>protected</literal> or
|
||||||
|
<literal>private</literal> get / set pair.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-inheritance">
|
||||||
|
<title>Implementing inheritance</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A subclass must also observe the first and second rules. It inherits its
|
||||||
|
identifier property from the superclass, <literal>Cat</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
public class DomesticCat extends Cat {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name=name;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-equalshashcode" revision="1">
|
||||||
|
<title>Implementing <literal>equals()</literal> and <literal>hashCode()</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You have to override the <literal>equals()</literal> and <literal>hashCode()</literal>
|
||||||
|
methods if you
|
||||||
|
</para>
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
intend to put instances of persistent classes in a <literal>Set</literal>
|
||||||
|
(the recommended way to represent many-valued associations)
|
||||||
|
<emphasis>and</emphasis>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
intend to use reattachment of detached instances
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate guarantees equivalence of persistent identity (database row) and Java identity
|
||||||
|
only inside a particular session scope. So as soon as we mix instances retrieved in
|
||||||
|
different sessions, we must implement <literal>equals()</literal> and
|
||||||
|
<literal>hashCode()</literal> if we wish to have meaningful semantics for
|
||||||
|
<literal>Set</literal>s.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The most obvious way is to implement <literal>equals()</literal>/<literal>hashCode()</literal>
|
||||||
|
by comparing the identifier value of both objects. If the value is the same, both must
|
||||||
|
be the same database row, they are therefore equal (if both are added to a <literal>Set</literal>,
|
||||||
|
we will only have one element in the <literal>Set</literal>). Unfortunately, we can't use that
|
||||||
|
approach with generated identifiers! Hibernate will only assign identifier values to objects
|
||||||
|
that are persistent, a newly created instance will not have any identifier value! Furthermore,
|
||||||
|
if an instance is unsaved and currently in a <literal>Set</literal>, saving it will assign
|
||||||
|
an identifier value to the object. If <literal>equals()</literal> and <literal>hashCode()</literal>
|
||||||
|
are based on the identifier value, the hash code would change, breaking the contract of the
|
||||||
|
<literal>Set</literal>. See the Hibernate website for a full discussion of this problem. Note
|
||||||
|
that this is not a Hibernate issue, but normal Java semantics of object identity and equality.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
We recommend implementing <literal>equals()</literal> and <literal>hashCode()</literal>
|
||||||
|
using <emphasis>Business key equality</emphasis>. Business key equality means that the
|
||||||
|
<literal>equals()</literal> method compares only the properties that form the business
|
||||||
|
key, a key that would identify our instance in the real world (a
|
||||||
|
<emphasis>natural</emphasis> candidate key):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Cat {
|
||||||
|
|
||||||
|
...
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) return true;
|
||||||
|
if ( !(other instanceof Cat) ) return false;
|
||||||
|
|
||||||
|
final Cat cat = (Cat) other;
|
||||||
|
|
||||||
|
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
|
||||||
|
if ( !cat.getMother().equals( getMother() ) ) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
result = getMother().hashCode();
|
||||||
|
result = 29 * result + getLitterId();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that a business key does not have to be as solid as a database
|
||||||
|
primary key candidate (see <xref linkend="transactions-basics-identity"/>).
|
||||||
|
Immutable or unique properties are usually good
|
||||||
|
candidates for a business key.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-dynamicmodels">
|
||||||
|
<title>Dynamic models</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Note that the following features are currently considered
|
||||||
|
experimental and may change in the near future.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Persistent entities don't necessarily have to be represented as POJO classes
|
||||||
|
or as JavaBean objects at runtime. Hibernate also supports dynamic models
|
||||||
|
(using <literal>Map</literal>s of <literal>Map</literal>s at runtime) and the
|
||||||
|
representation of entities as DOM4J trees. With this approach, you don't
|
||||||
|
write persistent classes, only mapping files.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
By default, Hibernate works in normal POJO mode. You may set a default entity
|
||||||
|
representation mode for a particular <literal>SessionFactory</literal> using the
|
||||||
|
<literal>default_entity_mode</literal> configuration option (see
|
||||||
|
<xref linkend="configuration-optional-properties"/>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following examples demonstrates the representation using <literal>Map</literal>s.
|
||||||
|
First, in the mapping file, an <literal>entity-name</literal> has to be declared
|
||||||
|
instead of (or in addition to) a class name:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class entity-name="Customer">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
type="long"
|
||||||
|
column="ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
column="NAME"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<property name="address"
|
||||||
|
column="ADDRESS"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<many-to-one name="organization"
|
||||||
|
column="ORGANIZATION_ID"
|
||||||
|
class="Organization"/>
|
||||||
|
|
||||||
|
<bag name="orders"
|
||||||
|
inverse="true"
|
||||||
|
lazy="false"
|
||||||
|
cascade="all">
|
||||||
|
<key column="CUSTOMER_ID"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
|
||||||
|
Note that even though associations are declared using target class names,
|
||||||
|
the target type of an associations may also be a dynamic entity instead
|
||||||
|
of a POJO.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
After setting the default entity mode to <literal>dynamic-map</literal>
|
||||||
|
for the <literal>SessionFactory</literal>, we can at runtime work with
|
||||||
|
<literal>Map</literal>s of <literal>Map</literal>s:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session s = openSession();
|
||||||
|
Transaction tx = s.beginTransaction();
|
||||||
|
Session s = openSession();
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
|
||||||
|
// Create an organization
|
||||||
|
Map foobar = new HashMap();
|
||||||
|
foobar.put("name", "Foobar Inc.");
|
||||||
|
|
||||||
|
// Link both
|
||||||
|
david.put("organization", foobar);
|
||||||
|
|
||||||
|
// Save both
|
||||||
|
s.save("Customer", david);
|
||||||
|
s.save("Organization", foobar);
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
s.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The advantages of a dynamic mapping are quick turnaround time for prototyping
|
||||||
|
without the need for entity class implementation. However, you lose compile-time
|
||||||
|
type checking and will very likely deal with many exceptions at runtime. Thanks
|
||||||
|
to the Hibernate mapping, the database schema can easily be normalized and sound,
|
||||||
|
allowing to add a proper domain model implementation on top later on.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Entity representation modes can also be set on a per <literal>Session</literal>
|
||||||
|
basis:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
dynamicSession.save("Customer", david);
|
||||||
|
...
|
||||||
|
dynamicSession.flush();
|
||||||
|
dynamicSession.close()
|
||||||
|
...
|
||||||
|
// Continue on pojoSession
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Please note that the call to <literal>getSession()</literal> using an
|
||||||
|
<literal>EntityMode</literal> is on the <literal>Session</literal> API, not the
|
||||||
|
<literal>SessionFactory</literal>. That way, the new <literal>Session</literal>
|
||||||
|
shares the underlying JDBC connection, transaction, and other context
|
||||||
|
information. This means you don't have tocall <literal>flush()</literal>
|
||||||
|
and <literal>close()</literal> on the secondary <literal>Session</literal>, and
|
||||||
|
also leave the transaction and connection handling to the primary unit of work.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
More information about the XML representation capabilities can be found
|
||||||
|
in <xref linkend="xml"/>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-tuplizers" revision="1">
|
||||||
|
<title>Tuplizers</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.tuple.Tuplizer</literal>, and its sub-interfaces, are responsible
|
||||||
|
for managing a particular representation of a piece of data, given that representation's
|
||||||
|
<literal>org.hibernate.EntityMode</literal>. If a given piece of data is thought of as
|
||||||
|
a data structure, then a tuplizer is the thing which knows how to create such a data structure
|
||||||
|
and how to extract values from and inject values into such a data structure. For example,
|
||||||
|
for the POJO entity mode, the correpsonding tuplizer knows how create the POJO through its
|
||||||
|
constructor and how to access the POJO properties using the defined property accessors.
|
||||||
|
There are two high-level types of Tuplizers, represented by the
|
||||||
|
<literal>org.hibernate.tuple.entity.EntityTuplizer</literal> and <literal>org.hibernate.tuple.component.ComponentTuplizer</literal>
|
||||||
|
interfaces. <literal>EntityTuplizer</literal>s are responsible for managing the above mentioned
|
||||||
|
contracts in regards to entities, while <literal>ComponentTuplizer</literal>s do the same for
|
||||||
|
components.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Users may also plug in their own tuplizers. Perhaps you require that a <literal>java.util.Map</literal>
|
||||||
|
implementation other than <literal>java.util.HashMap</literal> be used while in the
|
||||||
|
dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy
|
||||||
|
than the one used by default. Both would be achieved by defining a custom tuplizer
|
||||||
|
implementation. Tuplizers definitions are attached to the entity or component mapping they
|
||||||
|
are meant to manage. Going back to the example of our customer entity:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
<class entity-name="Customer">
|
||||||
|
<!--
|
||||||
|
Override the dynamic-map entity-mode
|
||||||
|
tuplizer for the customer entity
|
||||||
|
-->
|
||||||
|
<tuplizer entity-mode="dynamic-map"
|
||||||
|
class="CustomMapTuplizerImpl"/>
|
||||||
|
|
||||||
|
<id name="id" type="long" column="ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<!-- other properties -->
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
</hibernate-mapping>
|
||||||
|
|
||||||
|
|
||||||
|
public class CustomMapTuplizerImpl
|
||||||
|
extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer {
|
||||||
|
// override the buildInstantiator() method to plug in our custom map...
|
||||||
|
protected final Instantiator buildInstantiator(
|
||||||
|
org.hibernate.mapping.PersistentClass mappingInfo) {
|
||||||
|
return new CustomMapInstantiator( mappingInfo );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CustomMapInstantiator
|
||||||
|
extends org.hibernate.tuple.DynamicMapInstantitor {
|
||||||
|
// override the generateMap() method to return our custom map...
|
||||||
|
protected final Map generateMap() {
|
||||||
|
return new CustomMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
TODO: Document user-extension framework in the property and proxy packages
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,436 @@
|
||||||
|
<chapter id="querycriteria">
|
||||||
|
<title>Criteria Queries</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate features an intuitive, extensible criteria query API.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-creating">
|
||||||
|
<title>Creating a <literal>Criteria</literal> instance</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The interface <literal>org.hibernate.Criteria</literal> represents a query against
|
||||||
|
a particular persistent class. The <literal>Session</literal> is a factory for
|
||||||
|
<literal>Criteria</literal> instances.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Criteria crit = sess.createCriteria(Cat.class);
|
||||||
|
crit.setMaxResults(50);
|
||||||
|
List cats = crit.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-narrowing">
|
||||||
|
<title>Narrowing the result set</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An individual query criterion is an instance of the interface
|
||||||
|
<literal>org.hibernate.criterion.Criterion</literal>. The class
|
||||||
|
<literal>org.hibernate.criterion.Restrictions</literal> defines
|
||||||
|
factory methods for obtaining certain built-in
|
||||||
|
<literal>Criterion</literal> types.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.add( Restrictions.between("weight", minWeight, maxWeight) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Restrictions may be grouped logically.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.add( Restrictions.or(
|
||||||
|
Restrictions.eq( "age", new Integer(0) ),
|
||||||
|
Restrictions.isNull("age")
|
||||||
|
) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
|
.add( Restrictions.disjunction()
|
||||||
|
.add( Restrictions.isNull("age") )
|
||||||
|
.add( Restrictions.eq("age", new Integer(0) ) )
|
||||||
|
.add( Restrictions.eq("age", new Integer(1) ) )
|
||||||
|
.add( Restrictions.eq("age", new Integer(2) ) )
|
||||||
|
) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are quite a range of built-in criterion types (<literal>Restrictions</literal>
|
||||||
|
subclasses), but one that is especially useful lets you specify SQL directly.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>{alias}</literal> placeholder with be replaced by the row alias
|
||||||
|
of the queried entity.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An alternative approach to obtaining a criterion is to get it from a
|
||||||
|
<literal>Property</literal> instance. You can create a <literal>Property</literal>
|
||||||
|
by calling <literal>Property.forName()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
Property age = Property.forName("age");
|
||||||
|
List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.disjunction()
|
||||||
|
.add( age.isNull() )
|
||||||
|
.add( age.eq( new Integer(0) ) )
|
||||||
|
.add( age.eq( new Integer(1) ) )
|
||||||
|
.add( age.eq( new Integer(2) ) )
|
||||||
|
) )
|
||||||
|
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-ordering">
|
||||||
|
<title>Ordering the results</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may order the results using <literal>org.hibernate.criterion.Order</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "F%")
|
||||||
|
.addOrder( Order.asc("name") )
|
||||||
|
.addOrder( Order.desc("age") )
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Property.forName("name").like("F%") )
|
||||||
|
.addOrder( Property.forName("name").asc() )
|
||||||
|
.addOrder( Property.forName("age").desc() )
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-associations" revision="2">
|
||||||
|
<title>Associations</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may easily specify constraints upon related entities by navigating
|
||||||
|
associations using <literal>createCriteria()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "F%") )
|
||||||
|
.createCriteria("kittens")
|
||||||
|
.add( Restrictions.like("name", "F%") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
note that the second <literal>createCriteria()</literal> returns a new
|
||||||
|
instance of <literal>Criteria</literal>, which refers to the elements of
|
||||||
|
the <literal>kittens</literal> collection.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following, alternate form is useful in certain circumstances.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.createAlias("kittens", "kt")
|
||||||
|
.createAlias("mate", "mt")
|
||||||
|
.add( Restrictions.eqProperty("kt.name", "mt.name") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(<literal>createAlias()</literal> does not create a new instance of
|
||||||
|
<literal>Criteria</literal>.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that the kittens collections held by the <literal>Cat</literal> instances
|
||||||
|
returned by the previous two queries are <emphasis>not</emphasis> pre-filtered
|
||||||
|
by the criteria! If you wish to retrieve just the kittens that match the
|
||||||
|
criteria, you must use a <literal>ResultTransformer</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.createCriteria("kittens", "kt")
|
||||||
|
.add( Restrictions.eq("name", "F%") )
|
||||||
|
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
|
||||||
|
.list();
|
||||||
|
Iterator iter = cats.iterator();
|
||||||
|
while ( iter.hasNext() ) {
|
||||||
|
Map map = (Map) iter.next();
|
||||||
|
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
|
||||||
|
Cat kitten = (Cat) map.get("kt");
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-dynamicfetching" revision="1">
|
||||||
|
<title>Dynamic association fetching</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may specify association fetching semantics at runtime using
|
||||||
|
<literal>setFetchMode()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.setFetchMode("mate", FetchMode.EAGER)
|
||||||
|
.setFetchMode("kittens", FetchMode.EAGER)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This query will fetch both <literal>mate</literal> and <literal>kittens</literal>
|
||||||
|
by outer join. See <xref linkend="performance-fetching"/> for more information.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-examples">
|
||||||
|
<title>Example queries</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The class <literal>org.hibernate.criterion.Example</literal> allows
|
||||||
|
you to construct a query criterion from a given instance.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Cat cat = new Cat();
|
||||||
|
cat.setSex('F');
|
||||||
|
cat.setColor(Color.BLACK);
|
||||||
|
List results = session.createCriteria(Cat.class)
|
||||||
|
.add( Example.create(cat) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Version properties, identifiers and associations are ignored. By default,
|
||||||
|
null valued properties are excluded.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can adjust how the <literal>Example</literal> is applied.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Example example = Example.create(cat)
|
||||||
|
.excludeZeroes() //exclude zero valued properties
|
||||||
|
.excludeProperty("color") //exclude the property named "color"
|
||||||
|
.ignoreCase() //perform case insensitive string comparisons
|
||||||
|
.enableLike(); //use like for string comparisons
|
||||||
|
List results = session.createCriteria(Cat.class)
|
||||||
|
.add(example)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can even use examples to place criteria upon associated objects.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.add( Example.create(cat) )
|
||||||
|
.createCriteria("mate")
|
||||||
|
.add( Example.create( cat.getMate() ) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-projection">
|
||||||
|
<title>Projections, aggregation and grouping</title>
|
||||||
|
<para>
|
||||||
|
The class <literal>org.hibernate.criterion.Projections</literal> is a
|
||||||
|
factory for <literal>Projection</literal> instances. We apply a
|
||||||
|
projection to a query by calling <literal>setProjection()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.rowCount() )
|
||||||
|
.add( Restrictions.eq("color", Color.BLACK) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount() )
|
||||||
|
.add( Projections.avg("weight") )
|
||||||
|
.add( Projections.max("weight") )
|
||||||
|
.add( Projections.groupProperty("color") )
|
||||||
|
)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There is no explicit "group by" necessary in a criteria query. Certain
|
||||||
|
projection types are defined to be <emphasis>grouping projections</emphasis>,
|
||||||
|
which also appear in the SQL <literal>group by</literal> clause.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An alias may optionally be assigned to a projection, so that the projected value
|
||||||
|
may be referred to in restrictions or orderings. Here are two different ways to
|
||||||
|
do this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
|
||||||
|
.addOrder( Order.asc("colr") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.groupProperty("color").as("colr") )
|
||||||
|
.addOrder( Order.asc("colr") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>alias()</literal> and <literal>as()</literal> methods simply wrap a
|
||||||
|
projection instance in another, aliased, instance of <literal>Projection</literal>.
|
||||||
|
As a shortcut, you can assign an alias when you add the projection to a
|
||||||
|
projection list:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount(), "catCountByColor" )
|
||||||
|
.add( Projections.avg("weight"), "avgWeight" )
|
||||||
|
.add( Projections.max("weight"), "maxWeight" )
|
||||||
|
.add( Projections.groupProperty("color"), "color" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.desc("catCountByColor") )
|
||||||
|
.addOrder( Order.desc("avgWeight") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Domestic.class, "cat")
|
||||||
|
.createAlias("kittens", "kit")
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.property("cat.name"), "catName" )
|
||||||
|
.add( Projections.property("kit.name"), "kitName" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.asc("catName") )
|
||||||
|
.addOrder( Order.asc("kitName") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can also use <literal>Property.forName()</literal> to express projections:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Property.forName("name") )
|
||||||
|
.add( Property.forName("color").eq(Color.BLACK) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount().as("catCountByColor") )
|
||||||
|
.add( Property.forName("weight").avg().as("avgWeight") )
|
||||||
|
.add( Property.forName("weight").max().as("maxWeight") )
|
||||||
|
.add( Property.forName("color").group().as("color" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.desc("catCountByColor") )
|
||||||
|
.addOrder( Order.desc("avgWeight") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-detachedqueries">
|
||||||
|
<title>Detached queries and subqueries</title>
|
||||||
|
<para>
|
||||||
|
The <literal>DetachedCriteria</literal> class lets you create a query outside the scope
|
||||||
|
of a session, and then later execute it using some arbitrary <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.add( Property.forName("sex").eq('F') );
|
||||||
|
|
||||||
|
Session session = ....;
|
||||||
|
Transaction txn = session.beginTransaction();
|
||||||
|
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
|
||||||
|
txn.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>DetachedCriteria</literal> may also be used to express a subquery. Criterion
|
||||||
|
instances involving subqueries may be obtained via <literal>Subqueries</literal> or
|
||||||
|
<literal>Property</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.setProjection( Property.forName("weight").avg() );
|
||||||
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Property.forName("weight).gt(avgWeight) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.setProjection( Property.forName("weight") );
|
||||||
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Subqueries.geAll("weight", weights) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Even correlated subqueries are possible:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
|
||||||
|
.setProjection( Property.forName("weight").avg() )
|
||||||
|
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
|
||||||
|
session.createCriteria(Cat.class, "cat")
|
||||||
|
.add( Property.forName("weight).gt(avgWeightForSex) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<!--TODO: ResultSetTransformer + aliasing. AliasToBeanTransformer allow returning arbitrary
|
||||||
|
user objects - similar to setResultClass in JDO2. General use of ResultTransformer
|
||||||
|
could also be explained. -->
|
||||||
|
|
||||||
|
<sect1 id="query-criteria-naturalid">
|
||||||
|
<title>Queries by natural identifier</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For most queries, including criteria queries, the query cache is not very efficient,
|
||||||
|
because query cache invalidation occurs too frequently. However, there is one special
|
||||||
|
kind of query where we can optimize the cache invalidation algorithm: lookups by a
|
||||||
|
constant natural key. In some applications, this kind of query occurs frequently.
|
||||||
|
The criteria API provides special provision for this use case.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
First, you should map the natural key of your entity using
|
||||||
|
<literal><natural-id></literal>, and enable use of the second-level cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="User">
|
||||||
|
<cache usage="read-write"/>
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<natural-id>
|
||||||
|
<property name="name"/>
|
||||||
|
<property name="org"/>
|
||||||
|
</natural-id>
|
||||||
|
<property name="password"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Note that this functionality is not intended for use with entities with
|
||||||
|
<emphasis>mutable</emphasis> natural keys.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Next, enable the Hibernate query cache.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Now, <literal>Restrictions.naturalId()</literal> allows us to make use of
|
||||||
|
the more efficient cache algorithm.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.createCriteria(User.class)
|
||||||
|
.add( Restrictions.naturalId()
|
||||||
|
.set("name", "gavin")
|
||||||
|
.set("org", "hb")
|
||||||
|
).setCacheable(true)
|
||||||
|
.uniqueResult();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,758 @@
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<chapter id="querysql" revision="2">
|
||||||
|
<title>Native SQL</title>
|
||||||
|
|
||||||
|
<para>You may also express queries in the native SQL dialect of your
|
||||||
|
database. This is useful if you want to utilize database specific features
|
||||||
|
such as query hints or the <literal>CONNECT</literal> keyword in Oracle. It
|
||||||
|
also provides a clean migration path from a direct SQL/JDBC based
|
||||||
|
application to Hibernate.</para>
|
||||||
|
|
||||||
|
<para>Hibernate3 allows you to specify handwritten SQL (including stored
|
||||||
|
procedures) for all create, update, delete, and load operations.</para>
|
||||||
|
|
||||||
|
<sect1 id="querysql-creating" revision="4">
|
||||||
|
<title>Using a <literal>SQLQuery</literal></title>
|
||||||
|
|
||||||
|
<para>Execution of native SQL queries is controlled via the
|
||||||
|
<literal>SQLQuery</literal> interface, which is obtained by calling
|
||||||
|
<literal>Session.createSQLQuery()</literal>. The following describes how
|
||||||
|
to use this API for querying.</para>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Scalar queries</title>
|
||||||
|
|
||||||
|
<para>The most basic SQL query is to get a list of scalars
|
||||||
|
(values).</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").list();
|
||||||
|
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>These will both return a List of Object arrays (Object[]) with
|
||||||
|
scalar values for each column in the CATS table. Hibernate will use
|
||||||
|
ResultSetMetadata to deduce the actual order and types of the returned
|
||||||
|
scalar values.</para>
|
||||||
|
|
||||||
|
<para>To avoid the overhead of using
|
||||||
|
<literal>ResultSetMetadata</literal> or simply to be more explicit in
|
||||||
|
what is returned one can use <literal>addScalar()</literal>.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
|
||||||
|
.addScalar("ID", Hibernate.LONG)
|
||||||
|
.addScalar("NAME", Hibernate.STRING)
|
||||||
|
.addScalar("BIRTHDATE", Hibernate.DATE)
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>This query specified:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>the SQL query string</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>the columns and types to return</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>This will still return Object arrays, but now it will not use
|
||||||
|
<literal>ResultSetMetdata</literal> but will instead explicitly get the
|
||||||
|
ID, NAME and BIRTHDATE column as respectively a Long, String and a Short
|
||||||
|
from the underlying resultset. This also means that only these three
|
||||||
|
columns will be returned, even though the query is using
|
||||||
|
<literal>*</literal> and could return more than the three listed
|
||||||
|
columns.</para>
|
||||||
|
|
||||||
|
<para>It is possible to leave out the type information for all or some
|
||||||
|
of the scalars.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS")
|
||||||
|
.addScalar("ID", Hibernate.LONG)
|
||||||
|
.addScalar("NAME")
|
||||||
|
.addScalar("BIRTHDATE")
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>This is essentially the same query as before, but now
|
||||||
|
<literal>ResultSetMetaData</literal> is used to decide the type of NAME
|
||||||
|
and BIRTHDATE where as the type of ID is explicitly specified.</para>
|
||||||
|
|
||||||
|
<para>How the java.sql.Types returned from ResultSetMetaData is mapped
|
||||||
|
to Hibernate types is controlled by the Dialect. If a specific type is
|
||||||
|
not mapped or does not result in the expected type it is possible to
|
||||||
|
customize it via calls to <literal>registerHibernateType</literal> in
|
||||||
|
the Dialect.</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Entity queries</title>
|
||||||
|
|
||||||
|
<para>The above queries were all about returning scalar values,
|
||||||
|
basically returning the "raw" values from the resultset. The following
|
||||||
|
shows how to get entity objects from a native sql query via
|
||||||
|
<literal>addEntity()</literal>.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
|
||||||
|
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>This query specified:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>the SQL query string</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>the entity returned by the query</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>Assuming that Cat is mapped as a class with the columns ID, NAME
|
||||||
|
and BIRTHDATE the above queries will both return a List where each
|
||||||
|
element is a Cat entity.</para>
|
||||||
|
|
||||||
|
<para>If the entity is mapped with a <literal>many-to-one</literal> to
|
||||||
|
another entity it is required to also return this when performing the
|
||||||
|
native query, otherwise a database specific "column not found" error
|
||||||
|
will occur. The additional columns will automatically be returned when
|
||||||
|
using the * notation, but we prefer to be explicit as in the following
|
||||||
|
example for a <literal>many-to-one</literal> to a
|
||||||
|
<literal>Dog</literal>:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class);
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>This will allow cat.getDog() to function properly.</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Handling associations and collections</title>
|
||||||
|
|
||||||
|
<para>It is possible to eagerly join in the <literal>Dog</literal> to
|
||||||
|
avoid the possible extra roundtrip for initializing the proxy. This is
|
||||||
|
done via the <literal>addJoin()</literal> method, which allows you to
|
||||||
|
join in an association or collection.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addJoin("cat.dog");
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>In this example the returned <literal>Cat</literal>'s will have
|
||||||
|
their <literal>dog</literal> property fully initialized without any
|
||||||
|
extra roundtrip to the database. Notice that we added a alias name
|
||||||
|
("cat") to be able to specify the target property path of the join. It
|
||||||
|
is possible to do the same eager joining for collections, e.g. if the
|
||||||
|
<literal>Cat</literal> had a one-to-many to <literal>Dog</literal>
|
||||||
|
instead.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID")
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addJoin("cat.dogs");
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<p>At this stage we are reaching the limits of what is possible with
|
||||||
|
native queries without starting to enhance the sql queries to make them
|
||||||
|
usable in Hibernate; the problems starts to arise when returning
|
||||||
|
multiple entities of the same type or when the default alias/column
|
||||||
|
names are not enough.</p>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Returning multiple entities</title>
|
||||||
|
|
||||||
|
<para>Until now the result set column names are assumed to be the same
|
||||||
|
as the column names specified in the mapping document. This can be
|
||||||
|
problematic for SQL queries which join multiple tables, since the same
|
||||||
|
column names may appear in more than one table.</para>
|
||||||
|
|
||||||
|
<para>Column alias injection is needed in the following query (which
|
||||||
|
most likely will fail):</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT c.*, m.* FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addEntity("mother", Cat.class)
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>The intention for this query is to return two Cat instances per
|
||||||
|
row, a cat and its mother. This will fail since there is a conflict of
|
||||||
|
names since they are mapped to the same column names and on some
|
||||||
|
databases the returned column aliases will most likely be on the form
|
||||||
|
"c.ID", "c.NAME", etc. which are not equal to the columns specificed in
|
||||||
|
the mappings ("ID" and "NAME").</para>
|
||||||
|
|
||||||
|
<para>The following form is not vulnerable to column name
|
||||||
|
duplication:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT {cat.*}, {mother.*} FROM CATS c, CATS m WHERE c.MOTHER_ID = c.ID")
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addEntity("mother", Cat.class)
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>This query specified:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>the SQL query string, with placeholders for Hibernate to
|
||||||
|
inject column aliases</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>the entities returned by the query</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>The {cat.*} and {mother.*} notation used above is a shorthand for
|
||||||
|
"all properties". Alternatively, you may list the columns explicity, but
|
||||||
|
even in this case we let Hibernate inject the SQL column aliases for
|
||||||
|
each property. The placeholder for a column alias is just the property
|
||||||
|
name qualified by the table alias. In the following example, we retrieve
|
||||||
|
Cats and their mothers from a different table (cat_log) to the one
|
||||||
|
declared in the mapping metadata. Notice that we may even use the
|
||||||
|
property aliases in the where clause if we like.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[String sql = "SELECT ID as {c.id}, NAME as {c.name}, " +
|
||||||
|
"BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " +
|
||||||
|
"FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID";
|
||||||
|
|
||||||
|
List loggedCats = sess.createSQLQuery(sql)
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addEntity("mother", Cat.class).list()
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<sect3 id="querysql-aliasreferences" revision="2">
|
||||||
|
<title>Alias and property references</title>
|
||||||
|
|
||||||
|
<para>For most cases the above alias injection is needed, but for
|
||||||
|
queries relating to more complex mappings like composite properties,
|
||||||
|
inheritance discriminators, collections etc. there are some specific
|
||||||
|
aliases to use to allow Hibernate to inject the proper aliases.</para>
|
||||||
|
|
||||||
|
<para>The following table shows the different possibilities of using
|
||||||
|
the alias injection. Note: the alias names in the result are examples,
|
||||||
|
each alias will have a unique and probably different name when
|
||||||
|
used.</para>
|
||||||
|
|
||||||
|
<table frame="topbot" id="aliasinjection-summary">
|
||||||
|
<title>Alias injection names</title>
|
||||||
|
|
||||||
|
<tgroup cols="3">
|
||||||
|
<colspec colwidth="1*" />
|
||||||
|
|
||||||
|
<colspec colwidth="1*" />
|
||||||
|
|
||||||
|
<colspec colwidth="2.5*" />
|
||||||
|
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Description</entry>
|
||||||
|
|
||||||
|
<entry>Syntax</entry>
|
||||||
|
|
||||||
|
<entry>Example</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>A simple property</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].[propertyname]</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>A_NAME as {item.name}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>A composite property</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].[componentname].[propertyname]}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>CURRENCY as {item.amount.currency}, VALUE as
|
||||||
|
{item.amount.value}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>Discriminator of an entity</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].class}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>DISC as {item.class}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>All properties of an entity</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].*}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>{item.*}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>A collection key</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].key}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>ORGID as {coll.key}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>The id of an collection</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].id}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>EMPID as {coll.id}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>The element of an collection</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].element}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>XID as {coll.element}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>roperty of the element in the collection</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].element.[propertyname]}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>NAME as {coll.element.name}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>All properties of the element in the collection</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].element.*}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>{coll.element.*}</literal></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>All properties of the the collection</entry>
|
||||||
|
|
||||||
|
<entry><literal>{[aliasname].*}</literal></entry>
|
||||||
|
|
||||||
|
<entry><literal>{coll.*}</literal></entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
</sect3>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Returning non-managed entities</title>
|
||||||
|
|
||||||
|
<para>It is possible to apply a ResultTransformer to native sql queries. Allowing it to e.g. return non-managed entities.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
|
||||||
|
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))]]></programlisting>
|
||||||
|
|
||||||
|
<para>This query specified:</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>the SQL query string</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>a result transformer</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The above query will return a list of <literal>CatDTO</literal> which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding
|
||||||
|
properties or fields.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Handling inheritance</title>
|
||||||
|
|
||||||
|
<para>Native sql queries which query for entities that is mapped as part
|
||||||
|
of an inheritance must include all properties for the baseclass and all
|
||||||
|
it subclasses.</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2>
|
||||||
|
<title>Parameters</title>
|
||||||
|
|
||||||
|
<para>Native sql queries support positional as well as named
|
||||||
|
parameters:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class);
|
||||||
|
List pusList = query.setString(0, "Pus%").list();
|
||||||
|
|
||||||
|
query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class);
|
||||||
|
List pusList = query.setString("name", "Pus%").list(); ]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-namedqueries" revision="3">
|
||||||
|
<title>Named SQL queries</title>
|
||||||
|
|
||||||
|
<para>Named SQL queries may be defined in the mapping document and called
|
||||||
|
in exactly the same way as a named HQL query. In this case, we do
|
||||||
|
<emphasis>not</emphasis> need to call
|
||||||
|
<literal>addEntity()</literal>.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="persons">
|
||||||
|
<return alias="person" class="eg.Person"/>
|
||||||
|
SELECT person.NAME AS {person.name},
|
||||||
|
person.AGE AS {person.age},
|
||||||
|
person.SEX AS {person.sex}
|
||||||
|
FROM PERSON person
|
||||||
|
WHERE person.NAME LIKE :namePattern
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List people = sess.getNamedQuery("persons")
|
||||||
|
.setString("namePattern", namePattern)
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>The <literal><return-join></literal> and
|
||||||
|
<literal><load-collection></literal> elements are used to join
|
||||||
|
associations and define queries which initialize collections,
|
||||||
|
respectively.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="personsWith">
|
||||||
|
<return alias="person" class="eg.Person"/>
|
||||||
|
<return-join alias="address" property="person.mailingAddress"/>
|
||||||
|
SELECT person.NAME AS {person.name},
|
||||||
|
person.AGE AS {person.age},
|
||||||
|
person.SEX AS {person.sex},
|
||||||
|
adddress.STREET AS {address.street},
|
||||||
|
adddress.CITY AS {address.city},
|
||||||
|
adddress.STATE AS {address.state},
|
||||||
|
adddress.ZIP AS {address.zip}
|
||||||
|
FROM PERSON person
|
||||||
|
JOIN ADDRESS adddress
|
||||||
|
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
|
||||||
|
WHERE person.NAME LIKE :namePattern
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>A named SQL query may return a scalar value. You must declare the
|
||||||
|
column alias and Hibernate type using the
|
||||||
|
<literal><return-scalar></literal> element:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||||
|
<return-scalar column="name" type="string"/>
|
||||||
|
<return-scalar column="age" type="long"/>
|
||||||
|
SELECT p.NAME AS name,
|
||||||
|
p.AGE AS age,
|
||||||
|
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>You can externalize the resultset mapping informations in a
|
||||||
|
<literal><resultset></literal> element to either reuse them accross
|
||||||
|
several named queries or through the
|
||||||
|
<literal>setResultSetMapping()</literal> API.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<resultset name="personAddress">
|
||||||
|
<return alias="person" class="eg.Person"/>
|
||||||
|
<return-join alias="address" property="person.mailingAddress"/>
|
||||||
|
</resultset>
|
||||||
|
|
||||||
|
<sql-query name="personsWith" resultset-ref="personAddress">
|
||||||
|
SELECT person.NAME AS {person.name},
|
||||||
|
person.AGE AS {person.age},
|
||||||
|
person.SEX AS {person.sex},
|
||||||
|
adddress.STREET AS {address.street},
|
||||||
|
adddress.CITY AS {address.city},
|
||||||
|
adddress.STATE AS {address.state},
|
||||||
|
adddress.ZIP AS {address.zip}
|
||||||
|
FROM PERSON person
|
||||||
|
JOIN ADDRESS adddress
|
||||||
|
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
|
||||||
|
WHERE person.NAME LIKE :namePattern
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>You can alternatively use the resultset mapping information in your
|
||||||
|
hbm files directly in java code.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createSQLQuery(
|
||||||
|
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
|
||||||
|
)
|
||||||
|
.setResultSetMapping("catAndKitten")
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<sect2 id="propertyresults">
|
||||||
|
<title>Using return-property to explicitly specify column/alias
|
||||||
|
names</title>
|
||||||
|
|
||||||
|
<para>With <literal><return-property></literal> you can explicitly
|
||||||
|
tell Hibernate what column aliases to use, instead of using the
|
||||||
|
<literal>{}</literal>-syntax to let Hibernate inject its own
|
||||||
|
aliases.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||||
|
<return alias="person" class="eg.Person">
|
||||||
|
<return-property name="name" column="myName"/>
|
||||||
|
<return-property name="age" column="myAge"/>
|
||||||
|
<return-property name="sex" column="mySex"/>
|
||||||
|
</return>
|
||||||
|
SELECT person.NAME AS myName,
|
||||||
|
person.AGE AS myAge,
|
||||||
|
person.SEX AS mySex,
|
||||||
|
FROM PERSON person WHERE person.NAME LIKE :name
|
||||||
|
</sql-query>
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para><literal><return-property></literal> also works with
|
||||||
|
multiple columns. This solves a limitation with the
|
||||||
|
<literal>{}</literal>-syntax which can not allow fine grained control of
|
||||||
|
multi-column properties.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="organizationCurrentEmployments">
|
||||||
|
<return alias="emp" class="Employment">
|
||||||
|
<return-property name="salary">
|
||||||
|
<return-column name="VALUE"/>
|
||||||
|
<return-column name="CURRENCY"/>
|
||||||
|
</return-property>
|
||||||
|
<return-property name="endDate" column="myEndDate"/>
|
||||||
|
</return>
|
||||||
|
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
|
||||||
|
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
|
||||||
|
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
|
||||||
|
FROM EMPLOYMENT
|
||||||
|
WHERE EMPLOYER = :id AND ENDDATE IS NULL
|
||||||
|
ORDER BY STARTDATE ASC
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>Notice that in this example we used
|
||||||
|
<literal><return-property></literal> in combination with the
|
||||||
|
<literal>{}</literal>-syntax for injection. Allowing users to choose how
|
||||||
|
they want to refer column and properties.</para>
|
||||||
|
|
||||||
|
<para>If your mapping has a discriminator you must use
|
||||||
|
<literal><return-discriminator></literal> to specify the
|
||||||
|
discriminator column.</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="sp_query" revision="1">
|
||||||
|
<title>Using stored procedures for querying</title>
|
||||||
|
|
||||||
|
<para>Hibernate 3 introduces support for queries via stored procedures
|
||||||
|
and functions. Most of the following documentation is equivalent for
|
||||||
|
both. The stored procedure/function must return a resultset as the first
|
||||||
|
out-parameter to be able to work with Hibernate. An example of such a
|
||||||
|
stored function in Oracle 9 and higher is as follows:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION selectAllEmployments
|
||||||
|
RETURN SYS_REFCURSOR
|
||||||
|
AS
|
||||||
|
st_cursor SYS_REFCURSOR;
|
||||||
|
BEGIN
|
||||||
|
OPEN st_cursor FOR
|
||||||
|
SELECT EMPLOYEE, EMPLOYER,
|
||||||
|
STARTDATE, ENDDATE,
|
||||||
|
REGIONCODE, EID, VALUE, CURRENCY
|
||||||
|
FROM EMPLOYMENT;
|
||||||
|
RETURN st_cursor;
|
||||||
|
END;]]></programlisting>
|
||||||
|
|
||||||
|
<para>To use this query in Hibernate you need to map it via a named
|
||||||
|
query.</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="selectAllEmployees_SP" callable="true">
|
||||||
|
<return alias="emp" class="Employment">
|
||||||
|
<return-property name="employee" column="EMPLOYEE"/>
|
||||||
|
<return-property name="employer" column="EMPLOYER"/>
|
||||||
|
<return-property name="startDate" column="STARTDATE"/>
|
||||||
|
<return-property name="endDate" column="ENDDATE"/>
|
||||||
|
<return-property name="regionCode" column="REGIONCODE"/>
|
||||||
|
<return-property name="id" column="EID"/>
|
||||||
|
<return-property name="salary">
|
||||||
|
<return-column name="VALUE"/>
|
||||||
|
<return-column name="CURRENCY"/>
|
||||||
|
</return-property>
|
||||||
|
</return>
|
||||||
|
{ ? = call selectAllEmployments() }
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>Notice stored procedures currently only return scalars and
|
||||||
|
entities. <literal><return-join></literal> and
|
||||||
|
<literal><load-collection></literal> are not supported.</para>
|
||||||
|
|
||||||
|
<sect3 id="querysql-limits-storedprocedures" revision="1">
|
||||||
|
<title>Rules/limitations for using stored procedures</title>
|
||||||
|
|
||||||
|
<para>To use stored procedures with Hibernate the procedures/functions
|
||||||
|
have to follow some rules. If they do not follow those rules they are
|
||||||
|
not usable with Hibernate. If you still want to use these procedures
|
||||||
|
you have to execute them via <literal>session.connection()</literal>.
|
||||||
|
The rules are different for each database, since database vendors have
|
||||||
|
different stored procedure semantics/syntax.</para>
|
||||||
|
|
||||||
|
<para>Stored procedure queries can't be paged with
|
||||||
|
<literal>setFirstResult()/setMaxResults()</literal>.</para>
|
||||||
|
|
||||||
|
<para>Recommended call form is standard SQL92: <literal>{ ? = call
|
||||||
|
functionName(<parameters>) }</literal> or <literal>{ ? = call
|
||||||
|
procedureName(<parameters>}</literal>. Native call syntax is not
|
||||||
|
supported.</para>
|
||||||
|
|
||||||
|
<para>For Oracle the following rules apply:</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>A function must return a result set. The first parameter of
|
||||||
|
a procedure must be an <literal>OUT</literal> that returns a
|
||||||
|
result set. This is done by using a
|
||||||
|
<literal>SYS_REFCURSOR</literal> type in Oracle 9 or 10. In Oracle
|
||||||
|
you need to define a <literal>REF CURSOR</literal> type, see
|
||||||
|
Oracle literature.</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>For Sybase or MS SQL server the following rules apply:</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>The procedure must return a result set. Note that since
|
||||||
|
these servers can/will return multiple result sets and update
|
||||||
|
counts, Hibernate will iterate the results and take the first
|
||||||
|
result that is a result set as its return value. Everything else
|
||||||
|
will be discarded.</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>If you can enable <literal>SET NOCOUNT ON</literal> in your
|
||||||
|
procedure it will probably be more efficient, but this is not a
|
||||||
|
requirement.</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</sect3>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-cud">
|
||||||
|
<title>Custom SQL for create, update and delete</title>
|
||||||
|
|
||||||
|
<para>Hibernate3 can use custom SQL statements for create, update, and
|
||||||
|
delete operations. The class and collection persisters in Hibernate
|
||||||
|
already contain a set of configuration time generated strings (insertsql,
|
||||||
|
deletesql, updatesql etc.). The mapping tags
|
||||||
|
<literal><sql-insert></literal>,
|
||||||
|
<literal><sql-delete></literal>, and
|
||||||
|
<literal><sql-update></literal> override these strings:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
|
||||||
|
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
|
||||||
|
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>The SQL is directly executed in your database, so you are free to
|
||||||
|
use any dialect you like. This will of course reduce the portability of
|
||||||
|
your mapping if you use database specific SQL.</para>
|
||||||
|
|
||||||
|
<para>Stored procedures are supported if the <literal>callable</literal>
|
||||||
|
attribute is set:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
|
||||||
|
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
|
||||||
|
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>The order of the positional parameters are currently vital, as they
|
||||||
|
must be in the same sequence as Hibernate expects them.</para>
|
||||||
|
|
||||||
|
<para>You can see the expected order by enabling debug logging for the
|
||||||
|
<literal>org.hibernate.persister.entity</literal> level. With this level
|
||||||
|
enabled Hibernate will print out the static SQL that is used to create,
|
||||||
|
update, delete etc. entities. (To see the expected sequence, remember to
|
||||||
|
not include your custom SQL in the mapping files as that will override the
|
||||||
|
Hibernate generated static sql.)</para>
|
||||||
|
|
||||||
|
<para>The stored procedures are in most cases (read: better do it than
|
||||||
|
not) required to return the number of rows inserted/updated/deleted, as
|
||||||
|
Hibernate has some runtime checks for the success of the statement.
|
||||||
|
Hibernate always registers the first statement parameter as a numeric
|
||||||
|
output parameter for the CUD operations:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
|
||||||
|
RETURN NUMBER IS
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
update PERSON
|
||||||
|
set
|
||||||
|
NAME = uname,
|
||||||
|
where
|
||||||
|
ID = uid;
|
||||||
|
|
||||||
|
return SQL%ROWCOUNT;
|
||||||
|
|
||||||
|
END updatePerson;]]></programlisting>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-load">
|
||||||
|
<title>Custom SQL for loading</title>
|
||||||
|
|
||||||
|
<para>You may also declare your own SQL (or HQL) queries for entity
|
||||||
|
loading:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="person">
|
||||||
|
<return alias="pers" class="Person" lock-mode="upgrade"/>
|
||||||
|
SELECT NAME AS {pers.name}, ID AS {pers.id}
|
||||||
|
FROM PERSON
|
||||||
|
WHERE ID=?
|
||||||
|
FOR UPDATE
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>This is just a named query declaration, as discussed earlier. You
|
||||||
|
may reference this named query in a class mapping:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<loader query-ref="person"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>This even works with stored procedures.</para>
|
||||||
|
|
||||||
|
<para>You may even define a query for collection loading:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="employments" inverse="true">
|
||||||
|
<key/>
|
||||||
|
<one-to-many class="Employment"/>
|
||||||
|
<loader query-ref="employments"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="employments">
|
||||||
|
<load-collection alias="emp" role="Person.employments"/>
|
||||||
|
SELECT {emp.*}
|
||||||
|
FROM EMPLOYMENT emp
|
||||||
|
WHERE EMPLOYER = :id
|
||||||
|
ORDER BY STARTDATE ASC, EMPLOYEE ASC
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>You could even define an entity loader that loads a collection by
|
||||||
|
join fetching:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="person">
|
||||||
|
<return alias="pers" class="Person"/>
|
||||||
|
<return-join alias="emp" property="pers.employments"/>
|
||||||
|
SELECT NAME AS {pers.*}, {emp.*}
|
||||||
|
FROM PERSON pers
|
||||||
|
LEFT OUTER JOIN EMPLOYMENT emp
|
||||||
|
ON pers.ID = emp.PERSON_ID
|
||||||
|
WHERE ID=?
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
</sect1>
|
||||||
|
</chapter>
|
|
@ -0,0 +1,604 @@
|
||||||
|
<chapter id="toolsetguide" revision="2">
|
||||||
|
<title>Toolset Guide</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Roundtrip engineering with Hibernate is possible using a set of Eclipse plugins,
|
||||||
|
commandline tools, as well as Ant tasks.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <emphasis>Hibernate Tools</emphasis> currently include plugins for the Eclipse
|
||||||
|
IDE as well as Ant tasks for reverse engineering of existing databases:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Mapping Editor:</emphasis> An editor for Hibernate XML mapping files,
|
||||||
|
supporting auto-completion and syntax highlighting. It also supports semantic
|
||||||
|
auto-completion for class names and property/field names, making it much more versatile than a normal XML editor.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Console:</emphasis> The console is a new view in Eclipse. In addition to
|
||||||
|
a tree overview of your console configurations, you also get an interactive view
|
||||||
|
of your persistent classes and their relationships. The console allows you to
|
||||||
|
execute HQL queries against your database and browse the result directly in
|
||||||
|
Eclipse.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Development Wizards:</emphasis> Several wizards are provided with the
|
||||||
|
Hibernate Eclipse tools; you can use a wizard to quickly generate Hibernate configuration
|
||||||
|
(cfg.xml) files, or you may even completely reverse engineer an existing database schema
|
||||||
|
into POJO source files and Hibernate mapping files. The reverse engineering wizard
|
||||||
|
supports customizable templates.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Ant Tasks:</emphasis>
|
||||||
|
</para></listitem>
|
||||||
|
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Please refer to the <emphasis>Hibernate Tools</emphasis> package and it's documentation
|
||||||
|
for more information.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
However, the Hibernate main package comes bundled with an integrated tool (it can even
|
||||||
|
be used from "inside" Hibernate on-the-fly): <emphasis>SchemaExport</emphasis> aka
|
||||||
|
<literal>hbm2ddl</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="toolsetguide-s1" revision="2">
|
||||||
|
<title>Automatic schema generation</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
DDL may be generated from your mapping files by a Hibernate utility. The generated
|
||||||
|
schema includes referential integrity constraints (primary and foreign keys) for
|
||||||
|
entity and collection tables. Tables and sequences are also created for mapped
|
||||||
|
identifier generators.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You <emphasis>must</emphasis> specify a SQL <literal>Dialect</literal> via the
|
||||||
|
<literal>hibernate.dialect</literal> property when using this tool, as DDL
|
||||||
|
is highly vendor specific.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
First, customize your mapping files to improve the generated schema.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-2" revision="3">
|
||||||
|
<title>Customizing the schema</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Many Hibernate mapping elements define optional attributes named <literal>length</literal>,
|
||||||
|
<literal>precision</literal> and <literal>scale</literal>. You may set the length, precision
|
||||||
|
and scale of a column with this attribute.
|
||||||
|
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="zip" length="5"/>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[<property name="balance" precision="12" scale="2"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Some tags also accept a <literal>not-null</literal> attribute (for generating a
|
||||||
|
<literal>NOT NULL</literal> constraint on table columns) and a <literal>unique</literal>
|
||||||
|
attribute (for generating <literal>UNIQUE</literal> constraint on table columns).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="bar" column="barId" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<element column="serialNumber" type="long" not-null="true" unique="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>unique-key</literal> attribute may be used to group columns in
|
||||||
|
a single unique key constraint. Currently, the specified value of the
|
||||||
|
<literal>unique-key</literal> attribute is <emphasis>not</emphasis> used
|
||||||
|
to name the constraint in the generated DDL, only to group the columns in
|
||||||
|
the mapping file.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/>
|
||||||
|
<property name="employeeId" unique-key="OrgEmployee"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
An <literal>index</literal> attribute specifies the name of an index that
|
||||||
|
will be created using the mapped column or columns. Multiple columns may be
|
||||||
|
grouped into the same index, simply by specifying the same index name.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="lastName" index="CustName"/>
|
||||||
|
<property name="firstName" index="CustName"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A <literal>foreign-key</literal> attribute may be used to override the name
|
||||||
|
of any generated foreign key constraint.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Many mapping elements also accept a child <literal><column></literal> element.
|
||||||
|
This is particularly useful for mapping multi-column types:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="name" type="my.customtypes.Name"/>
|
||||||
|
<column name="last" not-null="true" index="bar_idx" length="30"/>
|
||||||
|
<column name="first" not-null="true" index="bar_idx" length="20"/>
|
||||||
|
<column name="initial"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>default</literal> attribute lets you specify a default value for
|
||||||
|
a column (you should assign the same value to the mapped property before
|
||||||
|
saving a new instance of the mapped class).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="credits" type="integer" insert="false">
|
||||||
|
<column name="credits" default="10"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<version name="version" type="integer" insert="false">
|
||||||
|
<column name="version" default="0"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>sql-type</literal> attribute allows the user to override the default
|
||||||
|
mapping of a Hibernate type to SQL datatype.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="balance" type="float">
|
||||||
|
<column name="balance" sql-type="decimal(13,3)"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>check</literal> attribute allows you to specify a check constraint.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="foo" type="integer">
|
||||||
|
<column name="foo" check="foo > 10"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Foo" table="foos" check="bar < 100.0">
|
||||||
|
...
|
||||||
|
<property name="bar" type="float"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<table frame="topbot" id="schemattributes-summary" revision="2">
|
||||||
|
<title>Summary</title>
|
||||||
|
<tgroup cols="3">
|
||||||
|
<colspec colwidth="1*"/>
|
||||||
|
<colspec colwidth="1*"/>
|
||||||
|
<colspec colwidth="2.5*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Attribute</entry>
|
||||||
|
<entry>Values</entry>
|
||||||
|
<entry>Interpretation</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>length</literal></entry>
|
||||||
|
<entry>number</entry>
|
||||||
|
<entry>column length</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>precision</literal></entry>
|
||||||
|
<entry>number</entry>
|
||||||
|
<entry>column decimal precision</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>scale</literal></entry>
|
||||||
|
<entry>number</entry>
|
||||||
|
<entry>column decimal scale</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>not-null</literal></entry>
|
||||||
|
<entry><literal>true|false</literal></entry>
|
||||||
|
<entry>specfies that the column should be non-nullable</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>unique</literal></entry>
|
||||||
|
<entry><literal>true|false</literal></entry>
|
||||||
|
<entry>specifies that the column should have a unique constraint</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>index</literal></entry>
|
||||||
|
<entry><literal>index_name</literal></entry>
|
||||||
|
<entry>specifies the name of a (multi-column) index</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>unique-key</literal></entry>
|
||||||
|
<entry><literal>unique_key_name</literal></entry>
|
||||||
|
<entry>specifies the name of a multi-column unique constraint</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>foreign-key</literal></entry>
|
||||||
|
<entry><literal>foreign_key_name</literal></entry>
|
||||||
|
<entry>
|
||||||
|
specifies the name of the foreign key constraint generated
|
||||||
|
for an association, for a <literal><one-to-one></literal>,
|
||||||
|
<literal><many-to-one></literal>, <literal><key></literal>,
|
||||||
|
or <literal><many-to-many></literal> mapping element. Note that
|
||||||
|
<literal>inverse="true"</literal> sides will not be considered
|
||||||
|
by <literal>SchemaExport</literal>.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>sql-type</literal></entry>
|
||||||
|
<entry><literal>SQL column type</literal></entry>
|
||||||
|
<entry>
|
||||||
|
overrides the default column type (attribute of
|
||||||
|
<literal><column></literal> element only)
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>default</literal></entry>
|
||||||
|
<entry>SQL expression</entry>
|
||||||
|
<entry>
|
||||||
|
specify a default value for the column
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>check</literal></entry>
|
||||||
|
<entry>SQL expression</entry>
|
||||||
|
<entry>
|
||||||
|
create an SQL check constraint on either column or table
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal><comment></literal> element allows you to specify comments
|
||||||
|
for the generated schema.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Customer" table="CurCust">
|
||||||
|
<comment>Current customers only</comment>
|
||||||
|
...
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="balance">
|
||||||
|
<column name="bal">
|
||||||
|
<comment>Balance in USD</comment>
|
||||||
|
</column>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This results in a <literal>comment on table</literal> or
|
||||||
|
<literal>comment on column</literal> statement in the generated
|
||||||
|
DDL (where supported).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-3" revision="2">
|
||||||
|
<title>Running the tool</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>SchemaExport</literal> tool writes a DDL script to standard out and/or
|
||||||
|
executes the DDL statements.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>java -cp </literal><emphasis>hibernate_classpaths</emphasis>
|
||||||
|
<literal>org.hibernate.tool.hbm2ddl.SchemaExport</literal> <emphasis>options mapping_files</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title><literal>SchemaExport</literal> Command Line Options</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Option</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--quiet</literal></entry>
|
||||||
|
<entry>don't output the script to stdout</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--drop</literal></entry>
|
||||||
|
<entry>only drop the tables</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--create</literal></entry>
|
||||||
|
<entry>only create the tables</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--text</literal></entry>
|
||||||
|
<entry>don't export to the database</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--output=my_schema.ddl</literal></entry>
|
||||||
|
<entry>output the ddl script to a file</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
|
||||||
|
<entry>select a <literal>NamingStrategy</literal></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
|
||||||
|
<entry>read Hibernate configuration from an XML file</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||||
|
<entry>read database properties from a file</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--format</literal></entry>
|
||||||
|
<entry>format the generated SQL nicely in the script</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--delimiter=;</literal></entry>
|
||||||
|
<entry>set an end of line delimiter for the script</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may even embed <literal>SchemaExport</literal> in your application:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = ....;
|
||||||
|
new SchemaExport(cfg).create(false, true);]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-4">
|
||||||
|
<title>Properties</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Database properties may be specified
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>as system properties with <literal>-D</literal><emphasis><property></emphasis></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>in <literal>hibernate.properties</literal></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>in a named properties file with <literal>--properties</literal></para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The needed properties are:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>SchemaExport Connection Properties</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Property Name</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.driver_class</literal></entry>
|
||||||
|
<entry>jdbc driver class</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.url</literal></entry>
|
||||||
|
<entry>jdbc url</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.username</literal></entry>
|
||||||
|
<entry>database user</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.password</literal></entry>
|
||||||
|
<entry>user password</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.dialect</literal></entry>
|
||||||
|
<entry>dialect</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-5">
|
||||||
|
<title>Using Ant</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can call <literal>SchemaExport</literal> from your Ant build script:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<target name="schemaexport">
|
||||||
|
<taskdef name="schemaexport"
|
||||||
|
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
|
||||||
|
classpathref="class.path"/>
|
||||||
|
|
||||||
|
<schemaexport
|
||||||
|
properties="hibernate.properties"
|
||||||
|
quiet="no"
|
||||||
|
text="no"
|
||||||
|
drop="no"
|
||||||
|
delimiter=";"
|
||||||
|
output="schema-export.sql">
|
||||||
|
<fileset dir="src">
|
||||||
|
<include name="**/*.hbm.xml"/>
|
||||||
|
</fileset>
|
||||||
|
</schemaexport>
|
||||||
|
</target>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-6" revision="2">
|
||||||
|
<title>Incremental schema updates</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>SchemaUpdate</literal> tool will update an existing schema with "incremental" changes.
|
||||||
|
Note that <literal>SchemaUpdate</literal> depends heavily upon the JDBC metadata API, so it will
|
||||||
|
not work with all JDBC drivers.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>java -cp </literal><emphasis>hibernate_classpaths</emphasis>
|
||||||
|
<literal>org.hibernate.tool.hbm2ddl.SchemaUpdate</literal> <emphasis>options mapping_files</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title><literal>SchemaUpdate</literal> Command Line Options</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Option</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--quiet</literal></entry>
|
||||||
|
<entry>don't output the script to stdout</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--text</literal></entry>
|
||||||
|
<entry>don't export the script to the database</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
|
||||||
|
<entry>select a <literal>NamingStrategy</literal></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||||
|
<entry>read database properties from a file</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
|
||||||
|
<entry>specify a <literal>.cfg.xml</literal> file</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may embed <literal>SchemaUpdate</literal> in your application:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = ....;
|
||||||
|
new SchemaUpdate(cfg).execute(false);]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-7">
|
||||||
|
<title>Using Ant for incremental schema updates</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can call <literal>SchemaUpdate</literal> from the Ant script:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<target name="schemaupdate">
|
||||||
|
<taskdef name="schemaupdate"
|
||||||
|
classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask"
|
||||||
|
classpathref="class.path"/>
|
||||||
|
|
||||||
|
<schemaupdate
|
||||||
|
properties="hibernate.properties"
|
||||||
|
quiet="no">
|
||||||
|
<fileset dir="src">
|
||||||
|
<include name="**/*.hbm.xml"/>
|
||||||
|
</fileset>
|
||||||
|
</schemaupdate>
|
||||||
|
</target>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-8" revision="1">
|
||||||
|
<title>Schema validation</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>SchemaValidator</literal> tool will validate that the existing database schema "matches"
|
||||||
|
your mapping documents. Note that <literal>SchemaValidator</literal> depends heavily upon the JDBC
|
||||||
|
metadata API, so it will not work with all JDBC drivers. This tool is extremely useful for testing.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>java -cp </literal><emphasis>hibernate_classpaths</emphasis>
|
||||||
|
<literal>org.hibernate.tool.hbm2ddl.SchemaValidator</literal> <emphasis>options mapping_files</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title><literal>SchemaValidator</literal> Command Line Options</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Option</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--naming=eg.MyNamingStrategy</literal></entry>
|
||||||
|
<entry>select a <literal>NamingStrategy</literal></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||||
|
<entry>read database properties from a file</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
|
||||||
|
<entry>specify a <literal>.cfg.xml</literal> file</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You may embed <literal>SchemaValidator</literal> in your application:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = ....;
|
||||||
|
new SchemaValidator(cfg).validate();]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-9">
|
||||||
|
<title>Using Ant for schema validation</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You can call <literal>SchemaValidator</literal> from the Ant script:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<target name="schemavalidate">
|
||||||
|
<taskdef name="schemavalidator"
|
||||||
|
classname="org.hibernate.tool.hbm2ddl.SchemaValidatorTask"
|
||||||
|
classpathref="class.path"/>
|
||||||
|
|
||||||
|
<schemavalidator
|
||||||
|
properties="hibernate.properties">
|
||||||
|
<fileset dir="src">
|
||||||
|
<include name="**/*.hbm.xml"/>
|
||||||
|
</fileset>
|
||||||
|
</schemaupdate>
|
||||||
|
</target>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,286 @@
|
||||||
|
<chapter id="xml">
|
||||||
|
<title>XML Mapping</title>
|
||||||
|
|
||||||
|
<para><emphasis>
|
||||||
|
Note that this is an experimental feature in Hibernate 3.0 and is under
|
||||||
|
extremely active development.
|
||||||
|
</emphasis></para>
|
||||||
|
|
||||||
|
<sect1 id="xml-intro" revision="1">
|
||||||
|
<title>Working with XML data</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate lets you work with persistent XML data in much the same way
|
||||||
|
you work with persistent POJOs. A parsed XML tree can be thought of
|
||||||
|
as just another way to represent the relational data at the object level,
|
||||||
|
instead of POJOs.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate supports dom4j as API for manipulating XML trees. You can write
|
||||||
|
queries that retrieve dom4j trees from the database and have any
|
||||||
|
modification you make to the tree automatically synchronized to the
|
||||||
|
database. You can even take an XML document, parse it using dom4j, and
|
||||||
|
write it to the database with any of Hibernate's basic operations:
|
||||||
|
<literal>persist(), saveOrUpdate(), merge(), delete(), replicate()</literal>
|
||||||
|
(merging is not yet supported).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This feature has many applications including data import/export,
|
||||||
|
externalization of entity data via JMS or SOAP and XSLT-based reporting.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A single mapping may be used to simultaneously map properties of a class
|
||||||
|
and nodes of an XML document to the database, or, if there is no class to map,
|
||||||
|
it may be used to map just the XML.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="xml-intro-mapping">
|
||||||
|
<title>Specifying XML and class mapping together</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Here is an example of mapping a POJO and XML simultaneously:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Account"
|
||||||
|
table="ACCOUNTS"
|
||||||
|
node="account">
|
||||||
|
|
||||||
|
<id name="accountId"
|
||||||
|
column="ACCOUNT_ID"
|
||||||
|
node="@id"/>
|
||||||
|
|
||||||
|
<many-to-one name="customer"
|
||||||
|
column="CUSTOMER_ID"
|
||||||
|
node="customer/@id"
|
||||||
|
embed-xml="false"/>
|
||||||
|
|
||||||
|
<property name="balance"
|
||||||
|
column="BALANCE"
|
||||||
|
node="balance"/>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="xml-onlyxml">
|
||||||
|
<title>Specifying only an XML mapping</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Here is an example where there is no POJO class:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class entity-name="Account"
|
||||||
|
table="ACCOUNTS"
|
||||||
|
node="account">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="ACCOUNT_ID"
|
||||||
|
node="@id"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<many-to-one name="customerId"
|
||||||
|
column="CUSTOMER_ID"
|
||||||
|
node="customer/@id"
|
||||||
|
embed-xml="false"
|
||||||
|
entity-name="Customer"/>
|
||||||
|
|
||||||
|
<property name="balance"
|
||||||
|
column="BALANCE"
|
||||||
|
node="balance"
|
||||||
|
type="big_decimal"/>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This mapping allows you to access the data as a dom4j tree, or as a graph of
|
||||||
|
property name/value pairs (java <literal>Map</literal>s). The property names
|
||||||
|
are purely logical constructs that may be referred to in HQL queries.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="xml-mapping" revision="1">
|
||||||
|
<title>XML mapping metadata</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Many Hibernate mapping elements accept the <literal>node</literal> attribute.
|
||||||
|
This let's you specify the name of an XML attribute or element that holds the
|
||||||
|
property or entity data. The format of the <literal>node</literal> attribute
|
||||||
|
must be one of the following:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"element-name"</literal> - map to the named XML element</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"@attribute-name"</literal> - map to the named XML attribute</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"."</literal> - map to the parent element</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>"element-name/@attribute-name"</literal> -
|
||||||
|
map to the named attribute of the named element
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For collections and single valued associations, there is an additional
|
||||||
|
<literal>embed-xml</literal> attribute. If <literal>embed-xml="true"</literal>,
|
||||||
|
the default, the XML tree for the associated entity (or collection of value type)
|
||||||
|
will be embedded directly in the XML tree for the entity that owns the association.
|
||||||
|
Otherwise, if <literal>embed-xml="false"</literal>, then only the referenced
|
||||||
|
identifier value will appear in the XML for single point associations and
|
||||||
|
collections will simply not appear at all.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
You should be careful not to leave <literal>embed-xml="true"</literal> for
|
||||||
|
too many associations, since XML does not deal well with circularity!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Customer"
|
||||||
|
table="CUSTOMER"
|
||||||
|
node="customer">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="CUST_ID"
|
||||||
|
node="@id"/>
|
||||||
|
|
||||||
|
<map name="accounts"
|
||||||
|
node="."
|
||||||
|
embed-xml="true">
|
||||||
|
<key column="CUSTOMER_ID"
|
||||||
|
not-null="true"/>
|
||||||
|
<map-key column="SHORT_DESC"
|
||||||
|
node="@short-desc"
|
||||||
|
type="string"/>
|
||||||
|
<one-to-many entity-name="Account"
|
||||||
|
embed-xml="false"
|
||||||
|
node="account"/>
|
||||||
|
</map>
|
||||||
|
|
||||||
|
<component name="name"
|
||||||
|
node="name">
|
||||||
|
<property name="firstName"
|
||||||
|
node="first-name"/>
|
||||||
|
<property name="initial"
|
||||||
|
node="initial"/>
|
||||||
|
<property name="lastName"
|
||||||
|
node="last-name"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
in this case, we have decided to embed the collection of account ids, but not
|
||||||
|
the actual account data. The following HQL query:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[from Customer c left join fetch c.accounts where c.lastName like :lastName]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Would return datasets such as this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<customer id="123456789">
|
||||||
|
<account short-desc="Savings">987632567</account>
|
||||||
|
<account short-desc="Credit Card">985612323</account>
|
||||||
|
<name>
|
||||||
|
<first-name>Gavin</first-name>
|
||||||
|
<initial>A</initial>
|
||||||
|
<last-name>King</last-name>
|
||||||
|
</name>
|
||||||
|
...
|
||||||
|
</customer>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If you set <literal>embed-xml="true"</literal> on the <literal><one-to-many></literal>
|
||||||
|
mapping, the data might look more like this:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<customer id="123456789">
|
||||||
|
<account id="987632567" short-desc="Savings">
|
||||||
|
<customer id="123456789"/>
|
||||||
|
<balance>100.29</balance>
|
||||||
|
</account>
|
||||||
|
<account id="985612323" short-desc="Credit Card">
|
||||||
|
<customer id="123456789"/>
|
||||||
|
<balance>-2370.34</balance>
|
||||||
|
</account>
|
||||||
|
<name>
|
||||||
|
<first-name>Gavin</first-name>
|
||||||
|
<initial>A</initial>
|
||||||
|
<last-name>King</last-name>
|
||||||
|
</name>
|
||||||
|
...
|
||||||
|
</customer>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
<sect1 id="xml-manipulation" revision="1">
|
||||||
|
<title>Manipulating XML data</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Let's rearead and update XML documents in the application. We do this by
|
||||||
|
obtaining a dom4j session:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Document doc = ....;
|
||||||
|
|
||||||
|
Session session = factory.openSession();
|
||||||
|
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
List results = dom4jSession
|
||||||
|
.createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
|
||||||
|
.list();
|
||||||
|
for ( int i=0; i<results.size(); i++ ) {
|
||||||
|
//add the customer data to the XML document
|
||||||
|
Element customer = (Element) results.get(i);
|
||||||
|
doc.add(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = factory.openSession();
|
||||||
|
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
Element cust = (Element) dom4jSession.get("Customer", customerId);
|
||||||
|
for ( int i=0; i<results.size(); i++ ) {
|
||||||
|
Element customer = (Element) results.get(i);
|
||||||
|
//change the customer name in the XML and database
|
||||||
|
Element name = customer.element("name");
|
||||||
|
name.element("first-name").setText(firstName);
|
||||||
|
name.element("initial").setText(initial);
|
||||||
|
name.element("last-name").setText(lastName);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
It is extremely useful to combine this feature with Hibernate's <literal>replicate()</literal>
|
||||||
|
operation to implement XML-based data import/export.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
A {
|
||||||
|
color: #003399;
|
||||||
|
}
|
||||||
|
|
||||||
|
A:active {
|
||||||
|
color: #003399;
|
||||||
|
}
|
||||||
|
|
||||||
|
A:visited {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
P, OL, UL, LI, DL, DT, DD, BLOCKQUOTE {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
TD, TH, SPAN {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCKQUOTE {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
H1, H2, H3, H4, H5, H6 {
|
||||||
|
color: #000000;
|
||||||
|
font-weight:500;
|
||||||
|
margin-top:10px;
|
||||||
|
padding-top:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
TABLE {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing:0;
|
||||||
|
border: 1px thin black;
|
||||||
|
empty-cells: hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
TD {
|
||||||
|
padding: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
H1 { font-size: 150%; }
|
||||||
|
H2 { font-size: 140%; }
|
||||||
|
H3 { font-size: 110%; font-weight: bold; }
|
||||||
|
H4 { font-size: 110%; font-weight: bold;}
|
||||||
|
H5 { font-size: 100%; font-style: italic; }
|
||||||
|
H6 { font-size: 100%; font-style: italic; }
|
||||||
|
|
||||||
|
TT {
|
||||||
|
font-size: 90%;
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
PRE {
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #CCCCCC;
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
UL, OL, LI {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
HR {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
border-width: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
color: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variablelist {
|
||||||
|
padding-top: 10;
|
||||||
|
padding-bottom:10;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemizedlist, UL {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.term {
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,429 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="354.331"
|
||||||
|
height="336.614"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,5.25349)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g659">
|
||||||
|
<rect
|
||||||
|
width="212.257"
|
||||||
|
height="57.2441"
|
||||||
|
x="17.9576"
|
||||||
|
y="100.132"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
transform="matrix(0.743454,0,0,0.482981,6.46949,52.2178)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
width="325.86"
|
||||||
|
height="63.6537"
|
||||||
|
x="17.4083"
|
||||||
|
y="15.194"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="325.86"
|
||||||
|
height="63.6537"
|
||||||
|
x="13.6713"
|
||||||
|
y="12.4966"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
<g
|
||||||
|
transform="matrix(1.14345,0,0,0.729078,-1.67818,105.325)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="170.824753"
|
||||||
|
y="58.402939"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="170.824997"
|
||||||
|
y="58.402901"
|
||||||
|
id="tspan360">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="178.076340"
|
||||||
|
y="364.281433"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="178.076004"
|
||||||
|
y="364.281006"
|
||||||
|
id="tspan421">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="68.605331"
|
||||||
|
y="138.524582"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="68.605301"
|
||||||
|
y="138.524994"
|
||||||
|
id="tspan384">
|
||||||
|
SessionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="67.0014"
|
||||||
|
height="101.35"
|
||||||
|
x="196.927"
|
||||||
|
y="89.2389"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect387" />
|
||||||
|
<rect
|
||||||
|
width="67.0014"
|
||||||
|
height="101.35"
|
||||||
|
x="194.633"
|
||||||
|
y="86.4389"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect388" />
|
||||||
|
<text
|
||||||
|
x="249.108841"
|
||||||
|
y="173.885559"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text389">
|
||||||
|
<tspan
|
||||||
|
x="249.108994"
|
||||||
|
y="173.886002"
|
||||||
|
id="tspan392">
|
||||||
|
Session</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="73.0355"
|
||||||
|
height="101.35"
|
||||||
|
x="270.995"
|
||||||
|
y="90.0018"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect395" />
|
||||||
|
<rect
|
||||||
|
width="73.0355"
|
||||||
|
height="101.35"
|
||||||
|
x="267.869"
|
||||||
|
y="87.2018"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect396" />
|
||||||
|
<text
|
||||||
|
x="328.593658"
|
||||||
|
y="174.715622"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text397">
|
||||||
|
<tspan
|
||||||
|
x="328.593994"
|
||||||
|
y="174.716003"
|
||||||
|
id="tspan563">
|
||||||
|
Transaction</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.29544,0,0,0.397877,9.70533,103.96)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g565">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect566" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect567" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="25.592752"
|
||||||
|
y="204.497803"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:10;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text568">
|
||||||
|
<tspan
|
||||||
|
x="25.592800"
|
||||||
|
y="204.498001"
|
||||||
|
id="tspan662">
|
||||||
|
TransactionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.298082,0,0,0.397877,99.6898,103.96)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g573">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect574" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect575" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="134.030670"
|
||||||
|
y="205.532791"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:10;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text576">
|
||||||
|
<tspan
|
||||||
|
x="134.031006"
|
||||||
|
y="205.533005"
|
||||||
|
id="tspan664">
|
||||||
|
ConnectionProvider</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.14345,0,0,0.729078,-1.67818,38.9539)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g587">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect588" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect589" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="25.6196"
|
||||||
|
y="206.028"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect594" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="24.4229"
|
||||||
|
y="204.135"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect595" />
|
||||||
|
<text
|
||||||
|
x="85.575645"
|
||||||
|
y="282.300354"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text596">
|
||||||
|
<tspan
|
||||||
|
x="85.575600"
|
||||||
|
y="282.299988"
|
||||||
|
id="tspan607">
|
||||||
|
JNDI</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="236.937"
|
||||||
|
y="206.791"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect610" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="235.741"
|
||||||
|
y="204.898"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect611" />
|
||||||
|
<text
|
||||||
|
x="342.093201"
|
||||||
|
y="283.226410"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text612">
|
||||||
|
<tspan
|
||||||
|
x="342.092987"
|
||||||
|
y="283.226013"
|
||||||
|
id="tspan621">
|
||||||
|
JTA</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="130.134"
|
||||||
|
y="206.791"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect616" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="128.937"
|
||||||
|
y="204.898"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect617" />
|
||||||
|
<text
|
||||||
|
x="212.445343"
|
||||||
|
y="283.226410"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text618">
|
||||||
|
<tspan
|
||||||
|
x="212.445007"
|
||||||
|
y="283.226013"
|
||||||
|
id="tspan623">
|
||||||
|
JDBC</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,6.19341)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g637">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.499515,0,0,0.415467,-0.237339,5.61339)"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="33.749969"
|
||||||
|
y="50.589706"
|
||||||
|
style="font-size:11;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="33.750000"
|
||||||
|
y="50.589699"
|
||||||
|
id="tspan635">
|
||||||
|
Transient Objects</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,5.25349)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g644">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.297486,0,0,0.516482,230.251,36.9178)"
|
||||||
|
id="g364">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect365" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect366" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="277.123230"
|
||||||
|
y="85.155571"
|
||||||
|
style="font-size:11;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text367">
|
||||||
|
<tspan
|
||||||
|
x="277.122986"
|
||||||
|
y="85.155602"
|
||||||
|
id="tspan631">
|
||||||
|
Persistent</tspan>
|
||||||
|
<tspan
|
||||||
|
x="277.122986"
|
||||||
|
y="96.155602"
|
||||||
|
id="tspan633">
|
||||||
|
Objects</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,334 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="318.898"
|
||||||
|
height="248.031"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
width="291.837"
|
||||||
|
height="57.0074"
|
||||||
|
x="17.3169"
|
||||||
|
y="18.646"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="291.837"
|
||||||
|
height="57.0074"
|
||||||
|
x="13.9703"
|
||||||
|
y="16.2302"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.326107,0,0,0.765831,9.59261,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g161">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.02406,0,0,0.652953,0.223384,39.9254)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.449834,0,0,0.338463,-3.15909,9.73319)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="302.277679"
|
||||||
|
y="65.943230"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="302.277954"
|
||||||
|
y="65.943184"
|
||||||
|
id="tspan360">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="36.235924"
|
||||||
|
y="63.796055"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:14;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="36.235950"
|
||||||
|
y="63.796051"
|
||||||
|
id="tspan427">
|
||||||
|
Transient Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="180.416245"
|
||||||
|
y="290.543701"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="180.415939"
|
||||||
|
y="290.543549"
|
||||||
|
id="tspan421">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="25.037701"
|
||||||
|
y="179.154755"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="25.037655"
|
||||||
|
y="179.154648"
|
||||||
|
id="tspan384">
|
||||||
|
SessionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.252763,0,0,0.765831,109.104,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g386">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect387" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect388" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.297394,0,0,0.572692,101.502,21.6359)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g364">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect365" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect366" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="202.746506"
|
||||||
|
y="102.992203"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:14;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text367">
|
||||||
|
<tspan
|
||||||
|
x="202.746948"
|
||||||
|
y="102.992249"
|
||||||
|
id="tspan423">
|
||||||
|
Persistent</tspan>
|
||||||
|
<tspan
|
||||||
|
x="202.746948"
|
||||||
|
y="116.992355"
|
||||||
|
id="tspan425">
|
||||||
|
Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="174.458496"
|
||||||
|
y="180.080795"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text389">
|
||||||
|
<tspan
|
||||||
|
x="174.458618"
|
||||||
|
y="180.080338"
|
||||||
|
id="tspan392">
|
||||||
|
Session</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,188.675,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g394">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect395" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect396" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="260.413269"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text397">
|
||||||
|
<tspan
|
||||||
|
x="260.412964"
|
||||||
|
y="179.154343"
|
||||||
|
id="tspan400">
|
||||||
|
JDBC</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,229.156,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g405">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect406" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect407" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="320.606903"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text408">
|
||||||
|
<tspan
|
||||||
|
x="320.606964"
|
||||||
|
y="179.154343"
|
||||||
|
id="tspan417">
|
||||||
|
JNDI</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,269.281,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g411">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect412" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect413" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="377.096313"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text414">
|
||||||
|
<tspan
|
||||||
|
x="377.096008"
|
||||||
|
y="179.154999"
|
||||||
|
id="tspan145">
|
||||||
|
JTA</tspan>
|
||||||
|
</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1,250 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="248.031"
|
||||||
|
height="248.031"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,-3.02123)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g158">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="17.3527"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="15.3883"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,3.04452)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g161">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,8.0993)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.543505,2.59104,21.1103)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="105.392174"
|
||||||
|
y="56.568123"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="105.392273"
|
||||||
|
y="56.568146"
|
||||||
|
id="tspan186">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="81.820183"
|
||||||
|
y="103.149330"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:20;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="81.820213"
|
||||||
|
y="103.149727"
|
||||||
|
id="tspan206">
|
||||||
|
Persistent Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="111.548180"
|
||||||
|
y="278.927887"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="111.547874"
|
||||||
|
y="278.927551"
|
||||||
|
id="tspan200">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="94.436180"
|
||||||
|
y="153.805740"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="94.436180"
|
||||||
|
y="153.805740"
|
||||||
|
id="tspan221">
|
||||||
|
HIBERNATE</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,2.59083,1.02261)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g254">
|
||||||
|
<g
|
||||||
|
transform="translate(4.58374,2.61928)"
|
||||||
|
id="g176">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.571429,0,0,0.67347,-10.6174,117.093)"
|
||||||
|
id="g170">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect171" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect172" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.571429,0,0,0.67347,138.682,117.093)"
|
||||||
|
id="g173">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect174" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect175" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="47.259438"
|
||||||
|
y="182.367538"
|
||||||
|
style="font-weight:bold;stroke-width:1pt;font-family:Courier;"
|
||||||
|
id="text191">
|
||||||
|
<tspan
|
||||||
|
x="47.259399"
|
||||||
|
y="182.367996"
|
||||||
|
id="tspan212">
|
||||||
|
hibernate.</tspan>
|
||||||
|
<tspan
|
||||||
|
x="47.259399"
|
||||||
|
y="194.367996"
|
||||||
|
id="tspan214">
|
||||||
|
properties</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="198.523010"
|
||||||
|
y="188.260941"
|
||||||
|
style="font-weight:normal;stroke-width:1pt;font-family:helvetica;"
|
||||||
|
id="text194">
|
||||||
|
<tspan
|
||||||
|
id="tspan195">
|
||||||
|
XML Mapping</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-manual</artifactId>
|
||||||
|
<version>3.3.0.beta1</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>hibernate-manual-${translation}</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>Hibernate Manual (${translation})</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<translation>es-ES</translation>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,203 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN"
|
||||||
|
"../support/docbook-dtd/docbookx.dtd"
|
||||||
|
[
|
||||||
|
<!ENTITY quickstart SYSTEM "modules/quickstart.xml">
|
||||||
|
<!ENTITY tutorial SYSTEM "modules/tutorial.xml">
|
||||||
|
<!ENTITY architecture SYSTEM "modules/architecture.xml">
|
||||||
|
<!ENTITY configuration SYSTEM "modules/configuration.xml">
|
||||||
|
<!ENTITY persistent-classes SYSTEM "modules/persistent_classes.xml">
|
||||||
|
<!ENTITY basic-mapping SYSTEM "modules/basic_mapping.xml">
|
||||||
|
<!ENTITY collection-mapping SYSTEM "modules/collection_mapping.xml">
|
||||||
|
<!ENTITY association-mapping SYSTEM "modules/association_mapping.xml">
|
||||||
|
<!ENTITY component-mapping SYSTEM "modules/component_mapping.xml">
|
||||||
|
<!ENTITY inheritance-mapping SYSTEM "modules/inheritance_mapping.xml">
|
||||||
|
<!ENTITY session-api SYSTEM "modules/session_api.xml">
|
||||||
|
<!ENTITY transactions SYSTEM "modules/transactions.xml">
|
||||||
|
<!ENTITY events SYSTEM "modules/events.xml">
|
||||||
|
<!ENTITY batch SYSTEM "modules/batch.xml">
|
||||||
|
<!ENTITY query-hql SYSTEM "modules/query_hql.xml">
|
||||||
|
<!ENTITY query-criteria SYSTEM "modules/query_criteria.xml">
|
||||||
|
<!ENTITY query-sql SYSTEM "modules/query_sql.xml">
|
||||||
|
<!ENTITY filters SYSTEM "modules/filters.xml">
|
||||||
|
<!ENTITY xml SYSTEM "modules/xml.xml">
|
||||||
|
<!ENTITY performance SYSTEM "modules/performance.xml">
|
||||||
|
<!ENTITY toolset-guide SYSTEM "modules/toolset_guide.xml">
|
||||||
|
<!ENTITY example-parentchild SYSTEM "modules/example_parentchild.xml">
|
||||||
|
<!ENTITY example-weblog SYSTEM "modules/example_weblog.xml">
|
||||||
|
<!ENTITY example-mappings SYSTEM "modules/example_mappings.xml">
|
||||||
|
<!ENTITY best-practices SYSTEM "modules/best_practices.xml">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<book lang="es">
|
||||||
|
|
||||||
|
<bookinfo>
|
||||||
|
<title>HIBERNATE - Persistencia Relacional para Java Idiomático</title>
|
||||||
|
<subtitle>Documentación de Referencia de Hibernate</subtitle>
|
||||||
|
<releaseinfo>3.0.5</releaseinfo>
|
||||||
|
</bookinfo>
|
||||||
|
|
||||||
|
<toc/>
|
||||||
|
|
||||||
|
<preface id="preface" revision="2">
|
||||||
|
<title>Prefacio</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Advertencia! Esta es una versión traducida del inglés de
|
||||||
|
la documentacién de referencia de Hibernate. La versión
|
||||||
|
traducida puede no estar actualizada! Sin embargo, las diferencias
|
||||||
|
deberían ser sólo menores. Consulta la documentación
|
||||||
|
de referencia en inglés si estás perdiendo información
|
||||||
|
o encuentras algún error de traducción. Si quieres colaborar con
|
||||||
|
una traducción en particular, contáctanos en la lista de correo
|
||||||
|
de desarrolladores de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Traductor(es): Bernardo Antonio Buffa Colomé <kreimer@bbs.frc.utn.edu.ar>
|
||||||
|
<!--,
|
||||||
|
Antonio López Gota <antoniogota@gmail.com> -->
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Trabajar con software orientado a objetos y una base de datos relacional puede ser
|
||||||
|
incómodo y consumir tiempo en los entornos de empresa de hoy. Hibernate es una
|
||||||
|
herramienta de mapeo objeto/relacional para entornos Java. El término mapeo
|
||||||
|
objeto/relacional (MOR) hace referencia a la técnica de mapear una
|
||||||
|
representación de datos desde un modelo de objetos a un modelo de datos relacional
|
||||||
|
con un esquema basado en SQL.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate no sólo se encarga de mapear de clases Java a tablas de base de datos
|
||||||
|
(y de tipos de datos de Java a tipos de datos SQL), sino que también provee
|
||||||
|
facilidades de consulta y recuperación de datos y puede reducir significativamente
|
||||||
|
el tiempo de desarrollo que de otra forma se gasta en el manejo de los datos en SQL y JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La meta de Hibernate es relevar al desarrollador del 95 por ciento de las tareas comunes
|
||||||
|
relacionadas a la programación de la persistencia de los datos.
|
||||||
|
Hibernate puede no ser la mejor solución para aplicaciones que usan solamente
|
||||||
|
procedimientos almacenados para implementar la lógica de negocio en la base de
|
||||||
|
datos, es mas útil con modelos de dominio orientados a objetos y lógica de
|
||||||
|
negocio en middle-tier basada en Java. Sin embargo, Hibernate ciertamente puede ayudarte
|
||||||
|
a quitar o encapsular código SQL específico de vendedor y ayudará
|
||||||
|
con la tarea común de traducción de resultados desde una representación
|
||||||
|
tabular a un grafo de objetos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si eres nuevo en Hibernate y lo del Mapeo Objeto/Relacional o incluso en Java,
|
||||||
|
sigue por favor estos pasos:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Lee <xref linkend="quickstart"/> para un tutorial de 30 minutos, usando Tomcat.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Lee <xref linkend="architecture"/> para entender los entornos en los que
|
||||||
|
puede ser usado Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Dale una mirada al directorio <literal>eg/</literal> en la distribución
|
||||||
|
de Hibernate, contiene una aplicación independiente simple.
|
||||||
|
Copia tu driver JDBC al directorio <literal>lib/</literal> y edita
|
||||||
|
<literal>etc/hibernate.properties</literal>, especificando los valores
|
||||||
|
correctos para tu base de datos. Desde línea de comandos en el
|
||||||
|
directorio de la distribución, tipea <literal>ant eg</literal>
|
||||||
|
(usando Ant), o bajo Windows, tipea <literal>build eg</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Usa esta documentación de referencia como tu fuente de información
|
||||||
|
primaria. Ten en consideración leer <emphasis>Hibernate in Action</emphasis>
|
||||||
|
(http://www.manning.com/bauer) si necesitas mas ayuda con el diseño
|
||||||
|
de aplicaciones o si prefieres un tutorial paso a paso.
|
||||||
|
Visita también http://caveatemptor.hibernate.org y descarga la aplicación
|
||||||
|
de ejemplo para Hibernate in Action.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Los FAQs son respondidos en el sitio web de Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
En el sitio web de Hibernate hay enlaces a demos de terceros, ejemplos
|
||||||
|
y tutoriales.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
El Area de Comunidad en el sitio web de Hibernate es una buena fuente
|
||||||
|
de patrones de diseño y varias soluciones de integración
|
||||||
|
(Tomcat, JBoss, Struts, EJB, etc.).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si tienes preguntas, usa el foro de usuarios enlazado en el sitio web de Hibernate.
|
||||||
|
También proveemos un sistema de seguimiento JIRA para reportes de defectos y
|
||||||
|
peticiones de nuevas características.
|
||||||
|
Si estas interesado en el desarrollo de Hibernate, únete a la lista de correo
|
||||||
|
de desarrolladores. Si estas interesado en traducir esta documentación a tu
|
||||||
|
lenguaje, contáctanos en la lista de correo de desarrolladores.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A través de JBoss Inc. (see http://www.hibernate.org/SupportTraining/) hay
|
||||||
|
disponibilidad de soporte comercial de desarrollo, soporte de producción y
|
||||||
|
entrenamiento en Hibernate.
|
||||||
|
Hibernate es un proyecto de la suite de productos de código abierto
|
||||||
|
JBoss Professional.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</preface>
|
||||||
|
|
||||||
|
&quickstart;
|
||||||
|
&tutorial;
|
||||||
|
&architecture;
|
||||||
|
|
||||||
|
&configuration;
|
||||||
|
|
||||||
|
&persistent-classes;
|
||||||
|
|
||||||
|
&basic-mapping;
|
||||||
|
&collection-mapping;
|
||||||
|
&association-mapping;
|
||||||
|
&component-mapping;
|
||||||
|
&inheritance-mapping;
|
||||||
|
|
||||||
|
&session-api;
|
||||||
|
&transactions;
|
||||||
|
&events;
|
||||||
|
&batch;
|
||||||
|
|
||||||
|
&query-hql;
|
||||||
|
&query-criteria;
|
||||||
|
&query-sql;
|
||||||
|
&filters;
|
||||||
|
&xml;
|
||||||
|
|
||||||
|
&performance;
|
||||||
|
|
||||||
|
&toolset-guide;
|
||||||
|
|
||||||
|
&example-parentchild;
|
||||||
|
&example-weblog;
|
||||||
|
&example-mappings;
|
||||||
|
|
||||||
|
&best-practices;
|
||||||
|
|
||||||
|
</book>
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
<chapter id="architecture">
|
||||||
|
|
||||||
|
<title>Arquitectura</title>
|
||||||
|
|
||||||
|
<sect1 id="architecture-overview" revision="1">
|
||||||
|
<title>Visión General</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una visión a (muy) alto nivel de la arquitectura de Hibernate:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/overview.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/overview.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este diagrama muestra a Hibernate usando la base de datos y los datos de
|
||||||
|
configuración para proveer servicios de persistencia (y objetos
|
||||||
|
persistentes) a la aplicación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nos gustaría mostrar una vista más detallada de la arquitectura de tiempo
|
||||||
|
de ejecución. Desafortunadamente, Hibernate es flexible y soporta diferentes
|
||||||
|
enfoques. Mostraremos los dos extremos. En la arquitectura "sencilla", es la
|
||||||
|
aplicación la que provee su propias conexiones JDBC y gestiona sus propias
|
||||||
|
transacciones. Este enfoque usa un mínimo subconjunto de la API de Hibernate:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/lite.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/lite.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La arquitectura "full cream" abstrae a la aplicación de las APIs
|
||||||
|
de JDBC/JTA y deja que Hibernate se encargue de los detalles.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/full_cream.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/full_cream.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
He aquí algunas definiciones de los objetos en los diagramas:
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>SessionFactory (<literal>org.hibernate.SessionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Caché threadsafe (inmutable) de mapeos compilados para
|
||||||
|
una sola base de datos. Es una fábrica de <literal>Session</literal>
|
||||||
|
y un cliente de <literal>ConnectionProvider</literal>. Opcionalmente,
|
||||||
|
puede mantener una caché (de segundo nivel) de datos reusables
|
||||||
|
entre transacciones, a un nivel de proceso o de cluster.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Session (<literal>org.hibernate.Session</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Objeto mono-hebra, de corta vida que representa una conversación
|
||||||
|
entre la aplicación y el almacenamiento persistente. Envuelve una
|
||||||
|
conexión JDBC. Es una fábrica de <literal>Transaction</literal>.
|
||||||
|
Mantiene una caché requerida (de primer nivel) de objetos persistentes,
|
||||||
|
usada mientras se navega el grafo de objetos o se recuperen objetos por
|
||||||
|
identificador.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Objetos y colecciones persistentes</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Objetos de corta vida, mono-hebra conteniendo estado persistente y
|
||||||
|
funciónalidad de negocio. Estos pueden ser JavaBeans/POJOs
|
||||||
|
(Plain Old Java Objects, o sea, cualquier objeto Java), la única
|
||||||
|
cosa especial en ellos es que estan asociados actualmente con una
|
||||||
|
(y sólo una) <literal>Session</literal>. Tan pronto como la
|
||||||
|
<literal>Session</literal> sea cerrada, serán separados y
|
||||||
|
estarán libres para ser usados en cualquier capa de aplicación.
|
||||||
|
(por ejemplo, directamente como objetos de transferencia de datos hacia
|
||||||
|
y desde la capa de presentación).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Objetos y colecciones transitorios y separados</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Instancias de clases persistentes que no estan acutualmente asociadas
|
||||||
|
con una <literal>Session</literal>. Pueden haber sido instanciadas por
|
||||||
|
la aplicación y (aún) no haber sido hechas persistentes,
|
||||||
|
o pueden haber sido instanciadas por una <literal>Session</literal> cerrada.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Transaction (<literal>org.hibernate.Transaction</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Opcional) Un objeto de corta vida, mono-hebra, usado por la aplicación
|
||||||
|
para especificar unidades atómicas de trabajo. Abstrae a la aplicación
|
||||||
|
de las subyacentes transacciones JDBC, JTA o CORBA. En algunos casos, una
|
||||||
|
<literal>Session</literal> puede extenderse sobre varias <literal>Transaction</literal>s.
|
||||||
|
Sin embargo, la demarcación de la transacción, ya sea usando la API
|
||||||
|
subyacente o <literal>Transaction</literal>, nunca es opcional!
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>ConnectionProvider (<literal>org.hibernate.connection.ConnectionProvider</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Opcional) Una fábrica (y pool) de conexiones JDBC. Abstrae a la aplicación
|
||||||
|
del <literal>Datasource</literal> o <literal>DriverManager</literal> subyacente.
|
||||||
|
No se expone a la aplicación, pero puede ser extendido/implementado por
|
||||||
|
el desarrollador.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>TransactionFactory (<literal>org.hibernate.TransactionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Opcional) Una fábrica de instancias de <literal>Transaction</literal>.
|
||||||
|
No se expone a la aplicación, pero puede ser extendido/implementado por
|
||||||
|
el desarrollador.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><emphasis>Interfaces de Extensión</emphasis></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate ofrece muchas interfaces de extensión opcional que puedes
|
||||||
|
implementar para modificar a medida el comportamiento de tu capa de persistencia.
|
||||||
|
Para más detalles, mira la documentación de la API.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dada una arquitectura "sencilla", la aplicación pasa por alto las APIs
|
||||||
|
de <literal>Transaction</literal>/<literal>TransactionFactory</literal> y/o
|
||||||
|
<literal>ConnectionProvider</literal>, para hablar directamente a JTA o JDBC.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-states" revision="1">
|
||||||
|
<title>Estados de instancia</title>
|
||||||
|
<para>
|
||||||
|
Una instancia de una clase persistente puede estar en uno de tres estados
|
||||||
|
diferentes, definidos respecto de su <emphasis>contexto de persistencia</emphasis>.
|
||||||
|
El objeto <literal>Session</literal> de Hibernate es el contexto de persistencia:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>transitorio</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La instancia no está y nunca estuvo asociada con
|
||||||
|
un contexto de persistencia. No tiene identidad persistente
|
||||||
|
(valor de clave primaria).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>persistente</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La instancia está actualmente asociada con un
|
||||||
|
contexto de persistencia. Tiene una identidad persistente
|
||||||
|
(valor de clave primaria) y, quizás, una fila
|
||||||
|
correspondiente en la base de datos. Para un contexto de
|
||||||
|
persistencia en particular, Hibernate <emphasis>garantiza</emphasis>
|
||||||
|
que la identidad persistente es equivalente a la identidad
|
||||||
|
Java (localización en memoria del objeto).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>separado</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La instancia estuvo una vez asociada con un contexto
|
||||||
|
de persistencia, pero ese contexto fue cerrado, o la
|
||||||
|
instancia fue serializada a otro proceso. Tiene una
|
||||||
|
identidad persistente y, quizás, una fila correspondiente
|
||||||
|
en la base de datos. Para las instancias separadas,
|
||||||
|
Hibernate no establece ninguna garantía sobre
|
||||||
|
la relación entre identidad persistente e identidad Java.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jmx" revision="1">
|
||||||
|
<title>Integración JMX</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
JMX es el estándar J2EE para la gestión de componentes Java. Hibernate
|
||||||
|
puede ser gestionado por medio de un servicio estándar JMX.
|
||||||
|
Proveemos una implementación de MBean en la distribución,
|
||||||
|
<literal>org.hibernate.jmx.HibernateService</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para ejemplo de cómo desplegar Hibernate como un servicio JMX en un Servidor
|
||||||
|
de Aplicaciones JBoss, por favor, mira la Guía del Usuario de JBoss.
|
||||||
|
En JBoss AS, tienes además estos beneficios si despliegas usando JMX:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Gestión de Sesión:</emphasis> El ciclo de vida de la <literal>Session</literal>
|
||||||
|
de Hibernate puede estar automáticamente ligado al ámbito de una transacción
|
||||||
|
JTA. Esto significa que ya no tienes que abrir ni cerrar la <literal>Session</literal> manualmente,
|
||||||
|
esto pasa a ser trabajo de un interceptor EJB de JBoss. Además tampoco tienes
|
||||||
|
que preocuparte más de la demarcación de la transacción (a menos que
|
||||||
|
que quieras escribir una capa de persitencia portable, por supuesto, usa la API de
|
||||||
|
<literal>Transaction</literal> de Hibernate para esto). Para acceder a una
|
||||||
|
<literal>Session</literal> llama al <literal>HibernateContext</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Despliegue de HAR:</emphasis> Usualmente despliegas el servicio JMX de Hibernate
|
||||||
|
usando un descriptor de despliegue de servicio de JBoss (en un fichero EAR y/o SAR), que soporta
|
||||||
|
todas las opciones de configuración usuales de una <literal>SessionFactory</literal> de
|
||||||
|
Hibernate. Sin embargo, todavía tienes que nombrar todos tus ficheros de mapeo en el
|
||||||
|
descriptor de despliegue. Si decides usar el depliegue de HAR opcional, JBoss detectará
|
||||||
|
automáticamente todos los ficheros de mapeo en tu fichero HAR.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para más información sobre estas opciones, consulta la
|
||||||
|
Guía de Usuario del JBoss AS.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Otra funcionalidad disponible como un servicio JMX son las estadísticas en
|
||||||
|
tiempo de ejecución de Hibernate. Mira <xref linkend="configuration-optional-statistics"/>.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jca" revision="1">
|
||||||
|
<title>Soporte JCA:</title>
|
||||||
|
<para>
|
||||||
|
Hiberate puede además ser configurado como un conector JCA. Por favor mira el
|
||||||
|
sitio web para más detalles. Por favor ten en cuenta que el soporte de JCA
|
||||||
|
de Hibernate está aún considerado experimental.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,527 @@
|
||||||
|
<chapter id="associations">
|
||||||
|
|
||||||
|
<title>Mapeos de Asociación</title>
|
||||||
|
|
||||||
|
<sect1 id="assoc-intro" revision="1">
|
||||||
|
<title>Introducción</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los mapeos de asociación son frecuentemente las cosas mas difíciles
|
||||||
|
de hacer correctamente. En esta sección iremos a través de los casos
|
||||||
|
canónicos uno a uno, comenzando con los mapeos unidireccionales, y considerando
|
||||||
|
luego los casos bidireccionales. Usaremos <literal>Person</literal> y <literal>Address</literal>
|
||||||
|
en todos los ejemplos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Clasificaremos las asociaciones por cuanto mapeen o no a una tabla
|
||||||
|
de unión interviniente, y por su multiplicidad.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las claves foráneas que aceptan valores nulos (en adelante, nullables)
|
||||||
|
no son consideradas una buena práctica en el modelado tradicional de datos,
|
||||||
|
así que todos nuestros ejemplos usan claves foráneas no nullables.
|
||||||
|
Esto no es un requerimiento de Hibernate, y todos los mapeos funcionarán
|
||||||
|
si quitas las restricciones de nulabilidad.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional" revision="1">
|
||||||
|
<title>Asociaciones Unidireccionales</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-m21">
|
||||||
|
<title>muchos a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional muchos-a-uno</emphasis> es el tipo
|
||||||
|
más común de asociaciones unidireccionales.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-121">
|
||||||
|
<title>uno a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional uno-a-uno en una clave primaria</emphasis>
|
||||||
|
es casi idéntica. La única diferencia es la restricción de unicidad
|
||||||
|
de la columna.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Usualmente, una <emphasis>asociación unidireccional uno-a-uno en una
|
||||||
|
clave primaria</emphasis> usa un generador de id especial. (Observa que hemos
|
||||||
|
invertido el sentido de la asociación en este ejemplo).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-12m">
|
||||||
|
<title>uno a muchos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional uno-a-muchos en una clave foránea</emphasis>
|
||||||
|
es un caso muy inusual, y realmente no está recomendada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses">
|
||||||
|
<key column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
<one-to-many class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key, personId bigint not null )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Creemos que es mejor usar una tabla de unión para este tipo de asociación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional-join" revision="1">
|
||||||
|
<title>Asociaciones unidireccionales con tablas de unión</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-12m">
|
||||||
|
<title>uno a muchos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional uno-a-muchos en una tabla de unión</emphasis>
|
||||||
|
es más preferible. Observa que especificando <literal>unique="true"</literal>, hemos
|
||||||
|
cambiado la multiplicidad de muchos-a-muchos a uno-a-muchos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m21">
|
||||||
|
<title>muchos a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional muchos-a-uno en una tabla de unión</emphasis>
|
||||||
|
es bastante común cuando la asociación es opcional.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId" unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-121">
|
||||||
|
<title>uno a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación unidireccional uno-a-uno en una tabla de unión</emphasis>
|
||||||
|
es inusual en extremo, pero posible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m2m">
|
||||||
|
<title>muchos a muchos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finalmente, tenemos una <emphasis>asociación unidireccional muchos-a-muchos</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional" revision="1">
|
||||||
|
<title>Asociaciones Bidireccionales</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-m21">
|
||||||
|
<title>uno a muchos / muchos a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación bidireccional muchos-a-uno</emphasis> es
|
||||||
|
el tipo más común de asociación. (Esta es la relación
|
||||||
|
estándar padre/hijo.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<one-to-many class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-121">
|
||||||
|
<title>uno a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación bidireccional uno-a-uno en una clave foránea</emphasis>
|
||||||
|
es bastante común.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
property-ref="address"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación bidireccional uno-a-uno en una clave primaria</emphasis>
|
||||||
|
usa el generador de id especial.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="address"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional-join" revision="1">
|
||||||
|
<title>Asociaciones bidireccionales con tablas de unión</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-12m">
|
||||||
|
<title>uno a muchos / muchos a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación bidireccional uno-a-muchos en una tabla de unión</emphasis>.
|
||||||
|
Observa que el <literal>inverse="true"</literal> puede ir a cualquier lado de la asociación,
|
||||||
|
en la colección, o en la unión.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses"
|
||||||
|
table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
inverse="true"
|
||||||
|
optional="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-one name="person"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-121">
|
||||||
|
<title>uno a uno</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <emphasis>asociación bidireccional uno-a-uno en una tabla de unión</emphasis>
|
||||||
|
es inusual en extremo, pero posible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true"
|
||||||
|
inverse="true">
|
||||||
|
<key column="addressId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-m2m">
|
||||||
|
<title>muchos a muchos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finalmente, tenemos una <emphasis>asociación bidireccional muchos-a-muchos</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-many column="personId"
|
||||||
|
class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
<chapter id="batch">
|
||||||
|
<title>Procesamiento por lotes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un enfoque ingenuo para insertar 100.000 filas en la base de datos usando Hibernate podría verse así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
}
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto podría caer sobre una <literal>OutOfMemoryException</literal> en algún sitio
|
||||||
|
cerca de la fila 50.000. Esto es porque Hibernate tiene en caché todas las instancias
|
||||||
|
de <literal>Customer</literal> recién instanciadas en el caché de nivel de sesión.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En este capítulo te mostraremos cómo evitar este problema. Primero, sin embargo,
|
||||||
|
si estás haciendo procesamiento por lotes (batch processing), es absolutamente crítico
|
||||||
|
que habilites el uso de loteo JDBC, si pretendes lograr un rendimiento razonable.
|
||||||
|
Establece el tamaño de lote JDBC a un número razonable (digamos 10-50):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Podrías además querer hacer este tipo de trabajo en un proceso donde la interacción con el caché de
|
||||||
|
segundo nivel esté completamente deshabilitado:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
|
||||||
|
|
||||||
|
<sect1 id="batch-inserts">
|
||||||
|
<title>Inserciones en lote</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Al hacer persistentes objetos nuevos, debes limpiar con <literal>flush()</literal> y
|
||||||
|
llamar a <literal>clear()</literal> en la sesión regularmente, para controlar el tamaño
|
||||||
|
del caché de primer nivel.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
|
||||||
|
//flush a batch of inserts and release memory:
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-update" >
|
||||||
|
<title>Actualizaciones en lote</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para recuperar y actualizar datos se aplican las mismas ideas. Adicionalmente, necesitas usar
|
||||||
|
<literal>scroll()</literal> para sacar ventaja de los cursores del lado del servidor en consultas
|
||||||
|
que devuelvan muchas filas de datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||||
|
.setCacheMode(CacheMode.IGNORE)
|
||||||
|
.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
int count=0;
|
||||||
|
while ( customers.next() ) {
|
||||||
|
Customer customer = (Customer) customers.get(0);
|
||||||
|
customer.updateStuff(...);
|
||||||
|
if ( ++count % 20 == 0 ) {
|
||||||
|
//flush a batch of updates and release memory:
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-direct">
|
||||||
|
<title>update/delete en masa</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Como ya se ha discutido, el mapeo objeto/relacional automático y transparente se refiere
|
||||||
|
al manejo de estado de objetos. Esto implica que el estado del objeto está disponible
|
||||||
|
en memoria, por lo tanto actualizar o borrar (usando <literal>UPDATE</literal> y
|
||||||
|
<literal>DELETE</literal> de SQL) datos directamente en la base de datos no afectará el
|
||||||
|
estado en memoria. Sin embargo, Hibernate provee métodos para la ejecución de sentencias
|
||||||
|
del estilo de <literal>UPDATE</literal> y <literal>DELETE</literal> de SQL que se realizan
|
||||||
|
a través del Lenguaje de Consulta de Hibernate (Hibernate Query Language o
|
||||||
|
<xref linkend="queryhql">HQL</xref>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La pseudo-sintáxis para sentencias <literal>UPDATE</literal> y <literal>DELETE</literal> es:
|
||||||
|
<literal>( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?</literal>. Algunos puntos
|
||||||
|
a tener en cuenta:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
En la cláusula-from, la palabra clave FROM es opcional
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Puede haber sólo una clase mencionada en la cláusula-from, y <emphasis>no puede</emphasis>
|
||||||
|
tener un alias.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
No puede especificarse ningún join (bien implícito o explícito) en una consulta masiva de HQL.
|
||||||
|
Pueden usarse subconsultas en la cláusula-where.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La cláusula-where es también opcional.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Como un ejemplo, para ejecutar un <literal>UPDATE</literal> HQL, usa el
|
||||||
|
método <literal>Query.executeUpdate()</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlUpdate = "update Customer set name = :newName where name = :oldName";
|
||||||
|
int updatedEntities = s.createQuery( hqlUpdate )
|
||||||
|
.setString( "newName", newName )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para ejecutar un <literal>DELETE</literal> HQL, usa el mismo método <literal>Query.executeUpdate()</literal>
|
||||||
|
(el método está nombrado para aquellos familiarizados con
|
||||||
|
<literal>PreparedStatement.executeUpdate()</literal> de JDBC):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlDelete = "delete Customer where name = :oldName";
|
||||||
|
int deletedEntities = s.createQuery( hqlDelete )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El valor <literal>int</literal> devuelto por el método <literal>Query.executeUpdate()</literal>
|
||||||
|
indica el número de entidades afectadas por la operación. Considera que esto puede o no
|
||||||
|
correlacionarse al número de filas afectadas en la base de datos. Una operación masiva HQL podría
|
||||||
|
resultar en que se ejecuten múltiples sentencias de SQL reales, para joined-subclass, por ejemplo.
|
||||||
|
El número devuelto indica el número de entidades reales afectadas por la sentencia. Volviendo al
|
||||||
|
ejemplo de joined-subclass, un borrado contra una de las subclases puede resultar realmente en
|
||||||
|
borrados contra no sólo la tabla a la que está mapeada esa subclase, sino también la tabla "raíz"
|
||||||
|
y potencialmente tablas de joined-subclass más debajo en la jerarquía de herencia.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ten en cuenta que existen actualmente unas pocas limitaciones con las operaciones HQL masivas,
|
||||||
|
que serán atendidas en lanzamientos futuros; consulta la hoja de ruta de JIRA para más detalles.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,229 @@
|
||||||
|
<chapter id="best-practices" revision="3">
|
||||||
|
<title>Mejores Prácticas</title>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>Escribe clase finamente granularizadas y mapealas usando <literal><component></literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Usa una clase <literal>Dirección</literal> para encapsular <literal>calle</literal>,
|
||||||
|
<literal>distrito</literal>, <literal>estado</literal>, <literal>código postal</literal>.
|
||||||
|
Esto alienta la reutilización de código y simplifica el refactoring.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Declara las propiedades identificadoras en clases persistentes.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate hace opcionales las propiedades identificadoras. Existen todo tipo de razones
|
||||||
|
por las que debes usarlas. Recomendamos que los identificadores sean 'sintéticos'
|
||||||
|
(generados, sin ningún significado de negocio).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identifica las claves naturales.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Identifica las claves naturales de todas las entidades, y mapealas usando
|
||||||
|
<literal><natural-id></literal>. Implementa <literal>equals()</literal> y
|
||||||
|
<literal>hashCode()</literal> para comparar las propiedades que componen la clave natural.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Coloca cada mapeo de clase en su propio fichero.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
No uses un solo documento monolítico de mapeo. Mapea <literal>com.eg.Foo</literal> en
|
||||||
|
el fichero <literal>com/eg/Foo.hbm.xml</literal>. Esto tiene sentido particularmente en un
|
||||||
|
ambiente de equipo.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Carga los mapeos como recursos.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Despliega los mapeos junto a las clases que mapean.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Considera externalizar las cadenas de consulta.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Esta es una buena práctica si tus consultas llaman a funciones SQL que no son del
|
||||||
|
estándar ANSI. Externalizar las cadenas de consulta a ficheros de mapeo hará la
|
||||||
|
aplicación más portable.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Usa variables de ligado.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Igual que en JDBC, siempre remplaza valores no constantes con "?". ¡Nunca uses manipulación
|
||||||
|
de cadenas para ligar un valor no constante en una consulta! Incluso mejor, considera usar
|
||||||
|
parámetros con nombre en las consultas.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>No manejes tus propias conexiones JDBC.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate deja a la aplicación administre las conexiones JDBC. Este enfoque debe considerarse
|
||||||
|
como último recurso. Si no puedes usar los provedores de conexión prefabricados, considera
|
||||||
|
prover tu propia implementación de <literal>org.hibernate.connection.ConnectionProvider</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Considera usar un tipo personalizado.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Supón que tienes un tipo Java, digamos de alguna biblioteca, que necesita hacerse persistente
|
||||||
|
pero no provee los métodos de acceso necesarios para mapearlo como un componente. Debes considerar
|
||||||
|
implementar <literal>org.hibernate.UserType</literal>. Este enfoque libera al código de aplicación
|
||||||
|
de implementar transformaciones a / desde un tipo Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Usa JDBC codificado a mano en cuellos de botella.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
En áreas del sistema de rendimiento crítico, algunos tipos de operaciones podrían beneficiarse
|
||||||
|
del JDBC directo. Pero por favor, espero hasta que <emphasis>sepas</emphasis> que algo es
|
||||||
|
un cuello de botella. Y no asumas que el JDBC directo es necesariamente más rápido. Si necesitas
|
||||||
|
usar JDBC directo, podría ser valioso abrir una <literal>Session</literal> de Hibernate y usar esa
|
||||||
|
conexión JDBC. De esta forma puedes usar aún la misma estrategia de transacción y el mismo
|
||||||
|
proveedor de conexiones subyacente.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Comprende la limpieza (flushing) de <literal>Session</literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
De vez en cuando la sesión sincroniza su estado persistente con la base de datos. El rendimiento
|
||||||
|
se verá afectado si este proceso ocurre demasiado frecuentemente. A veces puedes minimizar
|
||||||
|
limpieza innecesaria deshabilitando la limpieza automática o incluso cambiando el orden de las
|
||||||
|
consultas u otras operaciones en una transacción en particular.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>En una aplicación en tres gradas, considera usar objetos separados.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Al usar una arquitectura de servlet / sesión, puedes pasar objetos persistentes en el bean de
|
||||||
|
sesión hacia y desde la capa de servlet / JSP. Usa una sesión nueva para atender el servicio de cada
|
||||||
|
petición. Usa <literal>Session.merge()</literal> o <literal>Session.saveOrUpdate()</literal> para
|
||||||
|
sincronizar los objetos con la base de datos.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>En una arquitectura en dos gradas, considera usar contexto de persistencia largos.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Las transacciones de base de datos tienen que ser tan cortas como sea posible. Sin embargo,
|
||||||
|
frecuentemente es necesario implementar <emphasis>transacciones de aplicación</emphasis>
|
||||||
|
ejecutándose en largo, una sola unidad de trabajo desde el punto de vista de un usuario.
|
||||||
|
Una transacción de aplicación puede abarcar muchos ciclos petición/respuesta del cliente.
|
||||||
|
Es común usar objetos separados para implementar transacciones de aplicación. Una alternativa,
|
||||||
|
extremadamente apropiada en arquitecturas en dos gradas, es mantener un solo contacto de persistencia
|
||||||
|
abierto (sesión) para todo el ciclo de vida de la transacción de aplicación y simplemente
|
||||||
|
desconectar de la conexión JDBC al final de cada petición, y reconectar al comienzo de la
|
||||||
|
petición subsecuente. Nunca compartas una única sesión a través de más de una transacción
|
||||||
|
de aplicación, o estarás trabajando con datos añejos.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>No trates la excepciones como recuperables.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Esto es más una práctica necesaria que una "mejor" práctica. Cuando ocurra una excepción,
|
||||||
|
deshaz (rollback) la <literal>Transaction</literal> y cierra la <literal>Session</literal>.
|
||||||
|
Si no lo haces, Hibernate no puede garantizar que el estado en memoria representa con exactitud
|
||||||
|
el estado persistente. Como un caso especial de esto, no uses <literal>Session.load()</literal>
|
||||||
|
para determinar si una instancia con el identificador dado existe en la base de datos. En cambio,
|
||||||
|
usa <literal>Session.get()</literal> o una consulta.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Prefiere la recuperación perezosa para las asociaciones.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Usa escasamente la recuperación temprana. Usa proxies y colecciones perezosas para la mayoría
|
||||||
|
de asociaciones a clases probablemente no estén mantenidas en el caché de segundo nivel. Para
|
||||||
|
las asociaciones a clases en caché, donde hay una probabilidad de acceso a caché extremadamente
|
||||||
|
alta, deshabilita explícitamente la recuperación temprana usando <literal>lazy="false"</literal>.
|
||||||
|
Cuando sea apropiada la recuperación por unión (join fetching) para un caso de uso en particular,
|
||||||
|
usa una consulta con un <literal>left join fetch</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Usa el patrón <emphasis>sesión abierta en vista</emphasis>, o una <emphasis>fase de ensamblado</emphasis>
|
||||||
|
disciplinada para evitar problemas con datos no recuperados.
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate liberal al desarrollador de escribir <emphasis>Objetos de Transferencia de Datos
|
||||||
|
(Data Transfer Objects)</emphasis> (DTO). En una arquitectura tradicional de EJB, los DTOs tienen
|
||||||
|
un propósito doble: primero, atacan el problema que los beans de entidad no son serializables.
|
||||||
|
Segundo, definen implícitamente una fase de ensamblado cuando se recuperan y se forman (marshalling)
|
||||||
|
todos los datos a usar por la vista en los DTOs antes de devolver el control a la grada de
|
||||||
|
presentación. Hibernate elimina el primer propósito. Sin embargo, aún necesitas una fase
|
||||||
|
de ensamblado (piensa en tus métodos de negocio como si tuviesen un contrato estricto con la grada
|
||||||
|
de presentación sobre qué datos están disponibles en los objetos separados) a menos que estés
|
||||||
|
preparado para tener el contexto de persistencia (la sesión) abierto a través del proceso
|
||||||
|
de renderización de la vista. ¡Esta no es una limitación de Hibernate! Es un requerimiento
|
||||||
|
fundamental de acceso seguro a datos transaccionales.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Considera abstraer tu lógica de negocio de Hibernate</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Oculta el código de acceso a datos (Hibernate) detrás de una interface. Combina los patrones
|
||||||
|
<emphasis>DAO</emphasis> y <emphasis>Sesión de Hebra Local</emphasis>. Incluso puedes tener
|
||||||
|
algunas clases hechas persistentes por JDBC escrito a mano, asociadas a Hibernate por medio
|
||||||
|
de un <literal>UserType</literal>. (Este consejo está pensado para aplicaciones "suficientemente
|
||||||
|
grandes"; ¡no es apropiado para una aplicación con cinco tablas!)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>No uses mapeos de asociación exóticos.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Son raros los casos de uso de asociaciones reales muchos-a-muchos. La mayor parte del tiempo
|
||||||
|
necesitas información adicional almacenada en una "tabla de enlace". En este caso, es mucho
|
||||||
|
mejor usar dos asociaciones uno-a-muchos a una clase de enlace intermedia. De hecho, pensamos
|
||||||
|
que la mayoría de asociaciones son uno-a-muchos y muchos-a-uno, debes ser cuidadoso al usr
|
||||||
|
cualquier otro estilo de asociación y preguntarte si es realmente necesario.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Prefiere las asociaciones bidireccionales.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Las asociaciones unidireccionales son más difíciles de consultar. En una aplicación grande,
|
||||||
|
casi todas las asociaciones deben ser navegables en ambas direcciones en consultas.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,403 @@
|
||||||
|
<chapter id="components">
|
||||||
|
<title>Mapeo de Componentes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La noción de un <emphasis>componente</emphasis> es reusada en muchos contextos diferentes,
|
||||||
|
para propósitos diferentes, a través de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="components-dependentobjects">
|
||||||
|
<title>Objetos dependientes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un componente es un objeto contenido que es persistido como un tipo de valor, no una
|
||||||
|
referencia de entidad. El término "componente" hace referencia a la noción orientada a
|
||||||
|
objetos de composición (no a componentes a nivel de arquitectura). Por ejemplo, podrías
|
||||||
|
modelar una persona como:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Person {
|
||||||
|
private java.util.Date birthday;
|
||||||
|
private Name name;
|
||||||
|
private String key;
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
private void setKey(String key) {
|
||||||
|
this.key=key;
|
||||||
|
}
|
||||||
|
public java.util.Date getBirthday() {
|
||||||
|
return birthday;
|
||||||
|
}
|
||||||
|
public void setBirthday(java.util.Date birthday) {
|
||||||
|
this.birthday = birthday;
|
||||||
|
}
|
||||||
|
public Name getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public void setName(Name name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
......
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Name {
|
||||||
|
char initial;
|
||||||
|
String first;
|
||||||
|
String last;
|
||||||
|
public String getFirst() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
void setFirst(String first) {
|
||||||
|
this.first = first;
|
||||||
|
}
|
||||||
|
public String getLast() {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
void setLast(String last) {
|
||||||
|
this.last = last;
|
||||||
|
}
|
||||||
|
public char getInitial() {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
void setInitial(char initial) {
|
||||||
|
this.initial = initial;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora <literal>Name</literal> puede ser persistido como un componente de
|
||||||
|
<literal>Person</literal>. Observa que <literal>Name</literal> define métodos
|
||||||
|
getter y setter para sus propiedades persistentes, pero no necesita declarar
|
||||||
|
ninguna interface ni propiedades identificadoras.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nuestro mapeo de Hibernate se vería así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid.hex"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La tabla person tendría las columnas <literal>pid</literal>,
|
||||||
|
<literal>birthday</literal>,
|
||||||
|
<literal>initial</literal>,
|
||||||
|
<literal>first</literal> y
|
||||||
|
<literal>last</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Como todos los tipos de valor, los componentes no soportan referencias compartidas.
|
||||||
|
En otras palabras, dos personas pueden tener el mismo nombre, pero los dos objetos
|
||||||
|
persona contendrían dos objetos nombre independientes, sólo "iguales" en valor.
|
||||||
|
La semántica de valor nulo de un componente es <emphasis>ad hoc</emphasis>.
|
||||||
|
Cuando se recargue el objeto contenedor, Hibernate asumirá que si todas las columnas del
|
||||||
|
componente son nulas, el componente entero es nulo. Esto debe estar bien para la mayoría
|
||||||
|
de propósitos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las propiedades de un componentes pueden ser de cualquier tipo de Hibernate
|
||||||
|
(colecciones, muchos-a-uno, asociaciones, otros componentes, etc). Los componentes
|
||||||
|
anidados <emphasis>no</emphasis> deben ser considerados un uso exótico. Hibernate está
|
||||||
|
concebido para soportar un modelo de objetos granularizado en fino.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El elemento <literal><component></literal> permite un subelemento
|
||||||
|
<literal><parent></literal> que mapee una propiedad de la clase del componente
|
||||||
|
como una referencia de regreso a la entidad contenedora.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid.hex"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name" unique="true">
|
||||||
|
<parent name="namedPerson"/> <!-- reference back to the Person -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-incollections" revision="1">
|
||||||
|
<title>Colecciones de objetos dependientes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las colecciones de componentes están soportadas (por ejemplo,
|
||||||
|
un array de tipo <literal>Name</literal>). Declara tu colección
|
||||||
|
de componentes remplazando la etiqueta <literal><element></literal>
|
||||||
|
por una etiqueta <literal><composite-element></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="someNames" table="some_names" lazy="true">
|
||||||
|
<key column="id"/>
|
||||||
|
<composite-element class="eg.Name"> <!-- class attribute required -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota: si defines un <literal>Set</literal> de elementos compuestos, es muy
|
||||||
|
importante implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||||
|
correctamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los elementos compuestos pueden contener componentes pero no colecciones.
|
||||||
|
Si tu elemento compuesto contiene a su vez componentes, usa la etiqueta
|
||||||
|
<literal><nested-composite-element></literal>. Este es un caso bastante
|
||||||
|
exótico - una colección de componentes que a su vez tienen componentes. A esta
|
||||||
|
altura debes estar preguntándote si una asociación uno-a-muchos es más
|
||||||
|
apropiada. Intenta remodelar el elemento compuesto como una entidad - pero
|
||||||
|
observa que aunque el modelo Java es el mismo, el modelo relacional y la
|
||||||
|
semántica de persistencia siguen siendo ligeramente diferentes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por favor observa que un mapeo de elemento compuesto no soporta
|
||||||
|
propiedades nulables si estás usando un <literal><set></literal>.
|
||||||
|
Hibernate tiene que usar cada columna para identificar un registro
|
||||||
|
al borrar objetos (no hay una columna clave primaria separada en la tabla del
|
||||||
|
elemento compuesto), lo que es imposible con valores nulos. Tienes que, o bien usar
|
||||||
|
sólo propiedades no nulas en un elemento compuesto o elegir un
|
||||||
|
<literal><list></literal>, <literal><map></literal>,
|
||||||
|
<literal><bag></literal> o <literal><idbag></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un caso especial de un elemento compuesto es un elemento compuesto con un
|
||||||
|
elemento anidado <literal><many-to-one></literal>. Un mapeo como este
|
||||||
|
te permite mapear columnas extra de una tabla de asociación muchos-a-muchos
|
||||||
|
a la clase del elemento compuesto. La siguiente es una asociación muchos-a-muchos
|
||||||
|
de <literal>Order</literal> a <literal>Item</literal> donde
|
||||||
|
<literal>purchaseDate</literal>, <literal>price</literal> y
|
||||||
|
<literal>quantity</literal> son propiedades de la asociación:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.Purchase">
|
||||||
|
<property name="purchaseDate"/>
|
||||||
|
<property name="price"/>
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por supuesto, no puede haber una referencia a la compra del otro lado para la
|
||||||
|
navegación bidireccional de la asociación. Recuerda que los componentes son tipos de
|
||||||
|
valor no permiten referencias compartidas. Una sola <literal>Purchase</literal> puede
|
||||||
|
estar en el conjunto de una <literal>Order</literal>, pero no puede ser referenciada
|
||||||
|
por el <literal>Item</literal> al mismo tiempo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>Incluso son posibles las asociaciones ternarias (o cuaternarias, etc):</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.OrderLine">
|
||||||
|
<many-to-one name="purchaseDetails class="eg.Purchase"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los elementos compuestos pueden aparecer en consultas usando la misma
|
||||||
|
sintáxis que las asociaciones a otras entidades.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-asmapindex">
|
||||||
|
<title>Componentes como índices de Map</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El elemento <literal><composite-map-key></literal> te permite mapear
|
||||||
|
una clase componente como la clave de un <literal>Map</literal>. Asegúrate que
|
||||||
|
sobrescribes <literal>hashCode()</literal> y <literal>equals()</literal>
|
||||||
|
correctamente en la clase componente.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-compositeid" revision="1">
|
||||||
|
<title>Componentes como identificadores compuestos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes usar un componente como un identidicador de una clase entidad. Tu clase
|
||||||
|
componente debe satisfacer ciertos requerimientos:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Debe implementar <literal>java.io.Serializable</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Debe re-implementar <literal>equals()</literal> y
|
||||||
|
<literal>hashCode()</literal>, consistentemente con la
|
||||||
|
noción de base de datos de igualdad de clave compuesta.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Nota: en Hibernat3, el segundo requerimiento no es absolutamente un
|
||||||
|
requerimiento rígido de Hibernate. Pero de todas formas, házlo.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
No puedes usar un <literal>IdentifierGenerator</literal> para generar claves
|
||||||
|
compuestas. La aplicación debe, en cambio, asignar sus propios identificadores.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Usa la etiqueta <literal><composite-id></literal> (con elementos
|
||||||
|
anidados <literal><key-property></literal>) en lugar de la usual
|
||||||
|
declaración <literal><id></literal>. Por ejemplo, la clase
|
||||||
|
<literal>OrderLine</literal> tiene una clave primaria que depende de
|
||||||
|
la clave primaria (compuesta) de <literal>Order</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
|
||||||
|
<composite-id name="id" class="OrderLineId">
|
||||||
|
<key-property name="lineId"/>
|
||||||
|
<key-property name="orderId"/>
|
||||||
|
<key-property name="customerId"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="name"/>
|
||||||
|
|
||||||
|
<many-to-one name="order" class="Order"
|
||||||
|
insert="false" update="false">
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>
|
||||||
|
....
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora, cualquier clave foránea que referencie la tabla de <literal>OrderLine</literal>
|
||||||
|
es también compuesta. Debes declarar esto en tus mapeos de otras clases. Una asociación
|
||||||
|
a <literal>OrderLine</literal> sería mapeado así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="orderLine" class="OrderLine">
|
||||||
|
<!-- the "class" attribute is optional, as usual -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(Nota que la etiqueta <literal><column></literal> es una alternativa al
|
||||||
|
atributo <literal>column</literal> en cualquier sitio.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una asociación <literal>muchos-a-muchos</literal> a <literal>OrderLine</literal>
|
||||||
|
también usa la clave foránea compuesta:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="undeliveredOrderLines">
|
||||||
|
<key column name="warehouseId"/>
|
||||||
|
<many-to-many class="OrderLine">
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-many>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La colección de <literal>OrderLine</literal>s en <literal>Order</literal> usaría:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="orderLines" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="OrderLine"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(El elemento <literal><one-to-many></literal>, como es usual, no declara columnas.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si <literal>OrderLine</literal> posee una colección por sí misma, tiene también
|
||||||
|
una clave foránea compuesta.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
....
|
||||||
|
....
|
||||||
|
<list name="deliveryAttempts">
|
||||||
|
<key> <!-- a collection inherits the composite key type -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<list-index column="attemptId" base="1"/>
|
||||||
|
<composite-element class="DeliveryAttempt">
|
||||||
|
...
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-dynamic" revision="1">
|
||||||
|
<title>Componentes dinámicos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes incluso mapear una propiedad de tipo <literal>Map</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<dynamic-component name="userAttributes">
|
||||||
|
<property name="foo" column="FOO" type="string"/>
|
||||||
|
<property name="bar" column="BAR" type="integer"/>
|
||||||
|
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
|
||||||
|
</dynamic-component>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La semántica de un mapeo <literal><dynamic-component></literal> es ídentica
|
||||||
|
a la de <literal><component></literal>. La ventaja de este tipo de mapeos es
|
||||||
|
la habilidad para determinar las propiedades reales del bean en tiempo de despliegue,
|
||||||
|
sólo con editar el documento de mapeo. La manipulación del documento de mapeo en tiempo
|
||||||
|
de ejecución es también posible, usando un analizador DOM. Incluso mejor, puedes acceder
|
||||||
|
(y cambiar) el metamodelo de tiempo de configuración de Hibernate por medio del objeto
|
||||||
|
<literal>Configuration</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,233 @@
|
||||||
|
<chapter id="events">
|
||||||
|
<title>Interceptores y eventos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Frecuentemente es útil para la aplicación reaccionar a ciertos eventos que ocurran dentro de Hibernate.
|
||||||
|
Esto permite la implementación de ciertos tipos de funcionalidade genérica, y extensión de la
|
||||||
|
funcionalidad de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-interceptors" revision="1">
|
||||||
|
<title>Interceptores</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La interface <literal>Interceptor</literal> provee callbacks desde la sesión a la aplicación
|
||||||
|
permitiendo a ésta última inspeccionar y/o manipular las propiedades de un objeto persistente
|
||||||
|
antes que sea salvado, actualizado, borrado o cargado. Un uso posible de esto es seguir la pista
|
||||||
|
de información de auditoría. Por ejemplo, el siguiente <literal>Interceptor</literal> establece
|
||||||
|
automáticamente el <literal>createTimestamp</literal> cuando un <literal>Auditable</literal> es
|
||||||
|
creado y actualiza la propiedad <literal>lastUpdateTimestamp</literal> cuando un
|
||||||
|
<literal>Auditable</literal> es acutalizado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package org.hibernate.test;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.hibernate.Interceptor;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
public class AuditInterceptor implements Interceptor, Serializable {
|
||||||
|
|
||||||
|
private int updates;
|
||||||
|
private int creates;
|
||||||
|
|
||||||
|
public void onDelete(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFlushDirty(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] currentState,
|
||||||
|
Object[] previousState,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
updates++;
|
||||||
|
for ( int i=0; i < propertyNames.length; i++ ) {
|
||||||
|
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
currentState[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
creates++;
|
||||||
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
||||||
|
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
state[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void postFlush(Iterator entities) {
|
||||||
|
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preFlush(Iterator entities) {
|
||||||
|
updates=0;
|
||||||
|
creates=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El interceptor podría ser especificado cuando se crea la sesión:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes además establecer un interceptor a un nivel global, usando la <literal>Configuration</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-events" revision="2">
|
||||||
|
<title>Sistema de eventos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si tienes que reaccionar a eventos particulares en tu capa de persistencia, puedes también la
|
||||||
|
arquitectura de <emphasis>eventos</emphasis> de Hibernate3. El sistema de eventos puede ser usado
|
||||||
|
en adición o como un remplazo a los interceptores.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esencialmente todos los métodos de la interface <literal>Session</literal> se correlacionan
|
||||||
|
con un evento. Tienes un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
|
||||||
|
(consulta el DTD del fichero de configuración XML o el paquete <literal>org.hibernate.event</literal>
|
||||||
|
para la lista completa de tipos de evento definidos). Cuando se hace una petición de uno de estos
|
||||||
|
métodos, la <literal>Session</literal> de Hibernate genera un evento apropiado y se lo pasa
|
||||||
|
al oyente (listener) de eventos configurado para ese tipo. De fábrica, estos oyentes implementan
|
||||||
|
el mismo procesamiento en los que siempre resultan aquellos métodos. Sin embargo, eres libre de
|
||||||
|
implementar una personalización de una de las interfaces oyentes (es decir, el
|
||||||
|
<literal>LoadEvent</literal> es procesado por la implementación registrada de la interface
|
||||||
|
<literal>LoadEventListener</literal>), en cuyo caso su implementación sería responsable
|
||||||
|
de procesar cualquier petición <literal>load()</literal> hecha a la <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los oyentes deben ser considerados efectivamente singletons; quiere decir, que son compartidos
|
||||||
|
entre las peticiones, y por lo tanto no guardan ningún estado en variables de instancia.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un oyente personalizado debe implementar la interface apropiada para el evento que quiere procesar y/o
|
||||||
|
extender una de las clases base de conveniencia (o incluso los oyentes de eventos por defecto
|
||||||
|
usados por Hibernate de fábrica al ser éstos declarados non-final para este propósito). Los
|
||||||
|
oyentes personalizados pueden ser registrados programáticamente a través del objeto
|
||||||
|
<literal>Configuration</literal>, o especificados en el XML de configuración de Hibernate
|
||||||
|
(la declaración declarativa a través del fichero de propiedades no está soportada).
|
||||||
|
He aquí un ejemplo de un oyente personalizado de eventos load:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class MyLoadListener extends DefaultLoadEventListener {
|
||||||
|
// this is the single method defined by the LoadEventListener interface
|
||||||
|
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||||||
|
throws HibernateException {
|
||||||
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||||||
|
throw MySecurityException("Unauthorized access");
|
||||||
|
}
|
||||||
|
return super.onLoad(event, loadType);
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Necesitas además una entrada de configuración diciéndole a Hibernate que use el
|
||||||
|
oyente en vez del oyente por defecto:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-configuration>
|
||||||
|
<session-factory>
|
||||||
|
...
|
||||||
|
<listener type="load" class="MyLoadListener"/>
|
||||||
|
</session-factory>
|
||||||
|
</hibernate-configuration>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En cambio, puedes registrarlo programáticamente:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||||||
|
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los oyentes registrados declarativamente no pueden compartir instancias. Si el mismo nombre de clase es
|
||||||
|
usado en múltiples elementos <literal><listener/></literal>, cada referencia resultará en una instancia
|
||||||
|
separada de esa clase. Si necesitas la capacidad de compartir instancias de oyentes entre tipos de oyente
|
||||||
|
debes usar el enfoque de registración programática.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
¿Por qué implementar una interface y definir el tipo espcífico durante la configuración?
|
||||||
|
Bueno, una implementación de oyente podría implementar múltiples interfaces de oyente
|
||||||
|
de eventos. Teniendo el tipo definido adicionalmente durante la registración lo hace más
|
||||||
|
fácil para activar o desactivar oyentes personalizados durante la configuración.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-decl-security">
|
||||||
|
<title>Seguridad declarativa de Hibernate</title>
|
||||||
|
<para>
|
||||||
|
Usualmente, la seguridad declarativa en aplicaciones Hibernate es manejada en una capa de fachada
|
||||||
|
de sesión. Ahora, Hibernate3 permite que ciertas acciones sean permitidas vía JACC, y autorizadas vía
|
||||||
|
JAAS. Esta en una funcionalidad opcional construída encima de la arquitectura de eventos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Primero, debes configurar los oyentes de eventos apropiados, para habilitar el uso de
|
||||||
|
autorización JAAS.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||||||
|
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||||||
|
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||||||
|
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Seguido, aún en <literal>hibernate.cfg.xml</literal>, liga los permisos a roles:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||||||
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los nombres de role son los roles entendidos por tu proveedor de JACC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,654 @@
|
||||||
|
<chapter id="example-mappings">
|
||||||
|
<title>Ejemplo: Varios Mapeos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este capítulo muestra mapeos de asociaciones más complejos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-emp">
|
||||||
|
<title>Empleador/Empleado</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El siguiente modelo de la relación entre <literal>Employer</literal> y <literal>Employee</literal>
|
||||||
|
usa una clase de entidad real (<literal>Employment</literal>) para representar la asociación.
|
||||||
|
Esto se ha hecho esto porque podría haber más de un período de empleo para los mismos dos participantes.
|
||||||
|
Se usan componentes para modelar valores monetarios y nombres de empleado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
He aquí un documento de mapeo posible:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Employer" table="employers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employer_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employment" table="employment_periods">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employment_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="startDate" column="start_date"/>
|
||||||
|
<property name="endDate" column="end_date"/>
|
||||||
|
|
||||||
|
<component name="hourlyRate" class="MonetaryAmount">
|
||||||
|
<property name="amount">
|
||||||
|
<column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
|
||||||
|
</property>
|
||||||
|
<property name="currency" length="12"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<many-to-one name="employer" column="employer_id" not-null="true"/>
|
||||||
|
<many-to-one name="employee" column="employee_id" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employee" table="employees">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employee_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="taxfileNumber"/>
|
||||||
|
<component name="name" class="Name">
|
||||||
|
<property name="firstName"/>
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="lastName"/>
|
||||||
|
</component>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Y he aquí el esquema de tablas generado por <literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table employers (
|
||||||
|
id BIGINT not null,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employment_periods (
|
||||||
|
id BIGINT not null,
|
||||||
|
hourly_rate NUMERIC(12, 2),
|
||||||
|
currency VARCHAR(12),
|
||||||
|
employee_id BIGINT not null,
|
||||||
|
employer_id BIGINT not null,
|
||||||
|
end_date TIMESTAMP,
|
||||||
|
start_date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employees (
|
||||||
|
id BIGINT not null,
|
||||||
|
firstName VARCHAR(255),
|
||||||
|
initial CHAR(1),
|
||||||
|
lastName VARCHAR(255),
|
||||||
|
taxfileNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK0 foreign key (employer_id) references employers
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK1 foreign key (employee_id) references employees
|
||||||
|
create sequence employee_id_seq
|
||||||
|
create sequence employment_id_seq
|
||||||
|
create sequence employer_id_seq]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-authorwork">
|
||||||
|
<title>Autor/Obra</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Considera el siguiente modelo de las relaciones entre <literal>Work</literal>,
|
||||||
|
<literal>Author</literal> y <literal>Person</literal>. Representamos la relación entre <literal>Work</literal>
|
||||||
|
y <literal>Author</literal> como una asociación muchos-a-muchos. Elegimos representar la relación entre
|
||||||
|
<literal>Author</literal> y <literal>Person</literal> como una asociación uno-a-uno. Otra posibilidad
|
||||||
|
hubiese sido que <literal>Author</literal> extendiera <literal>Person</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El siguiente documento de mapeo representa estas relaciones correctamente:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Work" table="works" discriminator-value="W">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="type" type="character"/>
|
||||||
|
|
||||||
|
<property name="title"/>
|
||||||
|
<set name="authors" table="author_work">
|
||||||
|
<key column name="work_id"/>
|
||||||
|
<many-to-many class="Author" column name="author_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<subclass name="Book" discriminator-value="B">
|
||||||
|
<property name="text"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Song" discriminator-value="S">
|
||||||
|
<property name="tempo"/>
|
||||||
|
<property name="genre"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Author" table="authors">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<!-- The Author must have the same identifier as the Person -->
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="alias"/>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
|
||||||
|
<set name="works" table="author_work" inverse="true">
|
||||||
|
<key column="author_id"/>
|
||||||
|
<many-to-many class="Work" column="work_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Person" table="persons">
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hay cuatro tablas en este mapeo. <literal>works</literal>, <literal>authors</literal> y <literal>persons</literal>
|
||||||
|
tienen los datos de obra, autor y persona respectivamente. <literal>author_work</literal> es una tabla de
|
||||||
|
asociación enlazando autores a obras. He aquí el esquema de tablas, tal como fue generado por
|
||||||
|
<literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table works (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
tempo FLOAT,
|
||||||
|
genre VARCHAR(255),
|
||||||
|
text INTEGER,
|
||||||
|
title VARCHAR(255),
|
||||||
|
type CHAR(1) not null,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table author_work (
|
||||||
|
author_id BIGINT not null,
|
||||||
|
work_id BIGINT not null,
|
||||||
|
primary key (work_id, author_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table authors (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
alias VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table persons (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table authors
|
||||||
|
add constraint authorsFK0 foreign key (id) references persons
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK0 foreign key (author_id) references authors
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK1 foreign key (work_id) references works]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-customerorderproduct">
|
||||||
|
<title>Cliente/Orden/Producto</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora considera un modelo de las relaciones entre <literal>Customer</literal>,
|
||||||
|
<literal>Order</literal> y <literal>LineItem</literal> y <literal>Product</literal>.
|
||||||
|
Hay una asociación uno-a-muchos entre <literal>Customer</literal> y <literal>Order</literal>,
|
||||||
|
pero, ¿cómo deberíamos representar <literal>Order</literal> / <literal>LineItem</literal> / <literal>Product</literal>?
|
||||||
|
He elegido mapear <literal>LineItem</literal> como una clase de asociación representando la
|
||||||
|
asociación muchos-a-muchos entre <literal>Order</literal> y <literal>Product</literal>. En Hibernate,
|
||||||
|
esto se llama un elemento compuesto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El documento de mapeo:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Customer" table="customers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
<set name="orders" inverse="true">
|
||||||
|
<key column="customer_id"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="orders">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="date"/>
|
||||||
|
<many-to-one name="customer" column="customer_id"/>
|
||||||
|
<list name="lineItems" table="line_items">
|
||||||
|
<key column="order_id"/>
|
||||||
|
<list-index column="line_number"/>
|
||||||
|
<composite-element class="LineItem">
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="product" column="product_id"/>
|
||||||
|
</composite-element>
|
||||||
|
</list>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product" table="products">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="serialNumber"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>customers</literal>, <literal>orders</literal>, <literal>line_items</literal> y
|
||||||
|
<literal>products</literal> tienen los datos de cliente, orden, ítem de línea de orden y producto
|
||||||
|
respectivamente. Además <literal>line_items</literal> actúa como una tabla de asociación enlazando
|
||||||
|
órdenes con productos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table customers (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table orders (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
customer_id BIGINT,
|
||||||
|
date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table line_items (
|
||||||
|
line_number INTEGER not null,
|
||||||
|
order_id BIGINT not null,
|
||||||
|
product_id BIGINT,
|
||||||
|
quantity INTEGER,
|
||||||
|
primary key (order_id, line_number)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table products (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
serialNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table orders
|
||||||
|
add constraint ordersFK0 foreign key (customer_id) references customers
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK0 foreign key (product_id) references products
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK1 foreign key (order_id) references orders]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="misc">
|
||||||
|
<title>Mapeos misceláneos de ejemplo</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Todos estos ejemplos están tomados de la batería de pruebas de Hibernate.
|
||||||
|
Encontrarás muchos otros mapeos de ejemplo útiles allí. Mira en la carpeta
|
||||||
|
<literal>test</literal> de la distribución de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>POR HACER: poner palabras alrededor de este material</para>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-typed-onetone">
|
||||||
|
<title>Asociación uno-a-uno "Tipificada"</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="name"/>
|
||||||
|
<one-to-one name="address"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'HOME'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
<one-to-one name="mailingAddress"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'MAILING'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address" batch-size="2"
|
||||||
|
check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
|
||||||
|
<composite-id>
|
||||||
|
<key-many-to-one name="person"
|
||||||
|
column="personName"/>
|
||||||
|
<key-property name="type"
|
||||||
|
column="addressType"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="street" type="text"/>
|
||||||
|
<property name="state"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key">
|
||||||
|
<title>Ejemplo de clave compuesta</title>
|
||||||
|
<programlisting><![CDATA[<class name="Customer">
|
||||||
|
|
||||||
|
<id name="customerId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" not-null="true" length="100"/>
|
||||||
|
<property name="address" not-null="true" length="200"/>
|
||||||
|
|
||||||
|
<list name="orders"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key column="customerId"/>
|
||||||
|
<index column="orderNumber"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="CustomerOrder" lazy="true">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
<synchronize table="Product"/>
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="Order$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="orderDate"
|
||||||
|
type="calendar_date"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property name="total">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity*p.price)
|
||||||
|
from LineItem li, Product p
|
||||||
|
where li.productId = p.productId
|
||||||
|
and li.customerId = customerId
|
||||||
|
and li.orderNumber = orderNumber )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<many-to-one name="customer"
|
||||||
|
column="customerId"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<bag name="lineItems"
|
||||||
|
fetch="join"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key>
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="LineItem"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="LineItem">
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="LineItem$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
<key-property name="productId" length="10"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="quantity"/>
|
||||||
|
|
||||||
|
<many-to-one name="order"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true">
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</many-to-one>
|
||||||
|
|
||||||
|
<many-to-one name="product"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"
|
||||||
|
column="productId"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
|
||||||
|
<id name="productId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="description"
|
||||||
|
not-null="true"
|
||||||
|
length="200"/>
|
||||||
|
<property name="price" length="3"/>
|
||||||
|
<property name="numberAvailable"/>
|
||||||
|
|
||||||
|
<property name="numberOrdered">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity)
|
||||||
|
from LineItem li
|
||||||
|
where li.productId = productId )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key-manytomany">
|
||||||
|
<title>Muchos-a-muchos con atributo de clave compuesta compartido</title>
|
||||||
|
<programlisting><![CDATA[<class name="User" table="`User`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<set name="groups" table="UserGroup">
|
||||||
|
<key>
|
||||||
|
<column name="userName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="Group">
|
||||||
|
<column name="groupName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Group" table="`Group`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="description"/>
|
||||||
|
<set name="users" table="UserGroup" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="groupName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="User">
|
||||||
|
<column name="userName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-content-discrimination">
|
||||||
|
<title>Discriminación basada en contenido</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person"
|
||||||
|
discriminator-value="P">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="person_id"
|
||||||
|
unsaved-value="0">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
|
||||||
|
<discriminator
|
||||||
|
type="character">
|
||||||
|
<formula>
|
||||||
|
case
|
||||||
|
when title is not null then 'E'
|
||||||
|
when salesperson is not null then 'C'
|
||||||
|
else 'P'
|
||||||
|
end
|
||||||
|
</formula>
|
||||||
|
</discriminator>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
not-null="true"
|
||||||
|
length="80"/>
|
||||||
|
|
||||||
|
<property name="sex"
|
||||||
|
not-null="true"
|
||||||
|
update="false"/>
|
||||||
|
|
||||||
|
<component name="address">
|
||||||
|
<property name="address"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
<property name="country"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<subclass name="Employee"
|
||||||
|
discriminator-value="E">
|
||||||
|
<property name="title"
|
||||||
|
length="20"/>
|
||||||
|
<property name="salary"/>
|
||||||
|
<many-to-one name="manager"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Customer"
|
||||||
|
discriminator-value="C">
|
||||||
|
<property name="comments"/>
|
||||||
|
<many-to-one name="salesperson"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-association-alternatekeys" >
|
||||||
|
<title>Asociaciones sobre claves alternativas</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" length="100"/>
|
||||||
|
|
||||||
|
<one-to-one name="address"
|
||||||
|
property-ref="person"
|
||||||
|
cascade="all"
|
||||||
|
fetch="join"/>
|
||||||
|
|
||||||
|
<set name="accounts"
|
||||||
|
inverse="true">
|
||||||
|
<key column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
<one-to-many class="Account"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<property name="userId" length="8"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="address" length="300"/>
|
||||||
|
<property name="zip" length="5"/>
|
||||||
|
<property name="country" length="25"/>
|
||||||
|
<many-to-one name="person" unique="true" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Account">
|
||||||
|
<id name="accountId" length="32">
|
||||||
|
<generator class="uuid.hex"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<many-to-one name="user"
|
||||||
|
column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
|
||||||
|
<property name="type" not-null="true"/>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,362 @@
|
||||||
|
<chapter id="example-parentchild">
|
||||||
|
<title>Ejemplo: Padre/Hijo</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una de las primerísimas cosas que los usuarios nuevos intentan hacer con Hibernate es modelar una relación de
|
||||||
|
tipo padre / hijo. Para esto hay dos enfoques diferentes. Por varias razones, el enfoque más conveniente,
|
||||||
|
especialmente para usuarios nuevos, es modelar tanto <literal>Parent</literal> como <literal>Child</literal>
|
||||||
|
como clases de entidad con una asociación <literal><one-to-many></literal> desde <literal>Parent</literal>
|
||||||
|
a <literal>Child</literal>. (El enfoque alternativo es declarar el <literal>Child</literal> como un
|
||||||
|
<literal><composite-element></literal>.) Ahora, resulta que la semántica por defecto de una asociación
|
||||||
|
uno a muchos (en Hibernate) es mucho menos cercana a la semántica usual de una relación padre / hijo que aquellas
|
||||||
|
de un mapeo de elementos compuestos. Explicaremos cómo usar una <emphasis>asociación uno a muchos bidireccional
|
||||||
|
con tratamiento en cascada</emphasis> para modelar una relación padre / hijo eficiente y elegantemente.
|
||||||
|
¡No es para nada difícil!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-collections">
|
||||||
|
<title>Una nota sobre las colecciones</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Se considera que las colecciones de Hibernate son una parte lógica de la entidad que las posee; nunca de
|
||||||
|
las entidades contenidas. ¡Esta es una distinción crucial! Esto tiene las siguientes consecuencias:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Cuando se quita / añade un objeto desde / a una colección, se incrementa el número de versión del
|
||||||
|
dueño de la colección.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Si un objeto que fue quitado de una colección es una instancia de un tipo de valor (por ejemplo, un
|
||||||
|
elemento compuesto), ese objeta cesará de ser persistente y su estado será completamente quitado de la
|
||||||
|
base de datos. Asimismo, añadir una instancia de tipo de valor a la colección causará que su estado
|
||||||
|
sea inmediatamente persistente.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Por otro lado, si se quita una entidad de una colección (una asociación uno-a-muchos o muchos-a-muchos),
|
||||||
|
no será borrado, por defecto. Este comportamiento es completamente consistente. ¡Un cambio en el
|
||||||
|
estado interno de otra entidad no hace desaparecer la entidad asociada! Asimismo, añadir una entidad a
|
||||||
|
una colección no causa que la entidad se vuelva persistente, por defecto.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En cambio, el comportamiento por defecto es que al añadir una entidad a una colección se crea meramente
|
||||||
|
un enlace entre las dos entidades, mientras que al quitarla se quita el enlace. Esto es muy apropiado para
|
||||||
|
todos los tipos de casos. Donde no es para nada apropiado es en el caso de una relación padre / hijo. donde
|
||||||
|
la vida del hijo está ligada al ciclo de vida del padre.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-bidir">
|
||||||
|
<title>Uno-a-muchos bidirectional</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supón que empezamos con una asociación simple <literal><one-to-many></literal> desde
|
||||||
|
<literal>Parent</literal> a <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si ejecutásemos el siguiente código
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = .....;
|
||||||
|
Child c = new Child();
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate publicaría dos sentencias SQL:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>un <literal>INSERT</literal> para crear el registro de <literal>c</literal></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
un <literal>UPDATE</literal> para crear el enlace desde <literal>p</literal> a
|
||||||
|
<literal>c</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto no es sólo ineficiente, sino que además viola cualquier restricción <literal>NOT NULL</literal> en la
|
||||||
|
columna <literal>parent_id</literal>. Podemos reparar la violación de restricción de nulabilidad
|
||||||
|
especificando <literal>not-null="true"</literal> en el mapeo de la colección:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id" not-null="true"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, esta no es la solución recomendada.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
El caso subyacente de este comportamiento es que el enlace (la clave foránea <literal>parent_id</literal>)
|
||||||
|
de <literal>p</literal> a <literal>c</literal> no es considerado parte del estado del objeto
|
||||||
|
<literal>Child</literal> y por lo tanto no es creada en el <literal>INSERT</literal>. De modo que la
|
||||||
|
solución es hacer el enlace parte del mapeo del <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(Necesitamos además añadir la propiedad <literal>parent</literal> a la clase <literal>Child</literal>.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora que la entidad <literal>Child</literal> está gestionando el estado del enlace, le decimos a la
|
||||||
|
colección que no actualice el enlace. Usamos el atributo <literal>inverse</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El siguiente código podría ser usado para añadir un nuevo <literal>Child</literal>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
c.setParent(p);
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Y ahora, ¡Sólo se publicaría un <literal>INSERT</literal> de SQL!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para ajustar un poco más las cosas, podríamos crear un método <literal>addChild()</literal> en
|
||||||
|
<literal>Parent</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public void addChild(Child c) {
|
||||||
|
c.setParent(this);
|
||||||
|
children.add(c);
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora, el código para añadir un <literal>Child</literal> se ve así
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-cascades">
|
||||||
|
<title>Ciclo de vida en cascada</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La llamada explícita a <literal>save()</literal> es aún molesta. Apuntaremos a esto usando tratamientos
|
||||||
|
en cascada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto simplifica el código anterior a
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Similarmente, no necesitamos iterar los hijos al salvar o borrar un <literal>Parent</literal>.
|
||||||
|
Lo siguiente quita <literal>p</literal> y todos sus hijos de la base de datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
session.delete(p);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, este código
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
c.setParent(null);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
no quitará <literal>c</literal> de la base de datos; sólo quitará el enlace a <literal>p</literal>
|
||||||
|
(y causará una violación a una restricción <literal>NOT NULL</literal>). Necesitas borrar el hijo
|
||||||
|
explícitamente llamando a <literal>delete()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
session.delete(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora, en nuestro caso, un <literal>Child</literal> no puede existir realmente sin su padre. De modo que
|
||||||
|
si quitamos un <literal>Child</literal> de la colección, realmente queremos que sea borrado. Para esto,
|
||||||
|
debemos usar <literal>cascade="all-delete-orphan"</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota: aunque el mapeo de la colección especifique <literal>inverse="true"</literal>, el tratamiento en
|
||||||
|
cascada se procesa aún al iterar los elementos de colección. De modo que si requieres que un objeto sea
|
||||||
|
salvado, borrado o actualizado en cascada, debes añadirlo a la colección. No es suficiente con simplemente
|
||||||
|
llamar a <literal>setParent()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-update">
|
||||||
|
<title>Tratamiento en cascada y <literal>unsaved-value</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supón que hemos cargado un <literal>Parent</literal> en una <literal>Session</literal>, hemos hecho algunos
|
||||||
|
cambios en una acción de UI y deseamos hacer persistentes estos cambios en una nueva sesión llamando a
|
||||||
|
<literal>update()</literal>. El <literal>Parent</literal> contendrá una colección de hijos y, ya que
|
||||||
|
está habilitado el tratamiento en cascada, Hibernate necesita saber qué hijos están recién instanciados
|
||||||
|
y cuáles representan filas existentes en la base de datos. Asumamos que tanto <literal>Parent</literal> como
|
||||||
|
<literal>Child</literal> tienen propiedades identificadoras generadas de tipo <literal>Long</literal>.
|
||||||
|
Hibernate usará el identificador y el valor de la propiedad de versión/timestamp para determinar cuáles de
|
||||||
|
los hijos son nuevos. (Ver <xref linkend="objectstate-saveorupdate"/>.) <emphasis>En Hibernate3, no es
|
||||||
|
más necesario especificar un <literal>unsaved-value</literal> explícitamente.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following code will update <literal>parent</literal> and <literal>child</literal> and insert
|
||||||
|
<literal>newChild</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[//parent and child were both loaded in a previous session
|
||||||
|
parent.addChild(child);
|
||||||
|
Child newChild = new Child();
|
||||||
|
parent.addChild(newChild);
|
||||||
|
session.update(parent);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Bueno, todo eso está muy bien para el caso de un identificador generado, pero ¿qué de los
|
||||||
|
identificadores asignados y de los identificadores compuestos? Esto es más difícil, ya que Hibernate
|
||||||
|
no puede usar la propiedad identificadora para distinguir entre un objeto recién instanciado (con un
|
||||||
|
identificador asignado por el usuario) y un objeto cargado en una sesión previa. En este caso, Hibernate
|
||||||
|
bien usará la propiedad de versión o timestamp, o bien consultará realmente el caché de segundo nivel,
|
||||||
|
o bien, en el peor de los casos, la base de datos, para ver si existe la fila.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<!-- undocumenting
|
||||||
|
<para>
|
||||||
|
There is one further possibility. The <literal>Interceptor</literal> method named
|
||||||
|
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
||||||
|
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Persistent {
|
||||||
|
private boolean _saved = false;
|
||||||
|
public void onSave() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
public void onLoad() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
public boolean isSaved() {
|
||||||
|
return _saved;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(The <literal>saved</literal> property is non-persistent.)
|
||||||
|
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||||||
|
and <literal>onSave()</literal> as follows.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||||||
|
if (entity instanceof Persistent) {
|
||||||
|
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||||||
|
return false;
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
||||||
|
</para>
|
||||||
|
-->
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-conclusion">
|
||||||
|
<title>Conclusión</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hay que resumir un poco aquí y podría parecer confuso a la primera vez. Sin embargo, en la práctica,
|
||||||
|
todo funciona muy agradablemente. La mayoría de las aplicaciones de Hibernate usan el patrón
|
||||||
|
padre / hijo en muchos sitios.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hemos mencionado una alternativa en el primer párrafo. Ninguno de los temas anteriores existe en el caso
|
||||||
|
de los mapeos <literal><composite-element></literal>, que tienen exactamente la semántica de una
|
||||||
|
relación padre / hijo. Desafortunadamente, hay dos grandes limitaciones para las clases de elementos
|
||||||
|
compuestos: los elementos compuestos no pueden poseer sus propias colecciones, y no deben ser el hijo
|
||||||
|
de cualquier otra entidad que no sea su padre único.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,429 @@
|
||||||
|
<chapter id="example-weblog">
|
||||||
|
<title>Ejemplo: Aplicación de Weblog</title>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-classes">
|
||||||
|
<title>Clases Persistentes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las clases persistentes representan un weblog, y un ítem enviado a un weblog. Van a ser modelados como una
|
||||||
|
relación padre/hijo estñndar, pero usaremos un bag ordenado, en vez de un conjunto (set).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Blog {
|
||||||
|
private Long _id;
|
||||||
|
private String _name;
|
||||||
|
private List _items;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public List getItems() {
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setItems(List list) {
|
||||||
|
_items = list;
|
||||||
|
}
|
||||||
|
public void setName(String string) {
|
||||||
|
_name = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class BlogItem {
|
||||||
|
private Long _id;
|
||||||
|
private Calendar _datetime;
|
||||||
|
private String _text;
|
||||||
|
private String _title;
|
||||||
|
private Blog _blog;
|
||||||
|
|
||||||
|
public Blog getBlog() {
|
||||||
|
return _blog;
|
||||||
|
}
|
||||||
|
public Calendar getDatetime() {
|
||||||
|
return _datetime;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public String getText() {
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
public String getTitle() {
|
||||||
|
return _title;
|
||||||
|
}
|
||||||
|
public void setBlog(Blog blog) {
|
||||||
|
_blog = blog;
|
||||||
|
}
|
||||||
|
public void setDatetime(Calendar calendar) {
|
||||||
|
_datetime = calendar;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setText(String string) {
|
||||||
|
_text = string;
|
||||||
|
}
|
||||||
|
public void setTitle(String string) {
|
||||||
|
_title = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-mappings">
|
||||||
|
<title>Mapeos de Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los mapeos XML ahora deben ser absolutamente directos.
|
||||||
|
</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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="Blog"
|
||||||
|
table="BLOGS">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="name"
|
||||||
|
column="NAME"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
|
||||||
|
<bag
|
||||||
|
name="items"
|
||||||
|
inverse="true"
|
||||||
|
order-by="DATE_TIME"
|
||||||
|
cascade="all">
|
||||||
|
|
||||||
|
<key column="BLOG_ID"/>
|
||||||
|
<one-to-many class="BlogItem"/>
|
||||||
|
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="BlogItem"
|
||||||
|
table="BLOG_ITEMS"
|
||||||
|
dynamic-update="true">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ITEM_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="title"
|
||||||
|
column="TITLE"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="text"
|
||||||
|
column="TEXT"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="datetime"
|
||||||
|
column="DATE_TIME"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<many-to-one
|
||||||
|
name="blog"
|
||||||
|
column="BLOG_ID"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-code">
|
||||||
|
<title>Código Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La siguiente clase demuestra algunos de los tipos de cosas que podemos haces con estas clases,
|
||||||
|
usando Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.Query;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
|
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||||
|
|
||||||
|
public class BlogMain {
|
||||||
|
|
||||||
|
private SessionFactory _sessions;
|
||||||
|
|
||||||
|
public void configure() throws HibernateException {
|
||||||
|
_sessions = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class)
|
||||||
|
.buildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportTables() throws HibernateException {
|
||||||
|
Configuration cfg = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class);
|
||||||
|
new SchemaExport(cfg).create(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog createBlog(String name) throws HibernateException {
|
||||||
|
|
||||||
|
Blog blog = new Blog();
|
||||||
|
blog.setName(name);
|
||||||
|
blog.setItems( new ArrayList() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.persist(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Blog blog, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setBlog(blog);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
blog.getItems().add(item);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Long blogid, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Blog blog = (Blog) session.load(Blog.class, blogid);
|
||||||
|
item.setBlog(blog);
|
||||||
|
blog.getItems().add(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(BlogItem item, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
item.setText(text);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(Long itemid, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
BlogItem item = (BlogItem) session.load(BlogItem.class, itemid);
|
||||||
|
item.setText(text);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listAllBlogNamesAndItemCounts(int max)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"select blog.id, blog.name, count(blogItem) " +
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join blog.items as blogItem " +
|
||||||
|
"group by blog.name, blog.id " +
|
||||||
|
"order by max(blogItem.datetime)"
|
||||||
|
);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog getBlogAndAllItems(Long blogid)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
Blog blog = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join fetch blog.items " +
|
||||||
|
"where blog.id = :blogid"
|
||||||
|
);
|
||||||
|
q.setParameter("blogid", blogid);
|
||||||
|
blog = (Blog) q.uniqueResult();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listBlogsAndRecentItems() throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"inner join blog.items as blogItem " +
|
||||||
|
"where blogItem.datetime > :minDate"
|
||||||
|
);
|
||||||
|
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.roll(Calendar.MONTH, false);
|
||||||
|
q.setCalendar("minDate", cal);
|
||||||
|
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
<chapter id="filters">
|
||||||
|
<title>Filtrando datos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 provee un nuevo enfoque innovador para manejar datos con reglas de "visibilidad".
|
||||||
|
Un <emphasis>filtro de Hibernate</emphasis> es un filtro global, con nombre y parametrizado
|
||||||
|
que puede ser habilitado o deshabilitado para una sesión de Hibernate en particular.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-filters">
|
||||||
|
<title>Filtros de Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 añade la habilidad de predefinir criterios de filtros y unir esos filtros tanto a
|
||||||
|
nivel de una clase como de una colección. Un criterio de filtro es la habilidad de definir una
|
||||||
|
cláusula de restricción muy similar al atributo existente "where" disponible en el elemento
|
||||||
|
class y varios elementos de colección. Excepto en que estos filtros pueden ser parametrizados.
|
||||||
|
La aplicación puede tomar la decisión en tiempo de ejecución de qué filtros deben estar
|
||||||
|
habilitados y cuáles deben ser sus parámetros. Los filtros pueden ser usados como vistas de
|
||||||
|
base de datos, pero parametrizados dentro de la aplicación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para usar los filtros, éstos deben primero ser definidos y luego unidos a los elementos de mapeo
|
||||||
|
apropiados. Para definir un filtro, usa el elemento <literal><filter-def/></literal> dentro
|
||||||
|
de un elemento <literal><hibernate-mapping/></literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="myFilter">
|
||||||
|
<filter-param name="myFilterParam" type="string"/>
|
||||||
|
</filter-def>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Entonces este filtro puede ser unido a una clase:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="myClass" ...>
|
||||||
|
...
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
o a una colección:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set ...>
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
o incluso a ambos (o muchos de cada uno) al mismo tiempo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los métodos en <literal>Session</literal> son: <literal>enableFilter(String filterName)</literal>,
|
||||||
|
<literal>getEnabledFilter(String filterName)</literal>, y <literal>disableFilter(String filterName)</literal>.
|
||||||
|
Por defecto, los filtros <emphasis>no</emphasis> están habilitados para una sesión dada; deben ser
|
||||||
|
habilitados explícitamente por medio del uso del método <literal>Session.enableFilter()</literal>,
|
||||||
|
que devuelve una instancia de la interface <literal>Filter</literal>. Usando el filtro simple definido
|
||||||
|
arriba, esto se vería así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que los métodos en la interface org.hibernate.Filter permiten el encadenamiento de métodos
|
||||||
|
común en gran parte de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un ejemplo completo, usando datos temporales con un patrón efectivo de fechas de registro:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="effectiveDate">
|
||||||
|
<filter-param name="asOfDate" type="date"/>
|
||||||
|
</filter-def>
|
||||||
|
|
||||||
|
<class name="Employee" ...>
|
||||||
|
...
|
||||||
|
<many-to-one name="department" column="dept_id" class="Department"/>
|
||||||
|
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
|
||||||
|
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
|
||||||
|
...
|
||||||
|
<!--
|
||||||
|
Note that this assumes non-terminal records have an eff_end_dt set to
|
||||||
|
a max db date for simplicity-sake
|
||||||
|
-->
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Department" ...>
|
||||||
|
...
|
||||||
|
<set name="employees" lazy="true">
|
||||||
|
<key column="dept_id"/>
|
||||||
|
<one-to-many class="Employee"/>
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Entonces, en orden de asegurar que siempre tendrás de vuelta registros actualmente efectivos,
|
||||||
|
simplemente habilita el filtro en la sesión previo a recuperar los datos de empleados:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = ...;
|
||||||
|
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
|
||||||
|
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
|
||||||
|
.setLong("targetSalary", new Long(1000000))
|
||||||
|
.list();
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En el HQL de arriba, aunque sólo hemos mencionado explícitamente una restricción de salario en
|
||||||
|
los resultados, debido al filtro habilitado la consulta sólo devolverá empleados actualmente activos
|
||||||
|
que tengan un salario mayor que un millón de dólares.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota: si planeas usar filtros con unión externa (outer joining) (bien a través de HQL, o bien
|
||||||
|
de recuperación de carga) sé cuidadoso en la dirección de expresión de la condición. Lo más seguro
|
||||||
|
es establecer esto para unión externa izquierda (left outer joining). En general, coloca el primer
|
||||||
|
parámetro seguido del nombre(s) de columna(s) después del operador.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,464 @@
|
||||||
|
<chapter id="inheritance">
|
||||||
|
<title>Mapeo de Herencia</title>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-strategies" revision="2">
|
||||||
|
<title>Las Tres Estrategias</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate soporta las tres estrategias básicas de mapeo de herencia:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
tabla por jerarquía de clases
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
tabla por subclase
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
tabla por clase concreta
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En adición, Hibernate soporta un cuarto, ligeramente diferente tipo
|
||||||
|
de polimorfismo:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
polimorfismo implícito
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Es posible usar estrategias de mapeo diferentes para diferentes
|
||||||
|
ramificaciones de la misma jerarquía de herencia, y entonces usar
|
||||||
|
polimorfismo implícito para conseguir polimorfismo a través de
|
||||||
|
toda la jerarquía. Sin embargo, Hibernate no soporta la mezcla de
|
||||||
|
mapeos <literal><subclass></literal>,
|
||||||
|
y <literal><joined-subclass></literal>
|
||||||
|
y <literal><union-subclass></literal> bajo el mismo elemento
|
||||||
|
<literal><class></literal> raíz. Es posible mezclar juntas las
|
||||||
|
estrategias de tabla por jerarquía y tabla por subclase, bajo el mismo
|
||||||
|
elemento <literal><class></literal>, combinando los elementos
|
||||||
|
<literal><subclass></literal> y <literal><join></literal>
|
||||||
|
(ver debajo).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperclass" >
|
||||||
|
<title>Tabla por jerarquía de clases</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supón que tenemos una interface <literal>Payment</literal>, con
|
||||||
|
los implementadores <literal>CreditCardPayment</literal>,
|
||||||
|
<literal>CashPayment</literal>, <literal>ChequePayment</literal>.
|
||||||
|
El mapeo de tabla por jerarquía se vería así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Se requiere exactamente una tabla. Hay una gran limitación de esta estrategia de mapeo:
|
||||||
|
las columnas declaradas por las subclases, como <literal>CCTYPE</literal>, no pueden
|
||||||
|
tener restricciones <literal>NOT NULL</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass">
|
||||||
|
<title>Tabla por subclase</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un mapeo de tabla por sublclase se vería así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Se requieren cuatro tablas. Las tres tablas de subclase tienen
|
||||||
|
asociaciones de clave primaria a la tabla de superclase (de modo
|
||||||
|
que en el modelo relacional es realmente una asociación uno-a-uno).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
||||||
|
<title>Tabla por subclase, usando un discriminador</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Observa que la implementación de Hibernate de tabla por subclase
|
||||||
|
no requiere ninguna columna discriminadora. Otros mapeadores
|
||||||
|
objeto/relacional usan una implementación diferente de tabla por
|
||||||
|
subclase que requiere una columna discriminadora de tipo en la tabla
|
||||||
|
de superclase. Este enfoque es mucho más difícil de implementar
|
||||||
|
pero discutiblemente más correcto desde un punto de vista relacional.
|
||||||
|
Si quisieras usar una columna discriminadora con la estrategia de
|
||||||
|
tabla por subclase, puedes combinar el uso de <literal><subclass></literal>
|
||||||
|
y <literal><join></literal>, como sigue:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
<join table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
<join table="CHEQUE_PAYMENT" fetch="select">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
la declaración opcional <literal>fetch="select"</literal> dice a Hibernate
|
||||||
|
que no recupere los datos de la subclase <literal>ChequePayment</literal>
|
||||||
|
usando una unión externa (outer join) al consultar la superclase.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
||||||
|
<title>Mezclando tabla por jerarquía de clases con tabla por subclase</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes incluso mezclar las estrategias de tabla po jerarquía y tabla por
|
||||||
|
subclase usando este enfoque:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para cualquiera de estas estrategias de mapeo, una asociación polimórfica
|
||||||
|
a la clase raíz <literal>Payment</literal> es mapeada usando <literal><many-to-one></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcrete" revision="1">
|
||||||
|
<title>Tabla por clase concreta</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Podríamos ir de dos maneras a la estrategia de mapeo de tabla por clase
|
||||||
|
concreta. La primera es usar <literal><union-subclass></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Están implicadas tres tablas. Cada tabla define columnas para todas las
|
||||||
|
propiedades de la clase, inccluyendo las propiedades heredadas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La limitación de este enfoque es que si una propiedad es mapeada en la
|
||||||
|
superclase, el nombre de columna debe ser el mismo en todas las tablas
|
||||||
|
de subclase. (Podríamos relajar esto en un lanzamiento futuro de Hibernate.)
|
||||||
|
La estrategia de generador de indentidad no está permitida en la herencia
|
||||||
|
de unión de subclase, de hecho la semilla de clave primaria tiene que ser
|
||||||
|
compartida a través de todas las subclases unidas de una jerarquía.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
||||||
|
<title>Tabla por clase concreta, usando polimorfismo implícito</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un enfoque alternativo es hacer uso de polimorfismo implícito:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CASH_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que en ningún sitio mencionamos la interface <literal>Payment</literal>
|
||||||
|
explícitamente. Nota además que las propiedades de <literal>Payment</literal>
|
||||||
|
son mapeadas en cada una de las subclases. Si quieres evitar duplicación,
|
||||||
|
considera usar entidades XML. (por ejemplo,
|
||||||
|
<literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
||||||
|
en la declaración <literal>DOCTYPE</literal> y <literal>&allproperties;</literal>
|
||||||
|
en el mapeo).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La desventaja de este enfoque es que Hibernate no genera <literal>UNION</literal>s
|
||||||
|
de SQL al realizar consultas polimórficas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para esta estrategia de mapeo, una asociación polimórfica a <literal>Payment</literal>
|
||||||
|
es mapeada generalmente usando <literal><any></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<any name="payment" meta-type="string" id-type="long">
|
||||||
|
<meta-value value="CREDIT" class="CreditCardPayment"/>
|
||||||
|
<meta-value value="CASH" class="CashPayment"/>
|
||||||
|
<meta-value value="CHEQUE" class="ChequePayment"/>
|
||||||
|
<column name="PAYMENT_CLASS"/>
|
||||||
|
<column name="PAYMENT_ID"/>
|
||||||
|
</any>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritace-mixingpolymorphism">
|
||||||
|
<title>Mezclando polimorfismo implícito con otros mapeos de herencia</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hay una cosa más por notar acerca de este mapeo. Ya que las subclases se mapean
|
||||||
|
cada una en su propio elemento <literal><class></literal> (y ya que
|
||||||
|
<literal>Payment</literal> es sólo una interface), cada una de las subclases
|
||||||
|
podría ser parte de otra jerarquía de herencia! (Y todavía puedes seguir usando
|
||||||
|
consultas polimórficas contra la interface <literal>Payment</literal>.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="CREDIT_CARD" type="string"/>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
|
||||||
|
<subclass name="VisaPayment" discriminator-value="VISA"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
|
||||||
|
<id name="id" type="long" column="TXN_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una vez más, no mencionamos a <literal>Payment</literal> explícitamente.
|
||||||
|
Si ejecutamos una consulta contra la interface <literal>Payment</literal>
|
||||||
|
- por ejemplo, <literal>from Payment</literal> - Hibernate devuelve
|
||||||
|
automáticamente instancias de <literal>CreditCardPayment</literal>
|
||||||
|
(y sus subclases, ya que ellas también implementan <literal>Payment</literal>),
|
||||||
|
<literal>CashPayment</literal> y <literal>ChequePayment</literal> pero
|
||||||
|
no instancias de <literal>NonelectronicTransaction</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-limitations">
|
||||||
|
<title>Limitaciones</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Existen ciertas limitaciones al enfoque de "polimorfismo implícito" en
|
||||||
|
la estrategia de mapeo de tabla por clase concreta. Existen limitaciones
|
||||||
|
algo menos restrictivas a los mapeos <literal><union-subclass></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La siguiente tabla muestra las limitaciones de mapeos de tabla por
|
||||||
|
clase concreta, y de polmorfismo implícito, en Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Funcionalidades de mapeo de herencia</title>
|
||||||
|
<tgroup cols='8' align='left' colsep='1' rowsep='1'>
|
||||||
|
<colspec colname='c1' colwidth="1*"/>
|
||||||
|
<colspec colname='c2' colwidth="1*"/>
|
||||||
|
<colspec colname='c3' colwidth="1*"/>
|
||||||
|
<colspec colname='c4' colwidth="1*"/>
|
||||||
|
<colspec colname='c5' colwidth="1*"/>
|
||||||
|
<colspec colname='c6' colwidth="1*"/>
|
||||||
|
<colspec colname='c7' colwidth="1*"/>
|
||||||
|
<colspec colname='c8' colwidth="1*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Estrategia de herencia</entry>
|
||||||
|
<entry>muchos-a-uno polimórfica</entry>
|
||||||
|
<entry>uno-a-uno polimórfica</entry>
|
||||||
|
<entry>uno-a-muchos polimórfica</entry>
|
||||||
|
<entry>mushos-a-muchos polimórfica</entry>
|
||||||
|
<entry><literal>load()/get()</literal> polimórficos</entry>
|
||||||
|
<entry>Consultas polimórficas</entry>
|
||||||
|
<entry>Uniones polimórficas</entry>
|
||||||
|
<entry>Recuperación por unión externa (outer join)</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>tabla por jerarquía de clases</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>soportada</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>tabla por subclase</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>soportada</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>tabla por clase concreta (union-subclass)</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal> (para <literal>inverse="true"</literal> solamente)</entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>soportada</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>tabla por clase concreta (polimorfismo implícito)</entry>
|
||||||
|
<entry><literal><any></literal></entry>
|
||||||
|
<entry><emphasis>no soportada</emphasis></entry>
|
||||||
|
<entry><emphasis>no soportada</emphasis></entry>
|
||||||
|
<entry><literal><many-to-any></literal></entry>
|
||||||
|
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><emphasis>no suportadas</emphasis></entry>
|
||||||
|
<entry><emphasis>no soportada</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,478 @@
|
||||||
|
<chapter id="persistent-classes" revision="2">
|
||||||
|
<title>Clases Persistentes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Clases presistentes son clases en una aplicación que implementan las
|
||||||
|
entidades del problema de negocio (por ejemplo, Customer y Order en una
|
||||||
|
aplicación de comercio electrónico). No todas las instancias de una
|
||||||
|
clase persistente se considera que estén en el estado persistente,
|
||||||
|
una instancia puede en cambio ser transitoria o estar separada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate funciona mejor si las clases siguen algunas simples reglas, también
|
||||||
|
conocidas como el modelo de programación de Viejas Clases Java Planas
|
||||||
|
(Plain Old Java Object o POJO). Sin embargo, ninguna de estas reglas son
|
||||||
|
requerimientos rígidos. En cambio, Hibernate3 asume muy poco acerca de
|
||||||
|
la naturaleza de tus objetos persistentes. Puedes expresar un modelo de dominio en
|
||||||
|
otras formas: usando árboles de instancias de <literal>Map</literal>,
|
||||||
|
por ejemplo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-pojo">
|
||||||
|
<title>Un ejemplo simple de POJO</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La mayoría de aplicaciones Java requieren una clase
|
||||||
|
representando felinos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Cat {
|
||||||
|
private Long id; // identifier
|
||||||
|
|
||||||
|
private Date birthdate;
|
||||||
|
private Color color;
|
||||||
|
private char sex;
|
||||||
|
private float weight;
|
||||||
|
private int litterId;
|
||||||
|
|
||||||
|
private Cat mother;
|
||||||
|
private Set kittens = new HashSet();
|
||||||
|
|
||||||
|
private void setId(Long id) {
|
||||||
|
this.id=id;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBirthdate(Date date) {
|
||||||
|
birthdate = date;
|
||||||
|
}
|
||||||
|
public Date getBirthdate() {
|
||||||
|
return birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWeight(float weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
void setColor(Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSex(char sex) {
|
||||||
|
this.sex=sex;
|
||||||
|
}
|
||||||
|
public char getSex() {
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLitterId(int id) {
|
||||||
|
this.litterId = id;
|
||||||
|
}
|
||||||
|
public int getLitterId() {
|
||||||
|
return litterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMother(Cat mother) {
|
||||||
|
this.mother = mother;
|
||||||
|
}
|
||||||
|
public Cat getMother() {
|
||||||
|
return mother;
|
||||||
|
}
|
||||||
|
void setKittens(Set kittens) {
|
||||||
|
this.kittens = kittens;
|
||||||
|
}
|
||||||
|
public Set getKittens() {
|
||||||
|
return kittens;
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKitten not needed by Hibernate
|
||||||
|
public void addKitten(Cat kitten) {
|
||||||
|
kitten.setMother(this);
|
||||||
|
kitten.setLitterId( kittens.size() );
|
||||||
|
kittens.add(kitten);
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Aquí hay cuatro reglas principales a seguir:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-constructor" revision="1">
|
||||||
|
<title>Implementa un constructor sin argumentos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> tiene un contructor sin argumentos. Todas las clases persistentes
|
||||||
|
deben tener un constructor por defecto (que puede no ser público) de modo que Hibernate
|
||||||
|
pueda instanciarlas usando <literal>Constructor.newInstance()</literal>. Recomendamos fuertemente tener
|
||||||
|
un constructor por defecto con al menos visibilidad de <emphasis>package</emphasis> para la
|
||||||
|
generación de proxies en tiempo de ejecución en Hibernate.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-identifier" revision="2">
|
||||||
|
<title>Provee una propiedad identificadora (opcional)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> tiene una propiedad llamada <literal>id</literal>. Esta
|
||||||
|
propiedad mapea a la columna clave primaria de la tabla de base de datos. La propiedad
|
||||||
|
podría llamarse cualquierCosa, y su tipo podría haber sido cualquier tipo
|
||||||
|
primitivo, cualquier tipo de "envoltura" primitivo, <literal>java.lang.String</literal>
|
||||||
|
o <literal>java.util.Date</literal>. (Si tu tabla de base de datos heredada tiene claves
|
||||||
|
compuestas, puedes incluso usar una clase definida por el usuario con propiedades de
|
||||||
|
estos tipos, ver la sección sobre identificadores compuestos luego.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La propiedad identificadora es estrictamente opcional. Puedes olvidarla y dejar que Hibernate
|
||||||
|
siga internamente la pista de los identificadores del objeto. Sin embargo, no recomendamos esto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
De hecho, alguna funcionalidad está disponible sólo para clases que
|
||||||
|
declaran una propiedad identificadora:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Reasociación transitiva de objetos separados (actualizaciones o
|
||||||
|
fusiones en cascada) - ver <xref linkend="objectstate-transitive"/>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.saveOrUpdate()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.merge()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Recomendamos que declares propiedades identificadoras nombradas-consistentemente
|
||||||
|
en clases persistentes. Mas aún, recomendamos que uses un tipo nulable
|
||||||
|
(es decir, no primitivo).
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-final">
|
||||||
|
<title>Prefiere las clases no finales (opcional)</title>
|
||||||
|
<para>
|
||||||
|
Un aspecto central de Hibernate, <emphasis>proxies</emphasis>, depende de que
|
||||||
|
las clases persistentes sean ya no finales, o sean ya la implementación
|
||||||
|
de una interface que declare todos los métodos públicos.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Puedes persistir con Hibernate clases <literal>final</literal> que no implementen una
|
||||||
|
interface, pero no serás capaz de usar proxies para recuperación perezosa
|
||||||
|
de asociaciones, lo que limitará tus opciones para afinar el rendimiento.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Debes también evitar declarar métodos <literal>public final</literal>
|
||||||
|
en clases non-final. Si quieres usar una clase con un método <literal>public
|
||||||
|
final</literal>, debes deshabilitar explícitamente el uso de proxies estableciendo
|
||||||
|
<literal>lazy="false"</literal>.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-accessors" revision="2">
|
||||||
|
<title>Declara métodos de acceso y modificación para los campos persistentes (opcional)</title>
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> declara métodos de acceso para todos sus campos persistente.
|
||||||
|
Muchas otras herramientas ORM persisten directamente variables de instancia. Creemos que
|
||||||
|
es mejor proveer una indirección entre el esquema relacional y las estructuras internas de la clase.
|
||||||
|
Por defecto, Hibernate persiste propiedades del estilo JavaBeans, y reconoce nombres de método
|
||||||
|
de la forma <literal>getFoo</literal>, <literal>isFoo</literal> y <literal>setFoo</literal>.
|
||||||
|
Puedes cambiar a acceso directo a campos para propiedades en particular, de ser necesario.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las propiedades <emphasis>no</emphasis> necesitan ser declaradas públicas. Hibernate puede
|
||||||
|
persistir una propiedad con un par get / set <literal>protected</literal> o <literal>private</literal>.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-inheritance">
|
||||||
|
<title>Implementando herencia</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una subclase puede a su vez observar la primera y segunda regla. Hereda su
|
||||||
|
propiedad identificadora de la superclase, <literal>Cat</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
public class DomesticCat extends Cat {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name=name;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-equalshashcode" revision="1">
|
||||||
|
<title>Implementando <literal>equals()</literal> y <literal>hashCode()</literal></title>
|
||||||
|
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Tienes que sobrescribir los métodos <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||||
|
si :
|
||||||
|
</para>
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
piensas poner instancias de clases persistentes en un <literal>Set</literal>
|
||||||
|
(la forma recomendada de representar asociaciones multivaluadas)
|
||||||
|
<emphasis>y</emphasis>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
piensas usar reasociación de instancias separadas.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos) y
|
||||||
|
identidad Java sólo dentro del ámbito de una sesión en particular.
|
||||||
|
De modo que en el momento que mezclamos instancias recuperadas en sesiones diferentes,
|
||||||
|
debemos implementar <literal>equals()</literal> y <literal>hashCode()</literal> si
|
||||||
|
deseamos tener una semántica significativa de <literal>Set</literal>s.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La forma más obvia es implementar <literal>equals()</literal>/<literal>hashCode()</literal>
|
||||||
|
comparando el valor identificador de ambos objetos. Si el valor es el mismo, ambos deben ser
|
||||||
|
la misma fila de base de datos, por lo tanto son iguales (si ambos son agregados a un
|
||||||
|
<literal>Set</literal>, sólo tendremos un elemento en el <literal>Set</literal>).
|
||||||
|
Desafortunadamente, no podemos usar este enfoque con identificadores generados! Hibernate sólo
|
||||||
|
asignará valores identificadores a objetos que son persistentes, una instancia recién
|
||||||
|
creada no tendrá ningún valor identificador! Además, si una instancia no está
|
||||||
|
salvada y está actualmente en un <literal>Set</literal>, salvarla asignará un
|
||||||
|
valor identificador al objeto. Si <literal>equals()</literal> and <literal>hashCode()</literal>
|
||||||
|
están basados en el valor identificador, el código hash podría cambiar,
|
||||||
|
rompiendo el contrato de <literal>Set</literal>. Ver el sitio web de Hibernate para una
|
||||||
|
discusión completa de este problema. Observa que esto no es una incidencia de Hibernate,
|
||||||
|
sino la semántica normal de Java de identidad de objeto e igualdad.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Recomendamos implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
||||||
|
usando <emphasis>igualdad de clave de negocio (Business key equality)</emphasis>.
|
||||||
|
Igualdad de clave de negocio significa que el método <literal>equals()</literal>
|
||||||
|
compara sólo las propiedades que forman la clave de negocio, una clave que podría
|
||||||
|
identificar nuestra instancia en el mundo real (una clave candidata
|
||||||
|
<emphasis>natural</emphasis>):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Cat {
|
||||||
|
|
||||||
|
...
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) return true;
|
||||||
|
if ( !(other instanceof Cat) ) return false;
|
||||||
|
|
||||||
|
final Cat cat = (Cat) other;
|
||||||
|
|
||||||
|
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
|
||||||
|
if ( !cat.getMother().equals( getMother() ) ) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
result = getMother().hashCode();
|
||||||
|
result = 29 * result + getLitterId();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que una clave de negocio no tiene que ser tan sólida como
|
||||||
|
una clave primaria candidata de base de datos (ver
|
||||||
|
<xref linkend="transactions-basics-identity"/>). Las propiedades inmutables o
|
||||||
|
únicas son usualmente buenas candidatas para una clave de negocio.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-dynamicmodels">
|
||||||
|
<title>Modelos dinámicos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Ten en cuenta que las siguientes funcionalidades están
|
||||||
|
consideradas actualmente experimentales y pueden cambiar en el futuro
|
||||||
|
cercano.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las entidades persistentes no necesariamente tienen que estar representadas
|
||||||
|
como clases POJO o como objetos JavaBean en tiempo de ejecución. Hibernate
|
||||||
|
soporta además modelos dinámicos (usando <literal>Map</literal>s de
|
||||||
|
<literal>Map</literal>s en tiempo de ejecución) y la representación
|
||||||
|
de entidades como árboles de DOM4J. Con este enfoque no escribes clases
|
||||||
|
persistentes, sólo ficheros de mapeo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por defecto, Hibernate funciona en modo POJO normal. Puedes establecer una
|
||||||
|
representación de entidad por defecto para una <literal>SessionFactory</literal>
|
||||||
|
en particular usando la opción de configuración
|
||||||
|
<literal>default_entity_mode</literal>
|
||||||
|
(ver <xref linkend="configuration-optional-properties"/>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los siguientes ejemplos demuestran la representación usando
|
||||||
|
<literal>Map</literal>s. Primero, en el fichero de mapeo,
|
||||||
|
tiene que declararse un <literal>entity-name</literal> en vez de
|
||||||
|
(o como agregado a) un nombre de clase:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class entity-name="Customer">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
type="long"
|
||||||
|
column="ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
column="NAME"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<property name="address"
|
||||||
|
column="ADDRESS"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<many-to-one name="organization"
|
||||||
|
column="ORGANIZATION_ID"
|
||||||
|
class="Organization"/>
|
||||||
|
|
||||||
|
<bag name="orders"
|
||||||
|
inverse="true"
|
||||||
|
lazy="false"
|
||||||
|
cascade="all">
|
||||||
|
<key column="CUSTOMER_ID"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ten en cuenta que aunque las asociaciones se declaran usando nombres
|
||||||
|
de clase objetivo, el tipo objetivo de una asociación puede
|
||||||
|
ser además una entidad dinámica en vez de un POJO.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Después de establecer el modo de entidad por defecto a
|
||||||
|
<literal>dynamic-map</literal> para la <literal>SessionFactory</literal>,
|
||||||
|
podemos trabajar en tiempo de ejecución con <literal>Map</literal>s
|
||||||
|
de <literal>Map</literal>s:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session s = openSession();
|
||||||
|
Transaction tx = s.beginTransaction();
|
||||||
|
Session s = openSession();
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
|
||||||
|
// Create an organization
|
||||||
|
Map foobar = new HashMap();
|
||||||
|
foobar.put("name", "Foobar Inc.");
|
||||||
|
|
||||||
|
// Link both
|
||||||
|
david.put("organization", foobar);
|
||||||
|
|
||||||
|
// Save both
|
||||||
|
s.save("Customer", david);
|
||||||
|
s.save("Organization", foobar);
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
s.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las ventajas de un mapeo dinámico es rápido tiempo de ciclo
|
||||||
|
de prototipado sin la necesidad de implementación de clases de entidad.
|
||||||
|
Sin embargo, pierdes chequeo de tipos en tiempo de compilación y
|
||||||
|
muy probablemente tratarás con muchas excepciones en tiempo de ejecución.
|
||||||
|
Gracias al mapeo de Hibernate, el esquema de base de datos puede estar facilmente
|
||||||
|
sano y normalizado, permitiendo agregar una implementación apropiada del
|
||||||
|
modelo de dominio más tarde.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los modos de representación de entidad pueden ser establecidos
|
||||||
|
por <literal>Session</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
dynamicSession.save("Customer", david);
|
||||||
|
...
|
||||||
|
dynamicSession.flush();
|
||||||
|
dynamicSession.close()
|
||||||
|
...
|
||||||
|
// Continue on pojoSession
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por favor, ten en cuenta que la llamada a <literal>getSession()</literal>
|
||||||
|
usando un <literal>EntityMode</literal> está en la API de
|
||||||
|
<literal>Session</literal>, no en la de <literal>SessionFactory</literal>.
|
||||||
|
De esta forma, la nueva <literal>Session</literal> comparte la conexión
|
||||||
|
JDBC, transacción y otra información de contexto. Esto significa
|
||||||
|
que no tienes que llamar a <literal>flush()</literal> ni a <literal>close()</literal>
|
||||||
|
en la <literal>Session</literal> secundaria, y tembién dejar el manejo
|
||||||
|
de la transacción y de la conexión a la unidad de trabajo primaria.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puede encontrarse más información sobre las capacidades de
|
||||||
|
representación XML en <xref linkend="xml"/>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
PORHACER: Documentar el framework de extensiones del usuario en los paquetes
|
||||||
|
de propiedad y proxies.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,431 @@
|
||||||
|
<chapter id="querycriteria">
|
||||||
|
<title>Consultas por Criterios</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Acompaña a Hibernate una API de consultas por criterios intuitiva y extensible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-creating">
|
||||||
|
<title>Creando una instancia de <literal>Criteria</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La interface <literal>org.hibernate.Criteria</literal> representa una consulta contra
|
||||||
|
una clase persistente en particular. La <literal>Session</literal> es una fábrica de instancias
|
||||||
|
de <literal>Criteria</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Criteria crit = sess.createCriteria(Cat.class);
|
||||||
|
crit.setMaxResults(50);
|
||||||
|
List cats = crit.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-narrowing">
|
||||||
|
<title>Estrechando el conjunto resultado</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un criterio individual de consulta es una instancia de la interface
|
||||||
|
<literal>org.hibernate.criterion.Criterion</literal>. La clase
|
||||||
|
<literal>org.hibernate.criterion.Restrictions</literal> define métodos de fábrica para obtener ciertos tipos
|
||||||
|
prefabricados de <literal>Criterion</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.add( Restrictions.between("weight", minWeight, maxWeight) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las restricciones pueden ser agrupadas lógicamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.add( Restrictions.or(
|
||||||
|
Restrictions.eq( "age", new Integer(0) ),
|
||||||
|
Restrictions.isNull("age")
|
||||||
|
) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
|
.add( Restrictions.disjunction()
|
||||||
|
.add( Restrictions.isNull("age") )
|
||||||
|
.add( Restrictions.eq("age", new Integer(0) ) )
|
||||||
|
.add( Restrictions.eq("age", new Integer(1) ) )
|
||||||
|
.add( Restrictions.eq("age", new Integer(2) ) )
|
||||||
|
) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hay un gran rango de tipos de criterio prefabricados (subclases de <literal>Restrictions</literal>),
|
||||||
|
pero uno que es especialmente útil te deja especificar SQL directamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.sql("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El sitio <literal>{alias}</literal> será remplazado por el alias de fila de la entidad consultada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un enfoque alternativo para obtener un criterio es tomarlo de una instancia de
|
||||||
|
<literal>Property</literal>. Puedes crear una <literal>Property</literal> llamando a
|
||||||
|
<literal>Property.forName()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
Property age = Property.forName("age");
|
||||||
|
List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.disjunction()
|
||||||
|
.add( age.isNull() )
|
||||||
|
.add( age.eq( new Integer(0) ) )
|
||||||
|
.add( age.eq( new Integer(1) ) )
|
||||||
|
.add( age.eq( new Integer(2) ) )
|
||||||
|
) )
|
||||||
|
.add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-ordering">
|
||||||
|
<title>Ordenando los resultados</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes ordenar los resultados usando <literal>org.hibernate.criterion.Order</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "F%")
|
||||||
|
.addOrder( Order.asc("name") )
|
||||||
|
.addOrder( Order.desc("age") )
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Property.forName("name").like("F%") )
|
||||||
|
.addOrder( Property.forName("name").asc() )
|
||||||
|
.addOrder( Property.forName("age").desc() )
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-associations">
|
||||||
|
<title>Asociaciones</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes especificar fácilmente restricciones sobre las entidades relacionadas al navegar asociaciones
|
||||||
|
usando <literal>createCriteria()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "F%")
|
||||||
|
.createCriteria("kittens")
|
||||||
|
.add( Restrictions.like("name", "F%")
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
nota que el segundo <literal>createCriteria()</literal> devuelve una nueva instancia de
|
||||||
|
<literal>Criteria</literal>, que hace referencia a los elementos de la colección
|
||||||
|
<literal>kittens</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La siguiente forma alternativa es útil en ciertas circunstancias.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.createAlias("kittens", "kt")
|
||||||
|
.createAlias("mate", "mt")
|
||||||
|
.add( Restrictions.eqProperty("kt.name", "mt.name") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(<literal>createAlias()</literal> no crea una nueva instancia de
|
||||||
|
<literal>Criteria</literal>.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
¡Observa que las colecciones de gatitos tenidas por las instancias de <literal>Cat</literal> devueltas
|
||||||
|
por las dos consultas previas <emphasis>no</emphasis> están prefiltradas por los criterios! Si deseas
|
||||||
|
recuperar sólo los gatitos que emparejen los criterios, debes usar <literal>returnMaps()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.createCriteria("kittens", "kt")
|
||||||
|
.add( Restrictions.eq("name", "F%") )
|
||||||
|
.returnMaps()
|
||||||
|
.list();
|
||||||
|
Iterator iter = cats.iterator();
|
||||||
|
while ( iter.hasNext() ) {
|
||||||
|
Map map = (Map) iter.next();
|
||||||
|
Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
|
||||||
|
Cat kitten = (Cat) map.get("kt");
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-dynamicfetching" revision="1">
|
||||||
|
<title>Recuperación dinámica de asociaciones</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes especificar la semántica de recuperación de asociaciones en tiempo de ejecución usando
|
||||||
|
<literal>setFetchMode()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createCriteria(Cat.class)
|
||||||
|
.add( Restrictions.like("name", "Fritz%") )
|
||||||
|
.setFetchMode("mate", FetchMode.EAGER)
|
||||||
|
.setFetchMode("kittens", FetchMode.EAGER)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esta consulta recuperará tanto <literal>mate</literal> como <literal>kittens</literal> por
|
||||||
|
unión exterior (outer join). Ver <xref linkend="performance-fetching"/> para más información.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-examples">
|
||||||
|
<title>Consultas por ejemplos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La clase <literal>org.hibernate.criterion.Example</literal> te permite construir un criterio de consulta
|
||||||
|
a partir de una instancia dada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Cat cat = new Cat();
|
||||||
|
cat.setSex('F');
|
||||||
|
cat.setColor(Color.BLACK);
|
||||||
|
List results = session.createCriteria(Cat.class)
|
||||||
|
.add( Example.create(cat) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las propiedades de versión, los identificadores y las asociaciones son ignorados. Por defecto,
|
||||||
|
las propiedades valuadas a nulo son excluídas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes ajustar cómo se aplica el <literal>Example</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Example example = Example.create(cat)
|
||||||
|
.excludeZeroes() //exclude zero valued properties
|
||||||
|
.excludeProperty("color") //exclude the property named "color"
|
||||||
|
.ignoreCase() //perform case insensitive string comparisons
|
||||||
|
.enableLike(); //use like for string comparisons
|
||||||
|
List results = session.createCriteria(Cat.class)
|
||||||
|
.add(example)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes incluso usar ejemplos para colocar criterios sobre objetos asociados.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.add( Example.create(cat) )
|
||||||
|
.createCriteria("mate")
|
||||||
|
.add( Example.create( cat.getMate() ) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-projection">
|
||||||
|
<title>Proyecciones, agregación y agrupamiento</title>
|
||||||
|
<para>
|
||||||
|
La clase <literal>org.hibernate.criterion.Projections</literal> es una fábrica de instancias de
|
||||||
|
<literal>Projection</literal>. Aplicamos una proyección a una consulta llamando a
|
||||||
|
<literal>setProjection()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.rowCount() )
|
||||||
|
.add( Restrictions.eq("color", Color.BLACK) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount() )
|
||||||
|
.add( Projections.avg("weight") )
|
||||||
|
.add( Projections.max("weight") )
|
||||||
|
.add( Projections.groupProperty("color") )
|
||||||
|
)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
No es necesario ningún "group by" explícito en una consulta por criterios.
|
||||||
|
Ciertos tipos de proyecciones son definidos para ser <emphasis>proyecciones agrupadas</emphasis>,
|
||||||
|
que además aparecen en la cláusula SQL <literal>group by</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puede opcionalmente asignarse un alias a una proyección, de modo que el valor proyectado pueda
|
||||||
|
ser referido en restricciones u ordenamientos. Aquí hay dos formas diferentes de hacer esto:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
|
||||||
|
.addOrder( Order.asc("colr") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.groupProperty("color").as("colr") )
|
||||||
|
.addOrder( Order.asc("colr") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los métodos <literal>alias()</literal> y <literal>as()</literal> simplemente envuelven una instancia
|
||||||
|
de proyección en otra instancia de <literal>Projection</literal> con alias. Como un atajo, puedes asignar
|
||||||
|
un alias cuando agregas la proyección a una lista de proyecciones:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount(), "catCountByColor" )
|
||||||
|
.add( Projections.avg("weight"), "avgWeight" )
|
||||||
|
.add( Projections.max("weight"), "maxWeight" )
|
||||||
|
.add( Projections.groupProperty("color"), "color" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.desc("catCountByColor") )
|
||||||
|
.addOrder( Order.desc("avgWeight") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Domestic.class, "cat")
|
||||||
|
.createAlias("kittens", "kit")
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.property("cat.name"), "catName" )
|
||||||
|
.add( Projections.property("kit.name"), "kitName" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.asc("catName") )
|
||||||
|
.addOrder( Order.asc("kitName") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes también usar <literal>Property.forName()</literal> para expresar proyecciones:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Property.forName("name") )
|
||||||
|
.add( Property.forName("color").eq(Color.BLACK) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List results = session.createCriteria(Cat.class)
|
||||||
|
.setProjection( Projections.projectionList()
|
||||||
|
.add( Projections.rowCount().as("catCountByColor") )
|
||||||
|
.add( Property.forName("weight").avg().as("avgWeight") )
|
||||||
|
.add( Property.forName("weight").max().as("maxWeight") )
|
||||||
|
.add( Property.forName("color").group().as("color" )
|
||||||
|
)
|
||||||
|
.addOrder( Order.desc("catCountByColor") )
|
||||||
|
.addOrder( Order.desc("avgWeight") )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querycriteria-detachedqueries">
|
||||||
|
<title>Consultas y subconsultas separadas</title>
|
||||||
|
<para>
|
||||||
|
La clase <literal>DetachedCriteria</literal> te deja crear una consulta fuera del ámbito de una sesión,
|
||||||
|
y entonces ejecutarla luego usando alguna <literal>Session</literal> arbitraria.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.add( Property.forName("sex").eq('F') );
|
||||||
|
|
||||||
|
Session session = ....;
|
||||||
|
Transaction txn = session.beginTransaction();
|
||||||
|
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
|
||||||
|
txn.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
También una <literal>DetachedCriteria</literal> puede usarse para expresar una subconsulta.
|
||||||
|
Las instancias de Criterion implicando subconsultas pueden obtenerse vía <literal>Subqueries</literal> o
|
||||||
|
<literal>Property</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.setProjection( Property.forName("weight").avg() );
|
||||||
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Property.forName("weight).gt(avgWeight) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
|
||||||
|
.setProjection( Property.forName("weight") );
|
||||||
|
session.createCriteria(Cat.class)
|
||||||
|
.add( Subqueries.geAll("weight", weights) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Incluso son posibles las subconsultas correlacionadas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
|
||||||
|
.setProjection( Property.forName("weight").avg() )
|
||||||
|
.add( Property.forName("cat2.sex").eqProperty("cat.sex") );
|
||||||
|
session.createCriteria(Cat.class, "cat")
|
||||||
|
.add( Property.forName("weight).gt(avgWeightForSex) )
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<!--TODO: ResultSetTransformer + aliasing. AliasToBeanTransformer allow returning arbitrary
|
||||||
|
user objects - similar to setResultClass in JDO2. General use of ResultTransformer
|
||||||
|
could also be explained. -->
|
||||||
|
|
||||||
|
<sect1 id="query-criteria-naturalid">
|
||||||
|
<title>Consultas por identificador natural</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para la mayoría de consultas, incluyendo las consultas por criterios, el caché de consulta no es
|
||||||
|
muy eficiente, debido a que la invalidación del caché de consulta ocurre demasiado frecuentemente.
|
||||||
|
Sin embargo, hay un tipo especial de consulta donde podemos optimizar el algoritmo de invalidación
|
||||||
|
de caché: búsquedas por una clave natural constante. En algunas aplicaciones, este tipo de consulta,
|
||||||
|
ocurre frecuentemente. La API de criterios brinda especial provisión para este caso de uso.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Primero, debes mapear la clave natural de tu entidad usando
|
||||||
|
<literal><natural-id></literal>, y habilitar el uso del caché de segundo nivel.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="User">
|
||||||
|
<cache usage="read-write"/>
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<natural-id>
|
||||||
|
<property name="name"/>
|
||||||
|
<property name="org"/>
|
||||||
|
</natural-id>
|
||||||
|
<property name="password"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que esta funcionalidad no está pensada para uso con entidades con claves naturales
|
||||||
|
<emphasis>mutable</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Seguido, habilita el caché de consulta de Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora, <literal>Restrictions.naturalId()</literal> nos permite hacer uso de el algoritmo de caché
|
||||||
|
más eficiente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.createCriteria(User.class)
|
||||||
|
.add( Restrictions.naturalId()
|
||||||
|
.set("name", "gavin")
|
||||||
|
.set("org", "hb")
|
||||||
|
).setCacheable(true)
|
||||||
|
.uniqueResult();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,477 @@
|
||||||
|
<chapter id="querysql" revision="2">
|
||||||
|
<title>SQL Nativo</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes también expresar consultas en el dialecto SQL nativo de tu base de datos. Esto es útil si quieres
|
||||||
|
utilizar aspectos específicos de base de datos tal como consejos (hints) de consulta o la palabra clave
|
||||||
|
<literal>CONNECT</literal> en Oracle. Provee además una clara ruta de migración desde una aplicación
|
||||||
|
basada en SQL/JDBC directo a Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 te permite especificar SQL escrito a mano (incluyendo procedimientos almacenados) para todas
|
||||||
|
las operaciones de creación, actualización, borrado y carga.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="querysql-creating">
|
||||||
|
<title>Creando una <literal>Query</literal> de SQL nativo</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las consultas SQL se controlan por medio de la interface <literal>SQLQuery</literal>, que se obtiene
|
||||||
|
llamando a <literal>Session.createSQLQuery()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createSQLQuery("select {cat.*} from cats cat")
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esta consulta especificada:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
la cadena de consulta SQL, con un lugar para que Hibernate inyecte los alias de columnas
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
la entidad devuelta por la consulta, y sus alias de tablas SQL
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El método <literal>addEntity()</literal> asocia alias de tablas SQL con clases de entidad,
|
||||||
|
y determina la forma del conjunto resultado de la consulta.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El método <literal>addJoin()</literal> puede ser usado para cargar asociaciones a otras entidades y
|
||||||
|
colecciones.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List cats = sess.createSQLQuery(
|
||||||
|
"select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id"
|
||||||
|
)
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.addJoin("kitten", "cat.kittens")
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una consulta SQL nativa podría devolver un valor escalar simple o una combinación de escalares y entidades.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
|
||||||
|
.addScalar("maxWeight", Hibernate.DOUBLE);
|
||||||
|
.uniqueResult();]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-aliasreferences">
|
||||||
|
<title>Alias y referencias de propiedad</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La notación <literal>{cat.*}</literal> usada arriba es un atajo para "todas las propiedades".
|
||||||
|
Alternativamente, puedes listar las columnas explícitamente, pero incluso en este caso dejamos
|
||||||
|
que Hibernate inyecte los alias de columnas SQL para cada propiedad. El lugar para un alias de columna
|
||||||
|
es sólo el nombre de propiedad cualificado por el alias de la tabla. En el siguiente ejemplo,
|
||||||
|
recuperamos <literal>Cat</literal>s de una tabla diferente (<literal>cat_log</literal>) a una
|
||||||
|
declarada en los metadatos de mapeo. Nota que podríamos incluso usar los alias de propiedad en la
|
||||||
|
cláusula where si quisieramos.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
La sintáxis <literal>{}</literal> <emphasis>no</emphasis> es requerida para consultas con nombre.
|
||||||
|
Ver <xref linkend="querysql-namedqueries"/>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[String sql = "select cat.originalId as {cat.id}, " +
|
||||||
|
"cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +
|
||||||
|
"cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +
|
||||||
|
"from cat_log cat where {cat.mate} = :catId"
|
||||||
|
|
||||||
|
List loggedCats = sess.createSQLQuery(sql)
|
||||||
|
.addEntity("cat", Cat.class)
|
||||||
|
.setLong("catId", catId)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Nota:</emphasis> si listas cada propiedad explícitamente, ¡debes incluir todas las
|
||||||
|
propiedades de la clase <emphasis>y sus subclases</emphasis>!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-namedqueries" revision="2">
|
||||||
|
<title>Consultas SQL con nombre</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las consultas SQL con nombre pueden definirse en el documento de mapeo y llamadas exactamente
|
||||||
|
en la misma forma en que a una consulta HQL con nombre. En este caso, <emphasis>no</emphasis>
|
||||||
|
necesitamos llamar a <literal>addEntity()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="persons">
|
||||||
|
<return alias="person" class="eg.Person"/>
|
||||||
|
SELECT person.NAME AS {person.name},
|
||||||
|
person.AGE AS {person.age},
|
||||||
|
person.SEX AS {person.sex}
|
||||||
|
FROM PERSON person
|
||||||
|
WHERE person.NAME LIKE :namePattern
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[List people = sess.getNamedQuery("persons")
|
||||||
|
.setString("namePattern", namePattern)
|
||||||
|
.setMaxResults(50)
|
||||||
|
.list();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los elementos <literal><return-join></literal> y <literal><load-collection></literal>
|
||||||
|
se usan para unir asociaciones y definir consultas que inicialicen colecciones, respectivamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="personsWith">
|
||||||
|
<return alias="person" class="eg.Person"/>
|
||||||
|
<return-join alias="address" property="person.mailingAddress"/>
|
||||||
|
SELECT person.NAME AS {person.name},
|
||||||
|
person.AGE AS {person.age},
|
||||||
|
person.SEX AS {person.sex},
|
||||||
|
adddress.STREET AS {address.street},
|
||||||
|
adddress.CITY AS {address.city},
|
||||||
|
adddress.STATE AS {address.state},
|
||||||
|
adddress.ZIP AS {address.zip}
|
||||||
|
FROM PERSON person
|
||||||
|
JOIN ADDRESS adddress
|
||||||
|
ON person.ID = address.PERSON_ID AND address.TYPE='MAILING'
|
||||||
|
WHERE person.NAME LIKE :namePattern
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una consulta SQL con nombre puede devolver un valor escalar. Debes especificar el alias de columna y
|
||||||
|
tipo Hibernate usando el elementp <literal><return-scalar></literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||||
|
<return-scalar column="name" type="string"/>
|
||||||
|
<return-scalar column="age" type="long"/>
|
||||||
|
SELECT p.NAME AS name,
|
||||||
|
p.AGE AS age,
|
||||||
|
FROM PERSON p WHERE p.NAME LIKE 'Hiber%'
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<sect2 id="propertyresults">
|
||||||
|
<title>Usando return-property para especificar explícitamente nombres de columna/alias</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Con <literal><return-property></literal> puedes decirle explícitamente a Hibernate qué
|
||||||
|
alias de columna usar, en vez de usar la sintáxis <literal>{}</literal> para dejar que Hibernate
|
||||||
|
inyecte sus propios alias.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="mySqlQuery">
|
||||||
|
<return alias="person" class="eg.Person">
|
||||||
|
<return-property name="name" column="myName"/>
|
||||||
|
<return-property name="age" column="myAge"/>
|
||||||
|
<return-property name="sex" column="mySex"/>
|
||||||
|
</return>
|
||||||
|
SELECT person.NAME AS myName,
|
||||||
|
person.AGE AS myAge,
|
||||||
|
person.SEX AS mySex,
|
||||||
|
FROM PERSON person WHERE person.NAME LIKE :name
|
||||||
|
</sql-query>
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal><return-property></literal> también trabaja con múltiples columnas. Esto resuelve una
|
||||||
|
limitación de la sintáxis <literal>{}</literal>, la cual no puede permitir un control fino de propiedades
|
||||||
|
multi-columna.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="organizationCurrentEmployments">
|
||||||
|
<return alias="emp" class="Employment">
|
||||||
|
<return-property name="salary">
|
||||||
|
<return-column name="VALUE"/>
|
||||||
|
<return-column name="CURRENCY"/>
|
||||||
|
</return-property>
|
||||||
|
<return-property name="endDate" column="myEndDate"/>
|
||||||
|
</return>
|
||||||
|
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
|
||||||
|
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
|
||||||
|
REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY
|
||||||
|
FROM EMPLOYMENT
|
||||||
|
WHERE EMPLOYER = :id AND ENDDATE IS NULL
|
||||||
|
ORDER BY STARTDATE ASC
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que en este ejemplo hemos usado <literal><return-property></literal> en combinación con
|
||||||
|
la sintáxis <literal>{}</literal> para inyección, permitiendo a los usuarios elejir cómo quieren
|
||||||
|
referirse a las columnas y propiedades.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si tu mapeo tiene un discriminador debes usar <literal><return-discriminator></literal>
|
||||||
|
para especificar la columna discriminadora.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="sp_query">
|
||||||
|
<title>Usando procedimientos almacenados para consultar</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 introduce soporte para consultas vía procedimientos almacenados. Los procedimientos
|
||||||
|
almacenados deben devolver un conjunto resultado como el primer parámetro de salida para ser
|
||||||
|
capaces de funcionar con Hibernate. Un ejemplo de uno procedimiento almacenado en Oracle 9
|
||||||
|
o superior es así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION selectAllEmployments
|
||||||
|
RETURN SYS_REFCURSOR
|
||||||
|
AS
|
||||||
|
st_cursor SYS_REFCURSOR;
|
||||||
|
BEGIN
|
||||||
|
OPEN st_cursor FOR
|
||||||
|
SELECT EMPLOYEE, EMPLOYER,
|
||||||
|
STARTDATE, ENDDATE,
|
||||||
|
REGIONCODE, EID, VALUE, CURRENCY
|
||||||
|
FROM EMPLOYMENT;
|
||||||
|
RETURN st_cursor;
|
||||||
|
END;]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para usar esta consulta en Hibernate necesitas mapearla por medio de una consulta con nombre.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="selectAllEmployees_SP" callable="true">
|
||||||
|
<return alias="emp" class="Employment">
|
||||||
|
<return-property name="employee" column="EMPLOYEE"/>
|
||||||
|
<return-property name="employer" column="EMPLOYER"/>
|
||||||
|
<return-property name="startDate" column="STARTDATE"/>
|
||||||
|
<return-property name="endDate" column="ENDDATE"/>
|
||||||
|
<return-property name="regionCode" column="REGIONCODE"/>
|
||||||
|
<return-property name="id" column="EID"/>
|
||||||
|
<return-property name="salary">
|
||||||
|
<return-column name="VALUE"/>
|
||||||
|
<return-column name="CURRENCY"/>
|
||||||
|
</return-property>
|
||||||
|
</return>
|
||||||
|
{ ? = call selectAllEmployments() }
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que los procedimientos almacenados sólo devuelven escalares y entidades.
|
||||||
|
No están soportados <literal><return-join></literal> y <literal><load-collection></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect3 id="querysql-limits-storedprocedures">
|
||||||
|
<title>Reglas/limitaciones para usar procedimientos almacenados</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para usar procedimientos almacenados con Hibernate los procedimientos tienen que seguir algunas reglas.
|
||||||
|
Si no siguen esas reglas no son usables por Hibernate. Si aún quisieras usar estos procedimientos
|
||||||
|
tendrías que ejecutarlos por medio de <literal>session.connection()</literal>. Las reglas son
|
||||||
|
diferentes para cada base de datos, ya que los vendedores de base de datos tienen diferentes
|
||||||
|
semánticas/sintáxis de procedimientos almacenados.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las consultas de procedimientos almacenados no pueden ser paginadas con
|
||||||
|
<literal>setFirstResult()/setMaxResults()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para Oracle se aplican las siguientes reglas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
El procedimiento debe devolver un conjunto resultado. Esto se hace devolviendo un
|
||||||
|
<literal>SYS_REFCURSOR</literal> en Oracle 9 o 10. En Oracle necesitas definir un
|
||||||
|
tipo <literal>REF CURSOR</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La forma recomendada es <literal>{ ? = call procName(<parameters>) }</literal> o
|
||||||
|
<literal>{ ? = call procName }</literal> (esto es más una regla de Oracle que una regla de Hibernate).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para Sybase o MS SQL server se aplican las siguientes reglas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
El procedimiento debe devolver un conjunto resultado. Nota que ya que estos servidores pueden
|
||||||
|
y devolverán múltiples conjuntos resultados y cuentas de actualización, Hibernate iterará
|
||||||
|
los resultados y tomará el primer resultado que sea un conjunto resultado como su valor
|
||||||
|
a devolver. Todo lo demás será descartado.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Si habilitas <literal>SET NOCOUNT ON</literal> en tu procedimiento será probablemente más
|
||||||
|
eficiente, pero esto no es un requerimiento.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</sect3>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-cud">
|
||||||
|
<title>SQL personalizado para crear, actualizar y borrar</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 puede usar sentencias SQL personalizadas para las operaciones de
|
||||||
|
crear, actualizar y borrar. Los persistidores de clases y colecciones en Hibernate
|
||||||
|
ya contienen un conjunto de cadenas generadas en tiempo de configuración (insertsql,
|
||||||
|
deletesql, updatesql, etc.). Las etiquetas de mapeo <literal><sql-insert></literal>,
|
||||||
|
<literal><sql-delete></literal>, y <literal><sql-update></literal> sobrescriben
|
||||||
|
estas cadenas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert>
|
||||||
|
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
|
||||||
|
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El SQL se ejecuta directamente en tu base de datos, de modo que eres libre de usar cualquier
|
||||||
|
dialecto que quieras. Esto reducirá, por supuesto, la portabilidad de tu mapeo si usas SQL
|
||||||
|
específico de la base de datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los procedimientos almacenados son soportados si está establecido el atributo
|
||||||
|
<literal>callable</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
|
||||||
|
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
|
||||||
|
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El orden de los parámetros posicionales son actualmente vitales, ya que deben estar en la
|
||||||
|
misma secuencia en que las espera Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes ver el orden esperado habilitando el registro de depuración para el nivel
|
||||||
|
<literal>org.hibernate.persister.entity</literal>. Con este nivel habilitado, Hibernate
|
||||||
|
imprimirá el SQL estático que se usa para crear, actualizar, borrar, etc. las entidades.
|
||||||
|
(Para ver la secuencia esperada, recuerda no incluir tu SQL personalizado en los ficheros
|
||||||
|
de mapeo ya que sobrescribirán el sql estático generado por Hibernate.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los procedimientos almacenados son, en la mayoría de los casos (léase, mejor hacerlo que no hacerlo),
|
||||||
|
obligados a devolver el número de filas insertadas/actualizadas/borradas, ya que Hibernate tiene algunas
|
||||||
|
comprobaciones en tiempo de ejecución del éxito de la sentencia. Hibernate siempre registra el primer
|
||||||
|
parámetro de la sentencia como un parámetro de salida numérico para las operaciones CUD:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2)
|
||||||
|
RETURN NUMBER IS
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
update PERSON
|
||||||
|
set
|
||||||
|
NAME = uname,
|
||||||
|
where
|
||||||
|
ID = uid;
|
||||||
|
|
||||||
|
return SQL%ROWCOUNT;
|
||||||
|
|
||||||
|
END updatePerson;]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="querysql-load">
|
||||||
|
<title>SQL personalizado para carga</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes también declarar tu propias consultas SQL (o HQL) para cargar entidades:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="person">
|
||||||
|
<return alias="pers" class="Person" lock-mode="upgrade"/>
|
||||||
|
SELECT NAME AS {pers.name}, ID AS {pers.id}
|
||||||
|
FROM PERSON
|
||||||
|
WHERE ID=?
|
||||||
|
FOR UPDATE
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto es sólo una declaración de consulta con nombrem como se ha discutido anteriormente.
|
||||||
|
Puedes hacer referencia a esta consulta con nombre en un mapeo de clase:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="increment"/>
|
||||||
|
</id>
|
||||||
|
<property name="name" not-null="true"/>
|
||||||
|
<loader query-ref="person"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto incluso funciona con procedimientos almacenados.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes incluso definit una consulta para la carga de colecciones:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="employments" inverse="true">
|
||||||
|
<key/>
|
||||||
|
<one-to-many class="Employment"/>
|
||||||
|
<loader query-ref="employments"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="employments">
|
||||||
|
<load-collection alias="emp" role="Person.employments"/>
|
||||||
|
SELECT {emp.*}
|
||||||
|
FROM EMPLOYMENT emp
|
||||||
|
WHERE EMPLOYER = :id
|
||||||
|
ORDER BY STARTDATE ASC, EMPLOYEE ASC
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Podrías incluso definir un cargador de entidades que cargue una colección por
|
||||||
|
recuperación por unión (join fetching):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<sql-query name="person">
|
||||||
|
<return alias="pers" class="Person"/>
|
||||||
|
<return-join alias="emp" property="pers.employments"/>
|
||||||
|
SELECT NAME AS {pers.*}, {emp.*}
|
||||||
|
FROM PERSON pers
|
||||||
|
LEFT OUTER JOIN EMPLOYMENT emp
|
||||||
|
ON pers.ID = emp.PERSON_ID
|
||||||
|
WHERE ID=?
|
||||||
|
</sql-query>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,666 @@
|
||||||
|
<chapter id="quickstart">
|
||||||
|
<title>Comienzo rápido con Tomcat</title>
|
||||||
|
|
||||||
|
<sect1 id="quickstart-intro" revision="2">
|
||||||
|
<title>Empezando con Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este tutorial explica una instalación de Hibernate con el
|
||||||
|
contenedor de servlets Apache Tomcat (hemos usado la versión 4.1,
|
||||||
|
las diferencias con la 5.0 deben ser mínimas) para una aplicación
|
||||||
|
basada en web. Hibernate trabaja bien en un entorno manejado con
|
||||||
|
todos los servidores de aplicaciones J2EE importantes, o incluso en aplicaciones
|
||||||
|
Java independientes. El sistema de base de datos es sólo una cuestión
|
||||||
|
de cambiar la configuración del dialecto SQL de Hibernate y las
|
||||||
|
propiedades de conexión.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Primero, tenemos que copiar todas las bibliotecas requeridas a la instalación
|
||||||
|
de Tomcat. Usamos un contexto web separado (<literal>webapps/quickstart</literal>)
|
||||||
|
para este tutorial, de modo que tenemos que considerar tanto la ruta de búsqueda
|
||||||
|
de bibliotecas global (<literal>TOMCAT/common/lib</literal>) como también
|
||||||
|
el cargador de clases a nivel de contexto en <literal>webapps/quickstart/WEB-INF/lib</literal>
|
||||||
|
(para ficheros JAR) y <literal>webapps/quickstart/WEB-INF/classes</literal>.
|
||||||
|
Nos referiremos a ambos niveles de cargador de clases como el classpath global y el classpath
|
||||||
|
de contexto, respectivamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora, copia las bibliotecas a los dos classpaths:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Copia el driver JDBC para la base de datos al classpath global. Esto se
|
||||||
|
requiere para el software de pool de conexiones DBCP que se distribuye
|
||||||
|
con Tomcat. Hibernate usa conexiones JDBC para ejecutar SQL sobre la base de
|
||||||
|
datos, de modo que, o bien tienes que proveer conexiones JDBC en pool,
|
||||||
|
o bien configurar Hibernate para que use uno de los pools soportados
|
||||||
|
directamente (C3P0, Proxool). Para este tutorial, copia la biblioteca
|
||||||
|
<literal>pg74jdbc3.jar</literal> (para PostgreSQL 7.4 y JDK 1.4) al
|
||||||
|
classpath del cargador global. Si quisieras usar una base de datos diferente,
|
||||||
|
simplemente copia su apropiado driver JDBC.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Nunca copies nada más dentro de la ruta del cargador de clases global
|
||||||
|
en Tomcat, o tendrás problemas con varias herramientas, incluyendo
|
||||||
|
Log4J, commons-logging y otras. Siempre usa el classpath de contexto para
|
||||||
|
cada aplicación web, esto es, copia las bibliotecas a
|
||||||
|
<literal>WEB-INF/lib</literal> y tus propias clases y ficheros de
|
||||||
|
configuración/propiedades a <literal>WEB-INF/classes</literal>.
|
||||||
|
Ambos directorios están a nivel del classpath de contexto por defecto.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate está empaquetado como una biblioteca JAR. El fichero
|
||||||
|
<literal>hibernate3.jar</literal> debe ser copiado en el classpath de contexto
|
||||||
|
junto a las otras clases de la aplicación. Hibernate requiere algunas
|
||||||
|
bibliotecas de terceros en tiempo de ejecución; éstas vienen
|
||||||
|
incluídas con la distribución de Hibernate en el directorio
|
||||||
|
<literal>lib/</literal>. Ver <xref linkend="3rdpartylibs"/>. Copia las
|
||||||
|
bibliotecas de terceros requeridas al classpath de contexto.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<table frame="topbot" id="3rdpartylibs">
|
||||||
|
<title>
|
||||||
|
Bibliotecas de terceros de Hibernate
|
||||||
|
</title>
|
||||||
|
<tgroup cols="2" rowsep="1" colsep="1">
|
||||||
|
<colspec colname="c1" colwidth="1*"/>
|
||||||
|
<colspec colname="c2" colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry align="center">
|
||||||
|
Biblioteca
|
||||||
|
</entry>
|
||||||
|
<entry align="center">
|
||||||
|
Descripción
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
antlr (requerida)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate usa ANTLR para producir analizadores de consultas,
|
||||||
|
esta biblioteca también se necesita en tiempo de ejecución.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
dom4j (requerida)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate usa dom4j para analizar ficheros de configuración
|
||||||
|
XML y ficheros de metadatos de mapeo XML.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
CGLIB, asm (requerida)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate usa la biblioteca de generación de código
|
||||||
|
para aumentar las clases en tiempo de ejecución
|
||||||
|
(en combinación con reflección Java).
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
Commons Collections, Commons Logging (requeridas)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate usa varias bibliotecas de utilidad del proyecto
|
||||||
|
Jakarta Commons de Apache.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
EHCache (requerida)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate puede usar varios provedores de caché para
|
||||||
|
el caché de segundo nivel. EHCache es el provedor de
|
||||||
|
caché por defecto si no se cambia en la configuración.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
Log4j (opcional)
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Hibernate usa la API de Commons Logging, que a su vez puede
|
||||||
|
usar Log4J como el mecanismo de logging subyacente. Si la
|
||||||
|
biblioteca Log4J está disponible en el directorio de
|
||||||
|
bibliotecas del contexto, Commons Logging usará Log4J
|
||||||
|
y la configuración <literal>log4j.properties</literal>
|
||||||
|
en el classpath de contexto. Un fichero de propiedades de ejemplo
|
||||||
|
para Log4J se incluye con la distribución de Hibernate.
|
||||||
|
Así que copia log4j.jar y el fichero de configuración
|
||||||
|
(de <literal>src/</literal>) a tu classpath de contexto si quieres
|
||||||
|
ver que ocurre tras escénas.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
¿Requerida o no?
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Echa una mirada al fichero <literal>lib/README.txt</literal> en la
|
||||||
|
distribución de Hibernate. Esta es una lista actualizada
|
||||||
|
de bibliotecas de terceros distribuídas con Hibernate.
|
||||||
|
Encontrarás listadas ahí todas las bibliotecas
|
||||||
|
requeridas y opcionales (Observa que "buildtame required" significa
|
||||||
|
aquí para la construcción de Hibernate, no de tu
|
||||||
|
aplicación).
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora instalamos el pooling y modo compartido de conexiones de base de datos
|
||||||
|
tanto en Tomcat como Hibernate. Esto significa que Tomcat proveerá
|
||||||
|
conexiones JDBC en pool (usando su funcionalidad prefabricada de pooling DBCP).
|
||||||
|
Hibernate pide esas conexiones a través de JNDI. Alternativamente,
|
||||||
|
puedes dejar que Hibernate maneje el pool de conexiones. Tomcat liga su pool
|
||||||
|
de conexiones a JNDI; agregamos una declaración de recurso al fichero
|
||||||
|
de configuración principal de Tomcat, <literal>TOMCAT/conf/server.xml</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<Context path="/quickstart" docBase="quickstart">
|
||||||
|
<Resource name="jdbc/quickstart" scope="Shareable" type="javax.sql.DataSource"/>
|
||||||
|
<ResourceParams name="jdbc/quickstart">
|
||||||
|
<parameter>
|
||||||
|
<name>factory</name>
|
||||||
|
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<!-- DBCP database connection settings -->
|
||||||
|
<parameter>
|
||||||
|
<name>url</name>
|
||||||
|
<value>jdbc:postgresql://localhost/quickstart</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>driverClassName</name><value>org.postgresql.Driver</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>username</name>
|
||||||
|
<value>quickstart</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>password</name>
|
||||||
|
<value>secret</value>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<!-- DBCP connection pooling options -->
|
||||||
|
<parameter>
|
||||||
|
<name>maxWait</name>
|
||||||
|
<value>3000</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>maxIdle</name>
|
||||||
|
<value>100</value>
|
||||||
|
</parameter>
|
||||||
|
<parameter>
|
||||||
|
<name>maxActive</name>
|
||||||
|
<value>10</value>
|
||||||
|
</parameter>
|
||||||
|
</ResourceParams>
|
||||||
|
</Context>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El contexto que configuramos en este ejemplo se llama <literal>quickstart</literal>,
|
||||||
|
su base es el directorio <literal>TOMCAT/webapp/quickstart</literal>. Para acceder
|
||||||
|
a cualquier servlet, llama a la ruta <literal>http://localhost:8080/quickstart</literal>
|
||||||
|
en tu navegador (por supuesto, agregando el nombre del servlet como se mapee en tu
|
||||||
|
<literal>web.xml</literal>). Puedes también ir más allá y crear
|
||||||
|
ahora un servlet simple que tenga un método <literal>process()</literal>
|
||||||
|
vacío.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Tomcat provee ahora conexiones a través de JNDI en
|
||||||
|
<literal>java:comp/env/jdbc/quickstart</literal>. Si tienes problemas obteniendo
|
||||||
|
el pool de conexiones en ejecución, refiérete a la documentación
|
||||||
|
de Tomcat. Si obtienes mensajes de excepción del driver JDBC, intenta instalar
|
||||||
|
primero el pool de conexiones JDBC sin Hibernate. Hay disponibles en la Web
|
||||||
|
tutoriales de Tomcat y JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Tu próximo paso es configurar Hibernate. Hibernate tiene que saber cómo
|
||||||
|
debe obtener conexiones JDBC. Usamos la configuración de Hibernate basada en XML.
|
||||||
|
El otro enfoque, usando un ficheros de propiedad, es casi equivalente pero pierde unas
|
||||||
|
pocas funcionalidades que sí permite la sintaxis XML. El fichero de configuración
|
||||||
|
XML se ubica en el classpath de contexto (<literal>WEB-INF/classes</literal>), como
|
||||||
|
<literal>hibernate.cfg.xml</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<!DOCTYPE hibernate-configuration PUBLIC
|
||||||
|
"-//Hibernate/Hibernate Configuration DTD//EN"
|
||||||
|
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||||
|
|
||||||
|
<hibernate-configuration>
|
||||||
|
|
||||||
|
<session-factory>
|
||||||
|
|
||||||
|
<property name="connection.datasource">java:comp/env/jdbc/quickstart</property>
|
||||||
|
<property name="show_sql">false</property>
|
||||||
|
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
|
||||||
|
|
||||||
|
<!-- Mapping files -->
|
||||||
|
<mapping resource="Cat.hbm.xml"/>
|
||||||
|
|
||||||
|
</session-factory>
|
||||||
|
|
||||||
|
</hibernate-configuration>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Desactivamos el registro (logging) de comandos SQL y decimos a Hibernate
|
||||||
|
qué dialecto SQL de base de datos se usa y dónde obtener
|
||||||
|
conexiones JDBC (declarando la dirección JNDI del pool ligado a
|
||||||
|
Tomcat). El dialecto es una configuración requerida, las bases de
|
||||||
|
datos difieren en su interpretación del "estándar" de SQL.
|
||||||
|
Hibernate cuidará de las diferencias y viene con dialectos incluídos
|
||||||
|
para todas las principales bases de datos comerciales y de código
|
||||||
|
abierto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <literal>SessionFactory</literal> es el concepto de Hibernate
|
||||||
|
de un almacén de datos solo. Pueden usarse múltiples
|
||||||
|
bases de datos creando múltiples ficheros de configuración
|
||||||
|
XML y creando múltiples objetos <literal>Configuration</literal>
|
||||||
|
y <literal>SessionFactory</literal> en tu aplicación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El último elemento del <literal>hibernate.cfg.xml</literal>
|
||||||
|
declara <literal>Cat.hbm.xml</literal> como el nombre de un fichero
|
||||||
|
de mapeo XML para la clase persistente <literal>Cat</literal>. Este
|
||||||
|
fichero contiene los metadatos para el mapeo de la clase POJO
|
||||||
|
<literal>Cat</literal> a una tabla (o tablas) de base de datos.
|
||||||
|
Volveremos a este fichero pronto. Escribamos primero la clase POJO
|
||||||
|
y luego declaremos los metadatos de mapeo para ella.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="quickstart-persistentclass" revision="1">
|
||||||
|
<title>Primera clase persistente</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate trabaja mejor con el modelo de programación de los
|
||||||
|
Viejos Objetos Planos de Java (POJOs, a veces llamados Ordinarios Objetos Planos de Java)
|
||||||
|
para clases persistentes. Un POJO es como un JavaBean, con las propiedades
|
||||||
|
de la clase accesible vía métodos getter y setter,
|
||||||
|
encapsulando la representación interna de la interfaz publicamente
|
||||||
|
visible (Hibernate puede también acceder a los campos directamente, si se
|
||||||
|
necesita):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package org.hibernate.examples.quickstart;
|
||||||
|
|
||||||
|
public class Cat {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private char sex;
|
||||||
|
private float weight;
|
||||||
|
|
||||||
|
public Cat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char getSex() {
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSex(char sex) {
|
||||||
|
this.sex = sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeight(float weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate no está restringido en su uso de tipos de propiedad, todos
|
||||||
|
los tipos y tipos primitivos del JDK de Java (como <literal>String</literal>,
|
||||||
|
<literal>char</literal> y <literal>Date</literal>) pueden ser mapeados, incluyendo
|
||||||
|
clases del framework de colecciones de Java. Puedes mapearlos como valores,
|
||||||
|
colecciones de valores, o asociaciones a otras entidades. El <literal>id</literal>
|
||||||
|
es una propiedad especial que representa el identificador de base de datos (clave
|
||||||
|
primaria) de la clase. Es altamente recomendado para entidades como un
|
||||||
|
<literal>Cat</literal>. Hibernate puede usar identificadores sólo
|
||||||
|
internamente, pero perderíamos algo de la flexibilidad en nuestra
|
||||||
|
arquitectura de aplicación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
No tiene que implementarse ninguna interface especial para las clases persistentes
|
||||||
|
ni tienes que subclasear de una clase persistente raíz en especial. Hibernate
|
||||||
|
tampoco requiere ningún procesamiento en tiempo de construcción,
|
||||||
|
como manipulación del byte-code. Se basa solamente en reflección de Java
|
||||||
|
y aumentación de clases en tiempo de ejecución (a través de CGLIB).
|
||||||
|
De modo que, sin ninguna dependencia de la clase POJO en Hibernate, podemos mapearla
|
||||||
|
a una tabla de base de datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="quickstart-mapping" revision="1">
|
||||||
|
<title>Mapeando el gato</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El fichero de mapeo <literal>Cat.hbm.xml</literal> contiene los
|
||||||
|
metadatos requeridos para el mapeo objeto/relacional. Los metadatos
|
||||||
|
incluyen la declaración de clases persistentes y el mapeo de
|
||||||
|
propiedades (a columnas y relaciones de claves foráneas a otras
|
||||||
|
entidades) a tablas de base de datos.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<class name="org.hibernate.examples.quickstart.Cat" table="CAT">
|
||||||
|
|
||||||
|
<!-- A 32 hex character is our surrogate key. It's automatically
|
||||||
|
generated by Hibernate with the UUID pattern. -->
|
||||||
|
<id name="id" type="string" unsaved-value="null" >
|
||||||
|
<column name="CAT_ID" sql-type="char(32)" not-null="true"/>
|
||||||
|
<generator class="uuid.hex"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<!-- A cat has to have a name, but it shouldn' be too long. -->
|
||||||
|
<property name="name">
|
||||||
|
<column name="NAME" length="16" not-null="true"/>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property name="sex"/>
|
||||||
|
|
||||||
|
<property name="weight"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Cada clase persistente debe tener un atributo identificador (realmente,
|
||||||
|
sólo las clases que representen entidades, no las clases dependientes
|
||||||
|
de tipo-valor, que son mapeadas como componentes de una entidad). Esta propiedad
|
||||||
|
es usada para distinguir los objetos persistentes: Dos gatos son iguales si
|
||||||
|
<literal>catA.getId().equals(catB.getId())</literal> es verdadero. Este concepto
|
||||||
|
se llama <emphasis>identidad de base de datos (database identity)</emphasis>.
|
||||||
|
Hibernate viene empaquetado con varios generadores de identificador para diferentes
|
||||||
|
escenarios (incluyendo generadores nativos para secuencias de base de datos, tablas
|
||||||
|
de identificadores alto/bajo, e identificadores asignados por aplicación).
|
||||||
|
Usamos el generador UUID (recomendado sólo para pruebas, pues deben
|
||||||
|
preferirse las claves enteras delegadas generadas por la base de datos) y
|
||||||
|
también especificamos la columna <literal>CAT_ID</literal> de la tabla
|
||||||
|
<literal>CAT</literal> para el valor identificador generado por Hibernate
|
||||||
|
(como una clave primaria de la tabla).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Todas las demás propiedades de <literal>Cat</literal> son mapeadas a la
|
||||||
|
misma tabla. En el caso de la propiedad <literal>name</literal>, la hemos mapeado
|
||||||
|
con una declaración explícita de columna de base de datos. Esto es
|
||||||
|
especialmente útil cuando el esquema de base de datos es generado
|
||||||
|
automáticamente (como sentencias DDL de SQL) desde la declaración
|
||||||
|
de mapeo con la herramienta <emphasis>SchemaExport</emphasis> de Hibernate.
|
||||||
|
Todas las demás propiedades son mapeadas usando la configuración
|
||||||
|
por defecto de Hibernate, que es lo que necesitas la mayoría del tiempo.
|
||||||
|
La tabla <literal>CAT</literal> en la base de datos se ve así como:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[ Columna | Tipo | Modificadores
|
||||||
|
--------+-----------------------+-----------
|
||||||
|
cat_id | character(32) | not null
|
||||||
|
name | character varying(16) | not null
|
||||||
|
sex | character(1) |
|
||||||
|
weight | real |
|
||||||
|
Indexes: cat_pkey primary key btree (cat_id)]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora debes crear esta tabla manualmente en tu base de datos, y luego leer el
|
||||||
|
<xref linkend="toolsetguide"/> si quieres automatizar este paso con la
|
||||||
|
herramienta <literal>hbm2ddl</literal>. Esta herramienta puede crear un
|
||||||
|
DDL SQL completo, incluyendo definición de tablas, restricciones
|
||||||
|
personalizadas de tipo de columnas, restricciones de unicidad e índices.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="quickstart-playingwithcats" revision="2">
|
||||||
|
<title>Jugando con gatos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ahora estamos listos para comenzar la <literal>Session</literal> de Hibernate.
|
||||||
|
Es el <emphasis>manejador de persistencia</emphasis> que usamos para almacenar
|
||||||
|
y traer <literal>Cat</literal>s hacia y desde la base de datos. Pero primero,
|
||||||
|
tenemos que obtener una <literal>Session</literal> (unidad de trabajo de Hibernate)
|
||||||
|
de la <literal>SessionFactory</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[SessionFactory sessionFactory =
|
||||||
|
new Configuration().configure().buildSessionFactory();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La llamada a <literal>configure()</literal> carga el fichero de
|
||||||
|
configuración <literal>hibernate.cfg.xml</literal> e
|
||||||
|
inicializa la instancia de <literal>Configuration</literal>.
|
||||||
|
Puedes establecer otras propiedades (e incluso cambiar los metadatos de mapeo)
|
||||||
|
accediendo a la <literal>Configuration</literal> <emphasis>antes</emphasis>
|
||||||
|
que construyas la <literal>SessionFactory</literal> (que es inmutable).
|
||||||
|
¿Dónde creamos la <literal>SessionFactory</literal> y cómo
|
||||||
|
accedemos a ella en nuestra aplicación?
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <literal>SessionFactory</literal> usualmente se construye una vez,
|
||||||
|
por ejemplo, al arrancar con un servlet <emphasis>load-on-startup</emphasis>.
|
||||||
|
Esto significa también que no debes mantenerla en una variable de instancia
|
||||||
|
en tus servlets, sino en alguna otro sitio. Además, necesitamos algún
|
||||||
|
tipo de <emphasis>Singleton</emphasis>, de modo que podamos acceder a la
|
||||||
|
<literal>SessionFactory</literal> fácilmente en el código de
|
||||||
|
aplicación. El siguiente enfoque mostrado resuelve ambos problemas:
|
||||||
|
configuración de arranque y fácil acceso a una
|
||||||
|
<literal>SessionFactory</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Implementamos una clase de ayuda <literal>HibernateUtil</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[import org.hibernate.*;
|
||||||
|
import org.hibernate.cfg.*;
|
||||||
|
|
||||||
|
public class HibernateUtil {
|
||||||
|
|
||||||
|
private static Log log = LogFactory.getLog(HibernateUtil.class);
|
||||||
|
|
||||||
|
private static final SessionFactory sessionFactory;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
// Create the SessionFactory
|
||||||
|
sessionFactory = new Configuration().configure().buildSessionFactory();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
// Make sure you log the exception, as it might be swallowed
|
||||||
|
log.error("Initial SessionFactory creation failed.", ex);
|
||||||
|
throw new ExceptionInInitializerError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final ThreadLocal session = new ThreadLocal();
|
||||||
|
|
||||||
|
public static Session currentSession() {
|
||||||
|
Session s = (Session) session.get();
|
||||||
|
// Open a new Session, if this Thread has none yet
|
||||||
|
if (s == null) {
|
||||||
|
s = sessionFactory.openSession();
|
||||||
|
session.set(s);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeSession() {
|
||||||
|
Session s = (Session) session.get();
|
||||||
|
if (s != null)
|
||||||
|
s.close();
|
||||||
|
session.set(null);
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esta clase no sólo cuida de la <literal>SessionFactory</literal>
|
||||||
|
con su inicializador static, sino que además tiene una variable
|
||||||
|
<literal>ThreadLocal</literal> que tiene la <literal>Session</literal>
|
||||||
|
para la hebra actual. Asegúrate de entender el concepto Java de una
|
||||||
|
variable local a una hebra antes de intentar usar esta ayuda. Una clase
|
||||||
|
<literal>HibernateUtil</literal> más compleja y potente puede
|
||||||
|
encontrarse en <literal>CaveatEmptor</literal>, http://caveatemptor.hibernate.org/
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <literal>SessionFactory</literal> es segura entre hebras, muchas hebras pueden
|
||||||
|
acceder a ella concurrentemente y pedirle <literal>Session</literal>s. Una
|
||||||
|
<literal>Session</literal> no es un objeto seguro entre hebras que representa
|
||||||
|
una sola unidad-de-trabajo con la base de datos. Las <literal>Session</literal>s
|
||||||
|
se abren desde una <literal>SessionFactory</literal> y son cerradas cuando
|
||||||
|
todo el trabajo está completo. Un ejemplo en el método
|
||||||
|
<literal>process()</literal> de tu servlet podría parecerse a esto
|
||||||
|
(sin manejo de excepciones):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = HibernateUtil.currentSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
Cat princess = new Cat();
|
||||||
|
princess.setName("Princess");
|
||||||
|
princess.setSex('F');
|
||||||
|
princess.setWeight(7.4f);
|
||||||
|
|
||||||
|
session.save(princess);
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
HibernateUtil.closeSession();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En una <literal>Session</literal>, cada operación de base de datos
|
||||||
|
ocurre dentro de una transacción que aísla las operaciones
|
||||||
|
de base de datos (incluso operaciones de sólo lectura).
|
||||||
|
Usamos la API de <literal>Transaction</literal> de Hibernate para
|
||||||
|
abstraer de la estrategia de transacciones subyacente (en nuestro caso,
|
||||||
|
transacciones JDBC). Esto permite que nuestro código sea desplegado
|
||||||
|
con transacciones manejadas por contenedor (usando JTA) sin cambio alguno.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Observa que puedes llamar <literal>HibernateUtil.currentSession();</literal>
|
||||||
|
tantas veces como quieras, siempre obtendrás la <literal>Session</literal>
|
||||||
|
actual de esta hebra. Tienes que asegurarte que la <literal>Session</literal>
|
||||||
|
sea cerrada después que se complete tu unidad-de-trabajo, ya sea en
|
||||||
|
código de tu servlet o en un filtro de servlet antes que la respuesta HTTP
|
||||||
|
sea enviada. El bonito efecto colateral de la segunda opción es la
|
||||||
|
fácil inicialización perezosa: la <literal>Session</literal> todavía
|
||||||
|
está abierta cuando se dibuja la vista, de modo que Hibernate puede cargar
|
||||||
|
objetos no inicializados mientras navegas tu actual grafo de objetos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate tiene varios métodos que pueden ser usados para traer
|
||||||
|
objetos desde la base de datos. La forma más flexible es usando
|
||||||
|
el Lenguaje de Consulta de Hibernate (Hibernate Query Language o HQL),
|
||||||
|
que es una extensión orientada a objetos de SQL fácil de
|
||||||
|
aprender:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
Query query = session.createQuery("select c from Cat as c where c.sex = :sex");
|
||||||
|
query.setCharacter("sex", 'F');
|
||||||
|
for (Iterator it = query.iterate(); it.hasNext();) {
|
||||||
|
Cat cat = (Cat) it.next();
|
||||||
|
out.println("Female Cat: " + cat.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate también ofrece una API <emphasis>consulta por criterios</emphasis>
|
||||||
|
orientada a objetos que puede ser usada para formular consultas de tipo seguro.
|
||||||
|
Por supuesto, Hibernate usa <literal>PreparedStatement</literal>s y ligado de
|
||||||
|
parámetros para toda la comunicación SQL con la base de datos.
|
||||||
|
También puedes usar la funcionalidad de consulta SQL directa de Hibernate
|
||||||
|
u obtener una conexión plana de JDBC de una <literal>Session</literal>
|
||||||
|
en casos raros.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="quickstart-summary" revision="1">
|
||||||
|
<title>Finalmente</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Rasguñamos solamente la superficie de Hibernate en este pequeño
|
||||||
|
tutorial. Por favor, observa que no incluimos ningún código
|
||||||
|
específico de servlet en nuestros ejemplos. Tienes que crear un servlet
|
||||||
|
por tí mismo e insertar el código de Hibernate como lo veas
|
||||||
|
ubicado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ten en mente que Hibernate, como capa de acceso a datos, está firmemente
|
||||||
|
integrado dentro de tu aplicación. Usualmente, todas las otras capas dependen
|
||||||
|
del mecanismo de persistencia. Asegúrate de entender las implicaciones
|
||||||
|
de este diseño.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para un ejemplo de aplicación más compleja, ver
|
||||||
|
http://caveatemptor.hibernate.org/ y echa una mirada a los
|
||||||
|
otros tutoriales con links en http://www.hibernate.org/Documentation
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,459 @@
|
||||||
|
<chapter id="toolsetguide" revision="2">
|
||||||
|
<title>Guía del Conjunto de Herramientas</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La ingeniería de ida y vuelta con Hibernate es posible usando un conjunto de plugins de Eclipse,
|
||||||
|
herramientas de línea de comandos, así como tareas de Ant.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las <emphasis>Herramientas de Hibernate</emphasis> actualmente incluyen plugins para la IDE de
|
||||||
|
Eclipse así como tareas de Ant para la ingeniería inversa de bases de datos existentes:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Editor de Mapeo:</emphasis> Un editor de ficheros de mapeo XML, que soporta autocompleción
|
||||||
|
y resaltado de sintáxis. Soporta también autocompleción semántica de nombres de clases y nombres de
|
||||||
|
campos/propiedades, haciéndolo mucho más versátil que un editor de XML normal.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Consola:</emphasis> La consola es una nueva vista en Eclipse. Además de la vista de
|
||||||
|
árbol de tus configuraciones de consola, tienes también una vista interactiva de tus clases
|
||||||
|
persistentes y sus relaciones. La console te permite ejecutar consultas HQL contra tu base de datos y
|
||||||
|
navegar el resultado directamente en Eclipse.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Asistentes de Desarrollo:</emphasis> Se proveen muchos asistentes con las herramientas
|
||||||
|
de Eclipse. Puedes usar un asistente para generar rápidamente ficheros de configuración de Hibernate
|
||||||
|
(cfg.xml), o incluso puedes haceruna ingeniería inversa completa de un esquema de base de datos existente
|
||||||
|
en ficheros de código de POJO y ficheros de mapeo de Hibernate. El asistente de ingeniería inversa soporta
|
||||||
|
plantillas personalizables.
|
||||||
|
</para></listitem>
|
||||||
|
<listitem><para>
|
||||||
|
<emphasis>Tareas de Ant:</emphasis>
|
||||||
|
</para></listitem>
|
||||||
|
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por favor refiérete al paquete <emphasis>Herramientas de Hibernate</emphasis> y su documentación para
|
||||||
|
más información.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, el paquete principal de Hibernate viene incluyendo una herramienta integrada
|
||||||
|
(puede ser usada incluso "dentro" de Hibernate on-the-fly): <emphasis>SchemaExport</emphasis>
|
||||||
|
también conocido como <literal>hbm2ddl</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="toolsetguide-s1" revision="2">
|
||||||
|
<title>Generación automática de esquemas</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una utilidad de Hibernate puede generar DDL desde tus ficheros de mapeo. El esquema generado incluye
|
||||||
|
restricciones de integridad referencial (claves primarias y foráneas) para las tablas de entidades y
|
||||||
|
colecciones. Las tablas y secuencias también son creadas para los generadores de identificadores mapeados.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Debes</emphasis> especificar un <literal>Dialecto</literal> SQL vía la propiedad
|
||||||
|
<literal>hibernate.dialect</literal> al usar esta herramienta, ya que el DDL es altamente específico del
|
||||||
|
vendedor.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
First, customize your mapping files to improve the generated schema.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-2" revision="1">
|
||||||
|
<title>Personalizando el esquema</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Muchos elementos de mapeo de Hibernate definen un atributo opcional llamado <literal>length</literal>.
|
||||||
|
Con este atributo puedes establecer el tamaño de una columna. (O, para tipos de datos
|
||||||
|
numéricos/decimales, la precisión.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Algunas etiquetas también aceptan un atributo <literal>not-null</literal> (para generar una restricción
|
||||||
|
<literal>NOT NULL</literal> en columnas de tablas) y y un atributo <literal>unique</literal> (para generar
|
||||||
|
restricciones <literal>UNIQUE</literal> en columnas de tablas).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Algunas etiquetas aceptan un atributo <literal>index</literal> para especificar el nombre de un índice
|
||||||
|
para esa columna. Se puede usar un atributo <literal>unique-key</literal> para agrupar columnas en una
|
||||||
|
restricción de clave de una sola unidad. Actualmente, el valor especificado del atributo
|
||||||
|
<literal>unique-key</literal> <emphasis>no</emphasis> es usado para nombrar la restricción, sólo para
|
||||||
|
agrupar las columnas en el fichero de mapeo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ejemplos:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="foo" type="string" length="64" not-null="true"/>
|
||||||
|
|
||||||
|
<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>
|
||||||
|
|
||||||
|
<element column="serial_number" type="long" not-null="true" unique="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Alternativamente, estos elementos aceptan tambíen un elemento hijo <literal><column></literal>.
|
||||||
|
Esto es particularmente útil para tipos multicolumnas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="foo" type="string">
|
||||||
|
<column name="foo" length="64" not-null="true" sql-type="text"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="bar" type="my.customtypes.MultiColumnType"/>
|
||||||
|
<column name="fee" not-null="true" index="bar_idx"/>
|
||||||
|
<column name="fi" not-null="true" index="bar_idx"/>
|
||||||
|
<column name="fo" not-null="true" index="bar_idx"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El atributo <literal>sql-type</literal> permite al usuario sobrescribir el mapeo por defecto de
|
||||||
|
tipo Hibernate a tipo de datos SQL.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El atributo <literal>check</literal> te permite especificar una comprobación de restricción.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="foo" type="integer">
|
||||||
|
<column name="foo" check="foo > 10"/>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Foo" table="foos" check="bar < 100.0">
|
||||||
|
...
|
||||||
|
<property name="bar" type="float"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<table frame="topbot" id="schemattributes-summary" revision="2">
|
||||||
|
<title>Resumen</title>
|
||||||
|
<tgroup cols="3">
|
||||||
|
<colspec colwidth="1*"/>
|
||||||
|
<colspec colwidth="1*"/>
|
||||||
|
<colspec colwidth="2.5*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Atributo</entry>
|
||||||
|
<entry>Valores</entry>
|
||||||
|
<entry>Interpretación</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>length</literal></entry>
|
||||||
|
<entry>number</entry>
|
||||||
|
<entry>largo de columna/precisión decimal</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>not-null</literal></entry>
|
||||||
|
<entry><literal>true|false</literal></entry>
|
||||||
|
<entry>especifica que la columna debe ser no nulable</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>unique</literal></entry>
|
||||||
|
<entry><literal>true|false</literal></entry>
|
||||||
|
<entry>especifica que la columna debe tener una restricción de unicidad</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>index</literal></entry>
|
||||||
|
<entry><literal>index_name</literal></entry>
|
||||||
|
<entry>especifica el nombre de un índice (multicolumna)</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>unique-key</literal></entry>
|
||||||
|
<entry><literal>unique_key_name</literal></entry>
|
||||||
|
<entry>especifica el nombre de una restricción de unicidad multicolumna</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>foreign-key</literal></entry>
|
||||||
|
<entry><literal>foreign_key_name</literal></entry>
|
||||||
|
<entry>
|
||||||
|
especifica el nombre de la restricción de clave foránea generada por una
|
||||||
|
asociación, úsalo en los elementos de mapeo <one-to-one>, <many-to-one>,
|
||||||
|
<key>, y <many-to-many>. Nota que los lados
|
||||||
|
<literal>inverse="true"</literal> no serán considerados por
|
||||||
|
<literal>SchemaExport</literal>.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>sql-type</literal></entry>
|
||||||
|
<entry><literal>column_type</literal></entry>
|
||||||
|
<entry>
|
||||||
|
sobrescribe el tipo de columna por defecto (sólo atributo del elemento
|
||||||
|
<literal><column></literal>)
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>check</literal></entry>
|
||||||
|
<entry>expresión SQL</entry>
|
||||||
|
<entry>
|
||||||
|
crea una restricción de comprobación SQL en columna o tabla
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El elemento <literal><comment></literal> te permite especificar un comentario para el esquema
|
||||||
|
generado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Customer" table="CurCust">
|
||||||
|
<comment>Current customers only</comment>
|
||||||
|
...
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<property name="balance">
|
||||||
|
<column name="bal">
|
||||||
|
<comment>Balance in USD</comment>
|
||||||
|
</column>
|
||||||
|
</property>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esto resulta en una sentencia <literal>comment on table</literal> o <literal>comment on column</literal>
|
||||||
|
en el DDL generado (donde esté soportado).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-3">
|
||||||
|
<title>Ejecutando la herramienta</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La herramienta <literal>SchemaExport</literal> escribe un guión DDL a la salida estándar y/o
|
||||||
|
ejecuta las sentencias DDL.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>java -cp </literal><emphasis>classpaths_de_hibernate</emphasis>
|
||||||
|
<literal>org.hibernate.tool.hbm2ddl.SchemaExport</literal> <emphasis>opciones ficheros_de_mapeo</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Opciones de Línea de Comandos de <literal>SchemaExport</literal></title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Opción</entry>
|
||||||
|
<entry>Descripción</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--quiet</literal></entry>
|
||||||
|
<entry>no enviar a salida estándar el guión</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--drop</literal></entry>
|
||||||
|
<entry>sólo desechar las tablas</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--text</literal></entry>
|
||||||
|
<entry>no exportar a la base de datos</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--output=my_schema.ddl</literal></entry>
|
||||||
|
<entry>enviar la salida del guión ddl a un fichero</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
|
||||||
|
<entry>lee la configuración de Hibernate de un fichero XML</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||||
|
<entry>lee las propiedades de base de datos de un fichero</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--format</literal></entry>
|
||||||
|
<entry>formatea agradablemente el SQL generado en el guión</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--delimiter=x</literal></entry>
|
||||||
|
<entry>establece un delimitador de fin de línea para el guión</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes incluso encajar <literal>SchemaExport</literal> en tu aplicación:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = ....;
|
||||||
|
new SchemaExport(cfg).create(false, true);]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-4">
|
||||||
|
<title>Propiedades</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las propiedades de base de datos pueden especificarse
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>como propiedades de sistema con <literal>-D</literal><emphasis><property></emphasis></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>en <literal>hibernate.properties</literal></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>en un fichero de propiedades mencionado con <literal>--properties</literal></para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Las propiedades necesarias son:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Propiedades de Conexión de SchemaExport</title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Nombre de Propiedad</entry>
|
||||||
|
<entry>Descripción</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.driver_class</literal></entry>
|
||||||
|
<entry>clase del driver jdbc</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.url</literal></entry>
|
||||||
|
<entry>url de jdbc</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.username</literal></entry>
|
||||||
|
<entry>usuario de base de datos</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.connection.password</literal></entry>
|
||||||
|
<entry>contraseña de usuario</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>hibernate.dialect</literal></entry>
|
||||||
|
<entry>dialecto</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-5">
|
||||||
|
<title>Usando Ant</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes llamar a <literal>SchemaExport</literal> desde tu guión de construcción de Ant:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<target name="schemaexport">
|
||||||
|
<taskdef name="schemaexport"
|
||||||
|
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
|
||||||
|
classpathref="class.path"/>
|
||||||
|
|
||||||
|
<schemaexport
|
||||||
|
properties="hibernate.properties"
|
||||||
|
quiet="no"
|
||||||
|
text="no"
|
||||||
|
drop="no"
|
||||||
|
delimiter=";"
|
||||||
|
output="schema-export.sql">
|
||||||
|
<fileset dir="src">
|
||||||
|
<include name="**/*.hbm.xml"/>
|
||||||
|
</fileset>
|
||||||
|
</schemaexport>
|
||||||
|
</target>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-6">
|
||||||
|
<title>Actualizaciones incrementales de esquema</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La herramienta <literal>SchemaUpdate</literal> actualizará un esquema existente con cambios
|
||||||
|
"incrementales". Nota que <literal>SchemaUpdate</literal> depende fuertemente de la API de metadatos
|
||||||
|
de JDBC, de modo que no funcionará con todos los drivers JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>java -cp </literal><emphasis>classpaths_de_hibernate</emphasis>
|
||||||
|
<literal>org.hibernate.tool.hbm2ddl.SchemaUpdate</literal> <emphasis>opciones ficheros_de_mapeo</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Opciones de Línea de Comandos de <literal>SchemaUpdate</literal></title>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<colspec colwidth="1.5*"/>
|
||||||
|
<colspec colwidth="2*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Opción</entry>
|
||||||
|
<entry>Descripción</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--quiet</literal></entry>
|
||||||
|
<entry>no enviar a salida estándar el guión</entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry><literal>--properties=hibernate.properties</literal></entry>
|
||||||
|
<entry>lee las propiedades de base de datos de un fichero</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes encajar <literal>SchemaUpdate</literal> en tu aplicación:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = ....;
|
||||||
|
new SchemaUpdate(cfg).execute(false);]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="toolsetguide-s1-7">
|
||||||
|
<title>Usando Ant para actualizaciones incrementales de esquema</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes llamar a <literal>SchemaUpdate</literal> desde el guión de Ant:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<target name="schemaupdate">
|
||||||
|
<taskdef name="schemaupdate"
|
||||||
|
classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask"
|
||||||
|
classpathref="class.path"/>
|
||||||
|
|
||||||
|
<schemaupdate
|
||||||
|
properties="hibernate.properties"
|
||||||
|
quiet="no">
|
||||||
|
<fileset dir="src">
|
||||||
|
<include name="**/*.hbm.xml"/>
|
||||||
|
</fileset>
|
||||||
|
</schemaupdate>
|
||||||
|
</target>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,925 @@
|
||||||
|
<chapter id="transactions" revision="1">
|
||||||
|
<title>Transacciones y Concurrencia</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El punto más importante sobre Hibernate y el control de concurrencia es que muy fácil
|
||||||
|
de comprender. Hibernate usa directamente conexiones JDBC y recursos JTA sin agregar
|
||||||
|
ningún comportamiento de bloqueo adicional. Recomendamos altamente que gastes algo de
|
||||||
|
tiempo con la especificación de JDBC, ANSI, y el aislamiento de transacciones de tu sistema
|
||||||
|
de gestión de base de datos. Hibernate sólo añade versionado automático pero no bloquea
|
||||||
|
objetos en memoria ni cambia el nivel de aislamiento de tus transacciones de base de datos.
|
||||||
|
Básicamente, usa Hibernate como usarías JDBC directo (o JTA/CMT) con tus recursos de base de
|
||||||
|
datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, además del versionado automático, Hibernate ofrece una API (menor) para
|
||||||
|
bloqueo pesimista de filas, usando la sintáxis <literal>SELECT FOR UPDATE</literal>.
|
||||||
|
Esta API se discute más adelante en este capítulo:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Comenzamos la discusión del control de concurrencia en Hibernate con la granularidad
|
||||||
|
de <literal>Configuration</literal>, <literal>SessionFactory</literal>, y
|
||||||
|
<literal>Session</literal>, así como la base de datos y las transacciones de aplicación
|
||||||
|
largas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="transactions-basics">
|
||||||
|
<title>Ámbitos de sesión y de transacción</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <literal>SessionFactory</literal> es un objeto seguro entre hebras caro-de-crear
|
||||||
|
pensado para ser compartido por todas las hebras de la aplicación. Es creado una sola vez,
|
||||||
|
usualmente en el arranque de la aplicación, a partir de una instancia de <literal>Configuration</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una <literal>Session</literal> es un objeto barato, inseguro entre hebras que debe
|
||||||
|
ser usado una sola vez, para un solo proceso de negocio, una sola unidad de trabajo,
|
||||||
|
y luego descartado. Una <literal>Session</literal> no obtendrá una <literal>Connection</literal>
|
||||||
|
JDBC (o un <literal>Datasource</literal>) a menos que sea necesario, de modo que puedas
|
||||||
|
abrir y cerrar seguramente una <literal>Session</literal> incluso si no estás seguro
|
||||||
|
que se necesitará acceso a los datos para servir una petición en particular. (Esto se
|
||||||
|
vuelve importante en cuanto estés implementando alguno de los siguientes patrones usando
|
||||||
|
intercepción de peticiones).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para completar este cuadro tienes que pensar también en las transacciones de base de
|
||||||
|
datos. Una transacción de base de datos tiene que ser tan corta como sea posible, para
|
||||||
|
reducir la contención de bloqueos en la base de datos. Las transacciones largas de base de
|
||||||
|
datos prevendrán a tu aplicación de escalar a una carga altamente concurrente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
¿Qué es el ámbito de una unidad de trabajo? ¿Puede una sola <literal>Session</literal> de Hibernate
|
||||||
|
extenderse a través de varias transacciones de base de datos o es ésta una relación uno-a-uno
|
||||||
|
de ámbitos? ¿Cuándo debes abrir y cerrar una <literal>Session</literal> y cómo demarcas los
|
||||||
|
límites de la transacción de base de datos?
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="transactions-basics-uow">
|
||||||
|
<title>Unidad de trabajo</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Primero, no uses el antipatrón <emphasis>sesión-por-operación</emphasis>, esto es,
|
||||||
|
¡no abras y cierres una <literal>Session</literal> para cada simple llamada a la base
|
||||||
|
de datos en una sola hebra! Por supuesto, lo mismo es verdad para transacciones de base de
|
||||||
|
datos. Las llamadas a base de datos en una aplicación se hacen usando una secuencia
|
||||||
|
prevista, que están agrupadas dentro de unidades de trabajo atómicas. (Nota que esto
|
||||||
|
también significa que el auto-commit después de cada una de las sentencias SQL es inútil
|
||||||
|
en una aplicación, este modo está pensado para trabajo ad-hoc de consola SQL.
|
||||||
|
Hibernate deshabilita, o espera que el servidor de aplicaciones lo haga, el modo
|
||||||
|
auto-commit inmediatamente.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El patrón más común en una aplicación mutiusuario cliente/servidor es
|
||||||
|
<emphasis>sesión-por-petición</emphasis>. En este modelo, una petición del cliente
|
||||||
|
es enviada al servidor (en donde se ejecuta la capa de persistencia de Hibernate),
|
||||||
|
se abre una nueva <literal>Session</literal> de Hibernate, y todas las operaciones
|
||||||
|
de base de datos se ejecutan en esta unidad de trabajo. Una vez completado el trabajo
|
||||||
|
(y se ha preparado la respuesta para el cliente) la sesión es limpiada y cerrada.
|
||||||
|
Podrías usar una sola transacción de base de datos para servir a petición del cliente,
|
||||||
|
comenzándola y comprometiéndola cuando abres y cierras la <literal>Session</literal>.
|
||||||
|
La relación entre las dos es uno-a-uno y este modelo es a la medida perfecta de muchas
|
||||||
|
aplicaciones.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El desafío yace en la implementación: no sólo tienen que comenzarse y terminarse correctamente
|
||||||
|
la <literal>Session</literal> y la transacción, sino que además tienen que estar accesibles
|
||||||
|
para las operaciones de acceso a datos. La demarcación de una unidad de trabajo se implementa
|
||||||
|
idealmente usando un interceptor que se ejecuta cuando una petición llama al servidor y anter que
|
||||||
|
la respuesta sea enviada (es decir, un <literal>ServletFilter</literal>). Recomendamos ligar la
|
||||||
|
<literal>Session</literal> a la hebra que atiende la petición, usando una variable
|
||||||
|
<literal>ThreadLocal</literal>. Esto permite un fácil acceso (como acceder a una variable static)
|
||||||
|
en tódo el código que se ejecuta en esta hebra. Dependiendo del mecanismo de demarcación de
|
||||||
|
transacciones de base de datos que elijas, podrías mantener también el contexto de la transacción
|
||||||
|
en una variable <literal>ThreadLocal</literal>. Los patrones de implementación para esto son
|
||||||
|
conocidos como <emphasis>Sesión Local de Hebra (ThreadLocal Session)</emphasis> y
|
||||||
|
<emphasis>Sesión Abierta en Vista (Open Session in View)</emphasis>. Puedes extender fácilmente
|
||||||
|
la clase de ayuda <literal>HibernateUtil</literal> mostrada anteriormente para encontrar
|
||||||
|
una forma de implementar un interceptor e instalarlo en tu entorno. Ver el sitio web de Hibernate
|
||||||
|
para consejos y ejemplos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-basics-apptx">
|
||||||
|
<title>Transacciones de aplicación</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El patrón sesión-por-petición no es el único concepto útil que puedes usar para diseñar unidades
|
||||||
|
de trabajo. Muchos procesos de negocio requiere una serie completa de interacciones con el
|
||||||
|
usuario intercaladas con accesos a base de datos. En aplicaciones web y de empresa no es aceptable
|
||||||
|
que una transacción de base de datos se extienda a través de la interacción de un usuario.
|
||||||
|
Considera el siguiente ejemplo:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Se abre la primera pantalla de un diálogo, los datos vistos por el usuario han sido
|
||||||
|
cargados en una <literal>Session</literal> y transacción de base de datos particular.
|
||||||
|
El usuario es libre de modificar los objetos.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
El usuario hace click en "Salvar" después de 5 minutos y espera que sus modificaciones
|
||||||
|
sean hechas persistentes. También espera que él sea la única persona editando esta
|
||||||
|
información y que no puede ocurrir ninguna modificación en conflicto.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Llamamos a esto unidad de trabajo, desde el punto de vista del usuario, una larga
|
||||||
|
<emphasis>transacción de aplicación</emphasis> ejecutándose. Hay muchas formas en
|
||||||
|
que puedes implementar esto en tu aplicación.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una primera implementación ingenua podría mantener abierta la <literal>Session</literal>
|
||||||
|
y la transacción de base de datos durante el tiempo de pensar del usuario, con bloqueos
|
||||||
|
tomados en la base de datos para prevenir la modificación concurrente, y para garantizar
|
||||||
|
aislamiento y atomicidad. Esto es, por supuesto, un antipatrón, ya que la contención de
|
||||||
|
bloqueo no permitiría a la aplicación escalar con el número de usuarios concurrentes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Claramente, tenemos que usar muchas transacciones de base de datos para implementar la transacción
|
||||||
|
de aplicación. En este caso, mantener el aislamiento de los procesos de negocio se vuelve una
|
||||||
|
responsabilidad parcial de la capa de aplicación. Una sola transacción de aplicación usualmente
|
||||||
|
abarca varias transacciones de base de datos. Será atómica si sólo una de estas transacciones de
|
||||||
|
base de datos (la última) almacena los datos actualizados, todas las otras simplemente leen datos
|
||||||
|
(por ejemplo, en un diálogo estilo-asistente abarcando muchos ciclos petición/respuesta).
|
||||||
|
Esto es más fácil de implementar de lo que suena, especialmente si usas las funcionalidades de
|
||||||
|
Hibernate:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Versionado Automático</emphasis> - Hibernate puede llevar un control automático de
|
||||||
|
concurrencia optimista por ti, puede detectar automáticamente si una modificación concurrente
|
||||||
|
ha ocurrido durante el tiempo de pensar del usuario.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Objetos Separados</emphasis> - Si decides usar el ya discutido patrón
|
||||||
|
de <emphasis>sesión-por-petición</emphasis>, todas las instancias cargadas estarán
|
||||||
|
en estado separado durante el tiempo de pensar del usuario. Hibernate te permite
|
||||||
|
volver a unir los objetos y hacer persistentes las modificaciones. El patrón se
|
||||||
|
llama <emphasis>sesión-por-petición-con-objetos-separados</emphasis>. Se usa
|
||||||
|
versionado automático para aislar las modificaciones concurrentes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Sesión Larga</emphasis> - La <literal>Session</literal> de Hibernate puede ser
|
||||||
|
desconectada de la conexión JDBC subyacente después que se haya sido comprometida la
|
||||||
|
transacción de base de datos, y reconectada cuando ocurra una nueva petición del cliente.
|
||||||
|
Este patrón es conocido como <emphasis>sesión-por-transacción-de-aplicación</emphasis>
|
||||||
|
y hace la re-unión innecesaria. Para aislar las modificaciones concurrentes se usa el
|
||||||
|
versionado automático.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Tanto <emphasis>sesión-por-petición-con-objetos-separados</emphasis> como
|
||||||
|
<emphasis>sesión-por-transacción-de-aplicación</emphasis>, ambas tienen
|
||||||
|
ventajas y desventajas, las discutimos más adelante en este capítulo en el contexto
|
||||||
|
del control optimista de concurrencia.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-basics-identity">
|
||||||
|
<title>Considerando la identidad del objeto</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una aplicación puede acceder concurrentemente a el mismo estado persistente en dos
|
||||||
|
<literal>Session</literal>s diferentes. Sin embargo, una instancia de una clase
|
||||||
|
persistente nunca se comparte entre dos instancias de <literal>Session</literal>.
|
||||||
|
Por lo tanto existen dos nociones diferentes de identidad:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identidad de Base de Datos</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>foo.getId().equals( bar.getId() )</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identidad JVM</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>foo==bar</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Entonces para objetos unidos a una <literal>Session</literal> <emphasis>en particular</emphasis>
|
||||||
|
(es decir en el ámbito de una <literal>Session</literal>) las dos nociones son equivalentes, y
|
||||||
|
la identidad JVM para la identidad de base de datos está garantizada por Hibernate. Sin embargo,
|
||||||
|
mientras la aplicación acceda concurrentemente al "mismo" (identidad persistente) objeto de negocio
|
||||||
|
en dos sesiones diferentes, las dos instancias serán realmente "diferentes" (identidad JVM).
|
||||||
|
Los conflictos se resuelven (con versionado automático) en tiempo de limpieza (flush) usando un
|
||||||
|
enfoque optimista.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este enfoque deja que Hibernate y la base de datos se preocupen sobre la concurrencia. Además
|
||||||
|
provee la mejor escalabilidad, ya que garantizando la identidad un unidades de trabajo monohebra
|
||||||
|
no se necesitan bloqueos caros u otros medios de sincronización. La aplicación nunca necesita
|
||||||
|
sincronizar sobre ningún objeto de negocio, siempre que se apegue a una sola hebra por
|
||||||
|
<literal>Session</literal>. Dentro de una <literal>Session</literal> la aplicación puede usar
|
||||||
|
con seguridad <literal>==</literal> para comparar objetos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, una aplicación que usa <literal>==</literal> fuera de una <literal>Session</literal>,
|
||||||
|
podría ver resultados inesperados. Esto podría ocurrir incluso en sitios algo inesperados,
|
||||||
|
por ejemplo, si pones dos instancias separadas dentro del mismo <literal>Set</literal>.
|
||||||
|
Ambas podrían tener la misma identidad de base de datos (es decir, representar la misma fila),
|
||||||
|
pero la identidad JVM, por definición, no está garantizada para las instancias en estado separado.
|
||||||
|
El desarrollador tiene que sobrescribir los métodos <literal>equals()</literal> y
|
||||||
|
<literal>hashCode()</literal> en las clases persistentes e implementar su propia noción de igualdad
|
||||||
|
de objetos. Hay una advertencia: Nunca uses el identificador de base de datos para implementar
|
||||||
|
la igualdad, usa una clave de negocio, una combinación de atributos únicos, usualmente inmutables.
|
||||||
|
El identificador de base de datos cambiará si un objeto transitorio es hecho persistente.
|
||||||
|
Si la instancia transitoria (usualmente junta a instancias separadas) es mantenida en un
|
||||||
|
<literal>Set</literal>, cambiar el código hash rompe el contrato del <literal>Set</literal>.
|
||||||
|
Los atributos para las claves de negocio no tienen que ser tan estables como las claves primarias
|
||||||
|
de base de datos, sólo tienes que garantizar estabilidad en tanto los objetos estén en el mismo
|
||||||
|
<literal>Set</literal>. Mira el sitio web de Hibernate para una discusión más cuidadosa de este
|
||||||
|
tema. Nota también que éste no es un tema de Hibernate, sino simplemente cómo la identidad y la igualdad
|
||||||
|
de los objetos Java tiene que ser implementada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-basics-issues">
|
||||||
|
<title>Temas comunes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nunca uses los antipatrones <emphasis>sesión-por-sesión-de-usuario</emphasis> o
|
||||||
|
<emphasis>sesión-por-aplicación</emphasis> (por supuesto, hay raras excepciones a esta
|
||||||
|
regla). Nota que algunis de los siguientes temas podrían también aparecer con los patrones
|
||||||
|
recomendados. Asegúrate que entiendes las implicaciones antes de tomar una decisión de
|
||||||
|
diseño:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Una <literal>Session</literal> no es segura entre hebras. Las cosas que se suponen
|
||||||
|
que funcionan concurrentemente, como peticiones HTTP, beans de sesión, o workers de
|
||||||
|
Swing, provocarán condiciones de competencia si una instancia de <literal>Session</literal>
|
||||||
|
fuese compartida. Si guardas tu <literal>Session</literal> de Hibernate en tu
|
||||||
|
<literal>HttpSession</literal> (discutido más adelante), debes considerar sincronizar
|
||||||
|
el acceso a tu sesión HTTP. De otro modo, un usuario que hace click lo suficientemente
|
||||||
|
rápido puede llegar a usar la misma <literal>Session</literal> en dos hebras ejecutándose
|
||||||
|
concurrentemente.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Una excepción lanzada por Hibernate significa que tienes que deshacer (rollback) tu
|
||||||
|
transacción de base de datos y cerrar la <literal>Session</literal> inmediatamente
|
||||||
|
(discutido en más detalle luego). Si tu <literal>Session</literal> está ligada a la
|
||||||
|
aplicación, tienes que parar la aplicación. Deshacer (rollback) la transacción de base
|
||||||
|
de datos no pone a tus objetos de vuelta al estado en que estaban al comienzo de la
|
||||||
|
transacción. Esto significa que el estado de la base de datos y los objetos de negocio
|
||||||
|
quedan fuera de sincronía. Usualmente esto no es un problema, pues las excepciones no
|
||||||
|
son recuperables y tienes que volver a comenzar después del rollback de todos modos.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La <literal>Session</literal> pone en caché todo objeto que esté en estado persistente
|
||||||
|
(vigilado y chequeado por estado sucio por Hibernate). Esto significa que crece sin
|
||||||
|
fin hasta que obtienes una OutOfMemoryException, si la mantienes abierta por un largo
|
||||||
|
tiempo o simplemente cargas demasiados datos. Una solución para esto es llamar a
|
||||||
|
<literal>clear()</literal> y <literal>evict()</literal> para gestionar el caché de la
|
||||||
|
<literal>Session</literal>, pero probalemente debas considerar un procedimiento almacenado
|
||||||
|
si necesitas operaciones de datos masivas. Se muestran algunas soluciones en
|
||||||
|
<xref linkend="batch"/>. Mantener una <literal>Session</literal> abierta por la duración
|
||||||
|
de una sesión de usuario significa también una alta probabilidad de datos añejos.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="transactions-demarcation">
|
||||||
|
<title>Demarcación de la transacción de base de datos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los límites de las transacciones de base de datos (o sistema) son siempre necesarios. Ninguna comunicación
|
||||||
|
con la base de datos puede darse fuera de una transacción de base de datos (esto parece confundir muchos
|
||||||
|
desarrolladores acostumbrados al modo auto-commit). Siempre usa límites de transacción claros, incluso
|
||||||
|
para las operaciones de sólo lectura. Dependiendo del nivel de aislamiento y las capacidades de base de
|
||||||
|
datos, esto podría o no ser requerido, pero no hay un merma si siempre demarcas explícitamente
|
||||||
|
las transacciones.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una aplicación Hibernate puede ejecutarse en entornos no manejados (es decir, como independiente,
|
||||||
|
Web simple, o aplicaciones Swing) y entornos manejados J2EE. En un entorno no manejado, Hibernate es
|
||||||
|
usualmente responsable de su propio pool de conexiones de base de datos. El desarrollador de aplicaciones
|
||||||
|
tiene que establecer manualmente los límites de transacción, en otras palabras, hacer begin, commit, o
|
||||||
|
rollback las transacciones de base de datos por sí mismo. Un entorno manejado usualmente provee transacciones
|
||||||
|
gestionadas por contenedor, con el ensamble de transacción definido declarativamente en descriptores de
|
||||||
|
despliegue de beans de sesión EJB, por ejemplo. La demarcación programática de transacciones no es más
|
||||||
|
necesario, incluso limpiar (flush) la <literal>Session</literal> es hecho automáticamente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Sin embargo, frecuentemente es deseable mantener portable tu capa de persistencia. Hibernate ofrece
|
||||||
|
una API de envoltura llamada <literal>Transaction</literal> que se traduce al sistema de transacciones
|
||||||
|
nativo de tu entorno de despliegue. Esta API es realmente opcional, pero recomendamos fuertemente su uso
|
||||||
|
salvo que estés en un bean de sesión CMT.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Usualmente, finalizar una <literal>Session</literal> implica cuatro fases distintas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
limpiar (flush) la sesión
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
comprometer la transacción
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
cerrar la sesión
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
manejar excepciones
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Limpiar la sesión ha sido discutido anteriormente, tendremos ahora una mirada más de cerca
|
||||||
|
a la demarcación de transacciones y manejo de excepciones en sendos entornos manejado y no manejados.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
|
||||||
|
<sect2 id="transactions-demarcation-nonmanaged">
|
||||||
|
<title>Entorno no manejado</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si una capa de persistencia Hibernate se ejecuta en un entorno no manejado, las conexiones
|
||||||
|
de base de datos son manejadas usualmente por el mecanismo de pooling de Hibernate. El idioma
|
||||||
|
manejo de sesión/transacción se ve así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[//Non-managed environment idiom
|
||||||
|
Session sess = factory.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = sess.beginTransaction();
|
||||||
|
|
||||||
|
// do some work
|
||||||
|
...
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
if (tx != null) tx.rollback();
|
||||||
|
throw e; // or display error message
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
sess.close();
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
No tienes que limpiar con <literal>flush()</literal> la <literal>Session</literal> explícitamente -
|
||||||
|
la llamada a <literal>commit()</literal> automáticamente dispara la sincronización.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una llamada a <literal>close()</literal> marca el fin de una sesión. La principal implicación
|
||||||
|
de <literal>close()</literal> es que la conexión JDBC será abandonada por la sesión.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este código Java es portable y se ejecuta tanto en entornos no manejados como en entornos JTA.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Muy probablemente nunca veas este idioma en código de negocio en una aplicación normal;
|
||||||
|
las excepciones fatales (sistema) deben siempre ser capturadas en la "cima". En otras palabras,
|
||||||
|
el código que ejecuta las llamadas de Hibernate (en la capa de persistencia) y el código que
|
||||||
|
maneja <literal>RuntimeException</literal> (y usualmente sólo puede limpiar y salir) están en
|
||||||
|
capas diferentes. Esto puede ser un desafío de diseñarlo tú mismo y debes usar los servicios
|
||||||
|
de contenedor J2EE/EJB en cuanto estuviesen disponibles. El manejo de excepciones se dicute
|
||||||
|
más adelante en este capítulo.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que debes seleccionar <literal>org.hibernate.transaction.JDBCTransactionFactory</literal>
|
||||||
|
(que es el por defecto).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-demarcation-jta">
|
||||||
|
<title>Usando JTA</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si tu capa de persistencia se ejecuta en un servidor de aplicaciones (por ejemplo, detrás
|
||||||
|
de beans de sesión EJB), cada conexión de datasource obtenida por Hibernate será parte
|
||||||
|
automáticamente de la transacción JTA global. Hibernate ofrece dos estrategias para esta
|
||||||
|
integración.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si usas transacciones gestionadas-por-bean (BMT) Hibernate le dirá al servidor de aplicaciones
|
||||||
|
que comience y finalice una transacción BMT si usas la API de <literal>Transaction</literal>.
|
||||||
|
De modo que, el código de gestión de la transacción es idéntico al de un entorno no manejado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[// BMT idiom
|
||||||
|
Session sess = factory.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = sess.beginTransaction();
|
||||||
|
|
||||||
|
// do some work
|
||||||
|
...
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
if (tx != null) tx.rollback();
|
||||||
|
throw e; // or display error message
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
sess.close();
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Con CMT, la demarcación de la transacción se hace en descriptores de despliegue de beans de sesión,
|
||||||
|
no programáticamente. Si no quieres limpiar (flush) y cerrar manualmente la <literal>Session</literal>
|
||||||
|
por ti mismo, solamente establece <literal>hibernate.transaction.flush_before_completion</literal> a
|
||||||
|
<literal>true</literal>, <literal>hibernate.connection.release_mode</literal> a
|
||||||
|
<literal>after_statement</literal> o <literal>auto</literal> y
|
||||||
|
<literal>hibernate.transaction.auto_close_session</literal> a <literal>true</literal>. Hibernate
|
||||||
|
limpiará y cerrará entonces automáticamente la <literal>Session</literal> para ti. Lo único que resta
|
||||||
|
es deshacer (rollback) la transacción cuando ocurra una excepción. Afortunadamente, en un bean CMT,
|
||||||
|
incluso esto ocurre automáticamente, ya que una <literal>RuntimeException</literal> no manejada
|
||||||
|
disparada por un método de un bean de sesión le dice al contenedor que ponga a deshacer la transacción
|
||||||
|
global. <emphasis>Esto significa que, en CMT, no necesitas usar en absoluto la API de
|
||||||
|
<literal>Transaction</literal> de Hibernate.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota que debes elegir <literal>org.hibernate.transaction.JTATransactionFactory</literal> en un
|
||||||
|
bean de sesión BMT, y <literal>org.hibernate.transaction.CMTTransactionFactory</literal> en un
|
||||||
|
bean de sesión CMT, cuando configures la fábrica de transacciones de Hibernate. Recuerda además
|
||||||
|
establecer <literal>org.hibernate.transaction.manager_lookup_class</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si trabajas en un entorno CMT, y usas limpieza (flushing) y cierre automáticos de la sesión,
|
||||||
|
podrías querer también usar la misma sesión en diferentes partes de tu código. Típicamente,
|
||||||
|
en un entorno no manejado, usarías una variable <literal>ThreadLocal</literal> para tener la sesión,
|
||||||
|
pero una sola petición de EJB puede ejecutarse en diferentes hebras (por ejemplo, un bean de sesión
|
||||||
|
llamando a otro bean de sesión). Si no quieres molestarte en pasar tu <literal>Session</literal>
|
||||||
|
por alrededor, la <literal>SessionFactory</literal> provee el método
|
||||||
|
<literal>getCurrentSession()</literal>, que devuelve una sesión que está pegada al contexto de
|
||||||
|
transacción JTA. ¡Esta es la forma más fácil de integrar Hibernate en una aplicación!
|
||||||
|
La sesión "actual" siempre tiene habilitados limpieza, cierre y liberación de conexión automáticos
|
||||||
|
(sin importar la configuración de las propiedades anteriores). Nuestra idioma de gestión de
|
||||||
|
sesión/transacción se reduce a:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[// CMT idiom
|
||||||
|
Session sess = factory.getCurrentSession();
|
||||||
|
|
||||||
|
// do some work
|
||||||
|
...
|
||||||
|
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En otras palabras, todo lo que tienes que hacer en un entorno manejado, es llamar a
|
||||||
|
<literal>SessionFactory.getCurrentSession()</literal>, hacer tu trabajo de acceso a datos,
|
||||||
|
y dejar el resto al contenedor. Los límites de transacción se establecen declarativamente
|
||||||
|
en los descriptores de despliegue de tu bean de sesión. El ciclo de vida de la sesión es
|
||||||
|
manejado completamente por Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Existe una advertencia al uso del modo de liberación de conexión <literal>after_statement</literal>.
|
||||||
|
Debido a una limitación tonta de la especificación de JTA, no es posible para Hibernate
|
||||||
|
limpiar automáticamente ningún <literal>ScrollableResults</literal> no cerrado ni
|
||||||
|
instancias de <literal>Iterator</literal> devueltas por <literal>scroll()</literal> o
|
||||||
|
<literal>iterate()</literal>. <emphasis>Debes</emphasis> liberar el cursor de base de datos
|
||||||
|
subyacente llamando a <literal>ScrollableResults.close()</literal> o
|
||||||
|
<literal>Hibernate.close(Iterator)</literal> explícitamente desde un bloque <literal>finally</literal>.
|
||||||
|
(Por supuesto, la mayoría de las aplicaciones pueden evitarlo fácilmente no usando en absoluto ningún
|
||||||
|
<literal>scroll()</literal> o <literal>iterate()</literal> desde el código CMT.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-demarcation-exceptions">
|
||||||
|
<title>Manejo de excepciones</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si la <literal>Session</literal> lanza una excepción (incluyendo cualquier
|
||||||
|
<literal>SQLException</literal>), debes inmediatamente deshacer (rollback) la
|
||||||
|
transacción de base de datos, llamar a <literal>Session.close()</literal> y
|
||||||
|
descartar la instancia de <literal>Session</literal>. Ciertos métodos de
|
||||||
|
<literal>Session</literal> <emphasis>no</emphasis> dejarán la sesión en un
|
||||||
|
estado consistente. Ninguna excepción lanzada por Hibernate puede ser tratada
|
||||||
|
como recuperable. Asegúrate que la <literal>Session</literal> sea cerrada llamando
|
||||||
|
a <literal>close()</literal> en un bloque <literal>finally</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La <literal>HibernateException</literal>, que envuelve la mayoría de los errores que
|
||||||
|
pueden ocurrir en la capa de persistencia de Hibernate, en una excepción no chequeada
|
||||||
|
(no lo era en versiones anteriores de Hibernate). En nuestra opinión, no debemos forzar
|
||||||
|
al desarrollador de aplicaciones a capturar una excepción irrecuperable en una capa baja.
|
||||||
|
En la mayoría de los sistemas, las excepciones no chequeadas y fatales son manejadas
|
||||||
|
en uno de los primeros cuadros de la pila de llamadas a métodos (es decir, en las capas
|
||||||
|
más altas) y se presenta un mensaje de error al usuario de la aplicación (o se toma alguna
|
||||||
|
otra acción apropiada). Nota que Hibernate podría también lanzar otras excepciones no chequeadas
|
||||||
|
que no sean una <literal>HibernateException</literal>. Una vez más, no son recuperables y debe
|
||||||
|
tomarse una acción apropiada.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate envuelve <literal>SQLException</literal>s lanzadas mientras se interactúa con la base
|
||||||
|
de datos en una <literal>JDBCException</literal>. De hecho, Hibernate intentará convertir la excepción
|
||||||
|
en una subclase de <literal>JDBCException</literal> más significativa. La <literal>SQLException</literal>
|
||||||
|
está siempre disponible vía <literal>JDBCException.getCause()</literal>. Hibernate convierte la
|
||||||
|
<literal>SQLException</literal> en una subclase de <literal>JDBCException</literal> apropiada usando
|
||||||
|
el <literal>SQLExceptionConverter</literal> adjunto a la <literal>SessionFactory</literal>. Por defecto,
|
||||||
|
el <literal>SQLExceptionConverter</literal> está definido para el dialecto configurado; sin embargo,
|
||||||
|
es también posible enchufar una implementación personalizada (ver los javadocs de la clase
|
||||||
|
<literal>SQLExceptionConverterFactory</literal> para los detalles). Los subtipos estándar de
|
||||||
|
<literal>JDBCException</literal> son:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>JDBCConnectionException</literal> - indica un error con la comunicación JDBC subyacente.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>SQLGrammarException</literal> - indica un problema de gramática o sintáxis con el
|
||||||
|
SQL publicado.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>ConstraintViolationException</literal> - indica alguna forma de violación de restricción
|
||||||
|
de integridad.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockAcquisitionException</literal> - indica un error adquiriendo un nivel de bloqueo
|
||||||
|
necesario para realizar una operación solicitada.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>GenericJDBCException</literal> - una excepción genérica que no cayó en ninguna de las
|
||||||
|
otras categorías.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="transactions-optimistic">
|
||||||
|
<title>Control optimista de concurrencia</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El único enfoque que es consistente con alta concurrencia y alta escalabilidad es el control
|
||||||
|
optimista de concurrencia con versionamiento. El chuequeo de versión usa números de versión,
|
||||||
|
o timestamps, para detectar actualizaciones en conflicto (y para prevenir actualizaciones perdidas).
|
||||||
|
Hibernate provee para tres enfoques posibles de escribir código de aplicación que use concurrencia
|
||||||
|
optimista. Los casos de uso que hemos mostrado están en el contexto de transacciones de aplicación
|
||||||
|
largas pero el chequeo de versiones tiene además el beneficio de prevenir actualizaciones perdidas
|
||||||
|
en transacciones de base de datos solas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="transactions-optimistic-manual">
|
||||||
|
<title>Chequeo de versiones de aplicación</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En una implementación sin mucha ayuda de Hibernate, cada interacción con la base de datos ocurre en una
|
||||||
|
nueva <literal>Session</literal> y el desarrollador es responsable de recargar todas las intancias
|
||||||
|
persistentes desde la base de datos antes de manipularlas. Este enfoque fuerza a la aplicación a
|
||||||
|
realizar su propio chequeo de versiones para asegurar el aislamiento de transacciones de base de datos.
|
||||||
|
Es el enfoque más similar a los EJBs de entidad.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
||||||
|
session = factory.openSession();
|
||||||
|
Transaction t = session.beginTransaction();
|
||||||
|
int oldVersion = foo.getVersion();
|
||||||
|
session.load( foo, foo.getKey() ); // load the current state
|
||||||
|
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
|
||||||
|
foo.setProperty("bar");
|
||||||
|
t.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La propiedad <literal>version</literal> se mapea usando <literal><version></literal>,
|
||||||
|
e Hibernate la incrementará automáticamente durante la limpieza si la entidad está sucia.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Por supuesto, si estás operando un entorno de baja-concurrencia-de-datos y no requieres
|
||||||
|
chequeo de versiones, puedes usar este enfoque y simplemente saltar el chequeo de versiones.
|
||||||
|
En ese caso, <emphasis>el último compromiso (commit) gana</emphasis> será la estrategia por
|
||||||
|
defecto para tus transacciones de aplicación largas. Ten en mente que esto podría confundir
|
||||||
|
a los usuarios de la aplicación, pues podrían experimentar actualizaciones perdidas sin
|
||||||
|
mensajes de error ni chance de fusionar los cambios conflictivos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Claramente, el chequeo manual de versiones es factible solamente en circunstancias muy triviales,
|
||||||
|
y no es práctico para la mayoría de aplicaciones. Frecuentemente, no sólo intancias solas, sino grafos
|
||||||
|
completos de objetos modificados tienen que ser chequeados. Hibernate ofrece chequeo de versiones
|
||||||
|
automático con el paradigma de diseño de <literal>Session</literal> larga o de instancias separadas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-optimistic-longsession">
|
||||||
|
<title>Sesión larga y versionado automático</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Una sola instancia de <literal>Session</literal> y sus instancias persistentes
|
||||||
|
son usadas para toda la transacción de aplicación. Hibernate chequea las versiones
|
||||||
|
de instancia en el momento de limpieza (flush), lanzando una excepción si se detecta
|
||||||
|
una modificación concurrente. Concierne al desarrollador capturar y manejar esta excepción
|
||||||
|
(las opciones comunes son la oportunidad del usuario de fusionar los cambios, o recomenzar el
|
||||||
|
proceso de negocio sin datos añejos).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La <literal>Session</literal> se desconecta de cualquier conexión JDBC subyacente
|
||||||
|
al esperar por una interacción del usuario. Este enfoque es el más eficiente en términos
|
||||||
|
de acceso a base de datos. La aplicación no necesita tratar por sí misma con el chequeo de
|
||||||
|
versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cada
|
||||||
|
transacción de base de datos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session
|
||||||
|
session.reconnect(); // Obtain a new JDBC connection
|
||||||
|
Transaction t = session.beginTransaction();
|
||||||
|
foo.setProperty("bar");
|
||||||
|
t.commit(); // End database transaction, flushing the change and checking the version
|
||||||
|
session.disconnect(); // Return JDBC connection ]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
El objeto <literal>foo</literal> todavía conoce en qué <literal>Session</literal> fue cargado.
|
||||||
|
<literal>Session.reconnect()</literal> obtiene una nueva conexión (o puedes proveer una) y
|
||||||
|
reasume la sesión. El método <literal>Session.disconnect()</literal> desconectará la sesión
|
||||||
|
de la conexión JDBC y la devolverá la conexión al pool (a menos que hayas provisto la conexión).
|
||||||
|
Después de la reconexión, para forzar un chequeo de versión en datos que no estés actualizando,
|
||||||
|
puedes llamar a <literal>Session.lock()</literal> con <literal>LockMode.READ</literal> sobre
|
||||||
|
cualquier objeto que pudiese haber sido actualizado por otra transacción. No necesitas bloquear
|
||||||
|
ningún dato que <emphasis>sí estés</emphasis> actualizando.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si las llamadas explícitas a <literal>disconnect()</literal> y <literal>reconnect()</literal>
|
||||||
|
son muy onerosas, puedes usar en cambio <literal>hibernate.connection.release_mode</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este patrón es problemático si la <literal>Session</literal> es demasiado grande para ser almacenada
|
||||||
|
durante el tiempo de pensar del usuario, por ejemplo, una <literal>HttpSession</literal> debe
|
||||||
|
mantenerse tan pequeña como sea posible. Ya que la <literal>Session</literal> es también el caché
|
||||||
|
(obligatorio) de primer nivel y contiene todos los objetos cargados, podemos probablemente cargar
|
||||||
|
esta estrategia sólo para unos pocos ciclos petición/respuesta. Esto está de hecho recomendado, ya que
|
||||||
|
la <literal>Session</literal> tendrá pronto también datos añejos.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nota también que debes mantener la <literal>Session</literal> desconectada próxima a la capa
|
||||||
|
de persistencia. En otras palabras, usa una sesión de EJB con estado para tener la
|
||||||
|
<literal>Session</literal> y no transferirla a la capa web para almacenarla en la
|
||||||
|
<literal>HttpSession</literal> (ni incluso serializarla a una capa separada).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-optimistic-detached">
|
||||||
|
<title>Objetos separados y versionado automático</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Cada interacción con el almacén persistente ocurre en una nueva <literal>Session</literal>.
|
||||||
|
Sin embargo, las mismas instancias persistentes son reusadas para cada interacción con la base de
|
||||||
|
datos. La aplicación manipula el estado de las instancias separadas originalmente cargadas en otra
|
||||||
|
<literal>Session</literal> y luego las readjunta usando <literal>Session.update()</literal>,
|
||||||
|
<literal>Session.saveOrUpdate()</literal>, o <literal>Session.merge()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
||||||
|
foo.setProperty("bar");
|
||||||
|
session = factory.openSession();
|
||||||
|
Transaction t = session.beginTransaction();
|
||||||
|
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
|
||||||
|
t.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
De nuevo, Hibernate chequeará las versiones de instancia durante la limpieza (flush),
|
||||||
|
lanzando una excepción si ocurrieron actualizaciones en conflicto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes también llamar a <literal>lock()</literal> en vez de <literal>update()</literal>
|
||||||
|
y usar <literal>LockMode.READ</literal> (realizando un chequeo de versión, puenteando
|
||||||
|
todos los cachés) si estás seguro que el objeto no ha sido modificado.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="transactions-optimistic-customizing">
|
||||||
|
<title>Personalizando el versionado automático</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puedes deshabilitar el incremento de versión automático de Hibernate para propiedades en particular
|
||||||
|
y colecciones estableciendo el atributo de mapeo <literal>optimistic-lock</literal> a
|
||||||
|
<literal>false</literal>. Hibernate entonces no incrementará ya más las versiones si la propiedad está
|
||||||
|
sucia.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Los esquemas de base de datos heredados son frecuentemente estáticos y no pueden ser modificados.
|
||||||
|
U otras aplicaciones podrían también acceder la misma base de datos y no saber cómo manejar los números
|
||||||
|
de versión ni incluso timestamps. En ambos casos, el versionado no puede confiarse a una columna en
|
||||||
|
particular en una tabla. Para forzar un chequeo de versiones sin un mapeo de propiedad de versión o
|
||||||
|
timestamp, con una comparación del estado de todos los campos en una fila, activa
|
||||||
|
<literal>optimistic-lock="all"</literal> en el mapeo de <literal><class></literal>.
|
||||||
|
Nota que esto conceptualmente funciona solamente si Hibernate puede comparar el estado viejo y nuevo,
|
||||||
|
es decir, si usas una sola <literal>Session</literal> larga y no
|
||||||
|
sesión-por-petición-con-instancias-separadas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A veces las modificaciones concurrentes pueden permitirse, en cuanto los cambios que hayan sido
|
||||||
|
hechos no se traslapen. Si estableces <literal>optimistic-lock="dirty"</literal> al mapear la
|
||||||
|
<literal><class></literal>, Hibernate sólo comparará los campos sucios durante la limpieza.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En ambos casos, con columnas de versión/timestamp dedicadas o con comparación de campos
|
||||||
|
completa/sucios, Hibernate usa una sola sentencia <literal>UPDATE</literal>
|
||||||
|
(con una cláusula <literal>WHERE</literal> apropiada) por entidad para ejecutar el chequeo
|
||||||
|
de versiones y actualizar la información. Si usas persistencia transitiva para la re-unión
|
||||||
|
en cascada de entidades asociadas, Hibernate podría ejecutar actualizaciones innecesarias.
|
||||||
|
Esto usualmente no es un problema, pero podrían ejecutarse disparadores (triggers)
|
||||||
|
<emphasis>on update</emphasis> en la base de datos incluso cuando no se haya hecho ningún cambio
|
||||||
|
a las instancias separadas. Puedes personalizar este comportamiento estableciendo
|
||||||
|
<literal>select-before-update="true"</literal> en el mapeo de <literal><class></literal>,
|
||||||
|
forzando a Hibernate a <literal>SELECT</literal> la instancia para asegurar que las actualizaciones
|
||||||
|
realmente ocurran, antes de actualizar la fila.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="transactions-locking">
|
||||||
|
<title>Bloqueo pesimista</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
No se pretende que los usuarios gasten mucho tiempo preocupándose de las estrategias de bloqueo.
|
||||||
|
Usualmente es suficiente con especificar un nivel de aislamiento para las conexiones JDBC y entonces
|
||||||
|
simplemente dejar que la base de datos haga todo el trabajo. Sin embargo, los usuarios avanzados pueden
|
||||||
|
a veces obtener bloqueos exclusivos pesimistas, o reobtener bloqueos al comienzo de una nueva
|
||||||
|
transacción.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
¡Hibernate siempre usará el mecanismo de bloqueo de la base de datos, nunca bloqueo
|
||||||
|
de objetos en memoria!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La clase <literal>LockMode</literal> define los diferentes niveles de bloqueo que pueden ser adquiridos
|
||||||
|
por Hibernate. Un bloqueo se obtiene por los siguientes mecanismos:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockMode.WRITE</literal> se adquiere automáticamente cuando Hibernate actualiza o
|
||||||
|
inserta una fila.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockMode.UPGRADE</literal> puede ser adquirido bajo petición explícita del usuario
|
||||||
|
usando <literal>SELECT ... FOR UPDATE</literal> en base de datos que soporten esa sintáxis.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockMode.UPGRADE_NOWAIT</literal> puede ser adquirido bajo petición explícita del usuario
|
||||||
|
usando un <literal>SELECT ... FOR UPDATE NOWAIT</literal> bajo Oracle.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockMode.READ</literal> es adquirido automáticamente cuando Hibernate lee datos
|
||||||
|
bajo los niveles de aislamiento Repeatable Read o Serializable. Puede ser readquirido por
|
||||||
|
pedido explícito del usuario.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>LockMode.NONE</literal> representa la ausencia de un bloqueo. Todos los objetos se pasan
|
||||||
|
a este modo de bloqueo al final de una <literal>Transaction</literal>. Los objetos asociados con una
|
||||||
|
sesión vía una llamada a <literal>update()</literal> o <literal>saveOrUpdate()</literal> también
|
||||||
|
comienzan en este modo de bloqueo.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La "petición explícita del usuario" se expresa en una de las siguientes formas:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Una llamada a <literal>Session.load()</literal>, especificando un <literal>LockMode</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Una llamada a <literal>Session.lock()</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Una llamada a <literal>Query.setLockMode()</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si se llama a <literal>Session.load()</literal> con <literal>UPGRADE</literal> o
|
||||||
|
<literal>UPGRADE_NOWAIT</literal>, y el objeto pedido no ha sido aún cargado por la sesión, el objeto es
|
||||||
|
cargado usando <literal>SELECT ... FOR UPDATE</literal>. Si se llama a <literal>load()</literal> para
|
||||||
|
un objeto que ya esté cargado con un bloqueo menos restrictivo que el pedido, Hibernate llama a
|
||||||
|
<literal>lock()</literal> para ese objeto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Session.lock()</literal> realiza un chequeo de número de versión si el modo de bloqueo especificado
|
||||||
|
es <literal>READ</literal>, <literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>. (En el caso de
|
||||||
|
<literal>UPGRADE</literal> o <literal>UPGRADE_NOWAIT</literal>, se usa
|
||||||
|
<literal>SELECT ... FOR UPDATE</literal>.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si la base de datos no soporta el modo de bloqueo solicitado, Hibernate usará un modo alternativo
|
||||||
|
apropiado (en vez de lanzar una excepción). Esto asegura que las aplicaciones serán portables.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
<chapter id="xml">
|
||||||
|
<title>Mapeo XML</title>
|
||||||
|
|
||||||
|
<para><emphasis>
|
||||||
|
Nota que esta es una funcionalidad experimental en Hibernate 3.0 y está
|
||||||
|
bajo un desarrollo extremadamente activo.
|
||||||
|
</emphasis></para>
|
||||||
|
|
||||||
|
<sect1 id="xml-intro" revision="1">
|
||||||
|
<title>Trabajando con datos XML</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate te permite trabajar con datos XML persistentes en casi la misma forma
|
||||||
|
que trabajas con POJOs persistentes. Un árbol XML analizado (parsed) puede ser
|
||||||
|
pensado como sólo otra forma de representar los datos relacionales a nivel de objetos,
|
||||||
|
en vez de POJOs.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate soporta dom4j como API para manipular árboles XML. Puedes escribir
|
||||||
|
consultas que traigan árboles dom4j de la base de datos y tener cualquier modificación
|
||||||
|
que hagas al árbol sincronizada automáticamente a la base de datos. Puedes incluso tomar
|
||||||
|
un documento XML, analizarlo usando dom4j, y escribirlo a la base de datos con cualquiera
|
||||||
|
de las operaciones básicas de Hibernate: <literal>persist(), saveOrUpdate(), merge(),
|
||||||
|
delete(), replicate()</literal> (la fusión no está aún soportada).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Esta funcionalidad tiene muchas aplicaciones incluyendo la importación/exportación de datos,
|
||||||
|
externalización de datos de entidad vía JMS o SOAP y reportes basados en XSLT.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un solo mapeo puede ser usado para mapear simultáneamente las propiedades de una clase y los nodos de un
|
||||||
|
documento XML a la base de datos, o, si no hay ninguna clase a mapear, puede ser usado para mapear sólo
|
||||||
|
el XML.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect2 id="xml-intro-mapping">
|
||||||
|
<title>Especificando los mapeos de XML y de clase juntos</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
He aquí un ejemplo de mapear un POJO y XML simultáneamente:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Account"
|
||||||
|
table="ACCOUNTS"
|
||||||
|
node="account">
|
||||||
|
|
||||||
|
<id name="accountId"
|
||||||
|
column="ACCOUNT_ID"
|
||||||
|
node="@id"/>
|
||||||
|
|
||||||
|
<many-to-one name="customer"
|
||||||
|
column="CUSTOMER_ID"
|
||||||
|
node="customer/@id"
|
||||||
|
embed-xml="false"/>
|
||||||
|
|
||||||
|
<property name="balance"
|
||||||
|
column="BALANCE"
|
||||||
|
node="balance"/>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="xml-onlyxml">
|
||||||
|
<title>Especificando sólo un mapeo XML</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
He aquí un ejemplo donde no hay ninguna clase POJO:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class entity-name="Account"
|
||||||
|
table="ACCOUNTS"
|
||||||
|
node="account">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="ACCOUNT_ID"
|
||||||
|
node="@id"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<many-to-one name="customerId"
|
||||||
|
column="CUSTOMER_ID"
|
||||||
|
node="customer/@id"
|
||||||
|
embed-xml="false"
|
||||||
|
entity-name="Customer"/>
|
||||||
|
|
||||||
|
<property name="balance"
|
||||||
|
column="BALANCE"
|
||||||
|
node="balance"
|
||||||
|
type="big_decimal"/>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Este mapeo te permite acceder a los datos como un árbol dom4j, o como un grafo de pares nombre/valor de
|
||||||
|
propiedad (<literal>Map</literal>s de Java). Los nombres de propiedades son construcciones puramente
|
||||||
|
lógicas a las que se puede hacer referencia en consultas HQL.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="xml-mapping" revision="1">
|
||||||
|
<title>Mapeo de metadatos XML</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Muchos elementos de mapeo de Hibernate aceptan el atributo <literal>node</literal>. Esto te permite espcificar
|
||||||
|
el nombre de un atributo o elemento XML que contenga los datos de la propiedad o entidad. El formato del
|
||||||
|
atributo <literal>node</literal> debe ser uno de los siguientes:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"element-name"</literal> - mapea al elemento XML mencionado</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"@attribute-name"</literal> - mapea al atributo XML mencionado</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para><literal>"."</literal> - mapea al elemento padre</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>"element-name/@attribute-name"</literal> -
|
||||||
|
mapea al atributo mencionado del elemento mencionado
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Para las colecciones y asociaciones monovaluadas, existe un atributo adicional <literal>embed-xml</literal>.
|
||||||
|
Si <literal>embed-xml="true"</literal>, que es el valor por defecto, el árbol XML para la entidad
|
||||||
|
asociada (o colección de tipo de valor) será embebida directamente en el árbol XML para la entidad que
|
||||||
|
posee la asociación. En otro caso, si <literal>embed-xml="false"</literal>, sólo el valor identificador
|
||||||
|
referenciado aparecerá en el XML para asociaciones de punto único y para las colecciones simplemente
|
||||||
|
no aparecerá en absoluto.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
¡Debes ser cuidadoso de no dejar <literal>embed-xml="true"</literal> para demasiadas asociaciones,
|
||||||
|
ya que XML no trata bien la circularidad!
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Customer"
|
||||||
|
table="CUSTOMER"
|
||||||
|
node="customer">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="CUST_ID"
|
||||||
|
node="@id"/>
|
||||||
|
|
||||||
|
<map name="accounts"
|
||||||
|
node="."
|
||||||
|
embed-xml="true">
|
||||||
|
<key column="CUSTOMER_ID"
|
||||||
|
not-null="true"/>
|
||||||
|
<map-key column="SHORT_DESC"
|
||||||
|
node="@short-desc"
|
||||||
|
type="string"/>
|
||||||
|
<one-to-many entity-name="Account"
|
||||||
|
embed-xml="false"
|
||||||
|
node="account"/>
|
||||||
|
</map>
|
||||||
|
|
||||||
|
<component name="name"
|
||||||
|
node="name">
|
||||||
|
<property name="firstName"
|
||||||
|
node="first-name"/>
|
||||||
|
<property name="initial"
|
||||||
|
node="initial"/>
|
||||||
|
<property name="lastName"
|
||||||
|
node="last-name"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
en este caso, hemos decidido embeber la colección de ids de cuenta, pero no los datos reales de cuenta.
|
||||||
|
La siguiente consulta HQL:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[from Customer c left join fetch c.accounts where c.lastName like :lastName]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
devolvería conjuntos de datos como estos:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<customer id="123456789">
|
||||||
|
<account short-desc="Savings">987632567</account>
|
||||||
|
<account short-desc="Credit Card">985612323</account>
|
||||||
|
<name>
|
||||||
|
<first-name>Gavin</first-name>
|
||||||
|
<initial>A</initial>
|
||||||
|
<last-name>King</last-name>
|
||||||
|
</name>
|
||||||
|
...
|
||||||
|
</customer>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si estableces <literal>embed-xml="true"</literal> en el mapeo <literal><one-to-many></literal>, los datos
|
||||||
|
podrían verse así:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<customer id="123456789">
|
||||||
|
<account id="987632567" short-desc="Savings">
|
||||||
|
<customer id="123456789"/>
|
||||||
|
<balance>100.29</balance>
|
||||||
|
</account>
|
||||||
|
<account id="985612323" short-desc="Credit Card">
|
||||||
|
<customer id="123456789"/>
|
||||||
|
<balance>-2370.34</balance>
|
||||||
|
</account>
|
||||||
|
<name>
|
||||||
|
<first-name>Gavin</first-name>
|
||||||
|
<initial>A</initial>
|
||||||
|
<last-name>King</last-name>
|
||||||
|
</name>
|
||||||
|
...
|
||||||
|
</customer>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
<sect1 id="xml-manipulation" revision="1">
|
||||||
|
<title>Manipulando datos XML</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vamos a releer y actualizar documentos XML en la aplicación. Hacemos esto obteniendo una sesión dom4j:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Document doc = ....;
|
||||||
|
|
||||||
|
Session session = factory.openSession();
|
||||||
|
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
List results = dom4jSession
|
||||||
|
.createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName")
|
||||||
|
.list();
|
||||||
|
for ( int i=0; i<results.size(); i++ ) {
|
||||||
|
//add the customer data to the XML document
|
||||||
|
Element customer = (Element) results.get(i);
|
||||||
|
doc.add(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = factory.openSession();
|
||||||
|
Session dom4jSession = session.getSession(EntityMode.DOM4J);
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
Element cust = (Element) dom4jSession.get("Customer", customerId);
|
||||||
|
for ( int i=0; i<results.size(); i++ ) {
|
||||||
|
Element customer = (Element) results.get(i);
|
||||||
|
//change the customer name in the XML and database
|
||||||
|
Element name = customer.element("name");
|
||||||
|
name.element("first-name").setText(firstName);
|
||||||
|
name.element("initial").setText(initial);
|
||||||
|
name.element("last-name").setText(lastName);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Es extremadamente útil combinar esta funcionalidad con la operación <literal>replicate()</literal>
|
||||||
|
de Hibernate para implementar la importación/exportación de datos basada en XML.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
A {
|
||||||
|
color: #003399;
|
||||||
|
}
|
||||||
|
|
||||||
|
A:active {
|
||||||
|
color: #003399;
|
||||||
|
}
|
||||||
|
|
||||||
|
A:visited {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
P, OL, UL, LI, DL, DT, DD, BLOCKQUOTE {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
TD, TH, SPAN {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCKQUOTE {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
H1, H2, H3, H4, H5, H6 {
|
||||||
|
color: #000000;
|
||||||
|
font-weight:500;
|
||||||
|
margin-top:10px;
|
||||||
|
padding-top:15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
TABLE {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing:0;
|
||||||
|
border: 1px thin black;
|
||||||
|
empty-cells: hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
TD {
|
||||||
|
padding: 4pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
H1 { font-size: 150%; }
|
||||||
|
H2 { font-size: 140%; }
|
||||||
|
H3 { font-size: 110%; font-weight: bold; }
|
||||||
|
H4 { font-size: 110%; font-weight: bold;}
|
||||||
|
H5 { font-size: 100%; font-style: italic; }
|
||||||
|
H6 { font-size: 100%; font-style: italic; }
|
||||||
|
|
||||||
|
TT {
|
||||||
|
font-size: 90%;
|
||||||
|
font-family: "Courier New", Courier, monospace;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
PRE {
|
||||||
|
font-size: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #CCCCCC;
|
||||||
|
background-color: #F4F4F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
UL, OL, LI {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
HR {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
border-width: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
color: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variablelist {
|
||||||
|
padding-top: 10;
|
||||||
|
padding-bottom:10;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemizedlist, UL {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.term {
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,429 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="354.331"
|
||||||
|
height="336.614"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,5.25349)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g659">
|
||||||
|
<rect
|
||||||
|
width="212.257"
|
||||||
|
height="57.2441"
|
||||||
|
x="17.9576"
|
||||||
|
y="100.132"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
transform="matrix(0.743454,0,0,0.482981,6.46949,52.2178)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
width="325.86"
|
||||||
|
height="63.6537"
|
||||||
|
x="17.4083"
|
||||||
|
y="15.194"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="325.86"
|
||||||
|
height="63.6537"
|
||||||
|
x="13.6713"
|
||||||
|
y="12.4966"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
<g
|
||||||
|
transform="matrix(1.14345,0,0,0.729078,-1.67818,105.325)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="170.824753"
|
||||||
|
y="58.402939"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="170.824997"
|
||||||
|
y="58.402901"
|
||||||
|
id="tspan360">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="178.076340"
|
||||||
|
y="364.281433"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="178.076004"
|
||||||
|
y="364.281006"
|
||||||
|
id="tspan421">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="68.605331"
|
||||||
|
y="138.524582"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="68.605301"
|
||||||
|
y="138.524994"
|
||||||
|
id="tspan384">
|
||||||
|
SessionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="67.0014"
|
||||||
|
height="101.35"
|
||||||
|
x="196.927"
|
||||||
|
y="89.2389"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect387" />
|
||||||
|
<rect
|
||||||
|
width="67.0014"
|
||||||
|
height="101.35"
|
||||||
|
x="194.633"
|
||||||
|
y="86.4389"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect388" />
|
||||||
|
<text
|
||||||
|
x="249.108841"
|
||||||
|
y="173.885559"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text389">
|
||||||
|
<tspan
|
||||||
|
x="249.108994"
|
||||||
|
y="173.886002"
|
||||||
|
id="tspan392">
|
||||||
|
Session</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="73.0355"
|
||||||
|
height="101.35"
|
||||||
|
x="270.995"
|
||||||
|
y="90.0018"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect395" />
|
||||||
|
<rect
|
||||||
|
width="73.0355"
|
||||||
|
height="101.35"
|
||||||
|
x="267.869"
|
||||||
|
y="87.2018"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect396" />
|
||||||
|
<text
|
||||||
|
x="328.593658"
|
||||||
|
y="174.715622"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text397">
|
||||||
|
<tspan
|
||||||
|
x="328.593994"
|
||||||
|
y="174.716003"
|
||||||
|
id="tspan563">
|
||||||
|
Transaction</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.29544,0,0,0.397877,9.70533,103.96)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g565">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect566" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect567" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="25.592752"
|
||||||
|
y="204.497803"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:10;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text568">
|
||||||
|
<tspan
|
||||||
|
x="25.592800"
|
||||||
|
y="204.498001"
|
||||||
|
id="tspan662">
|
||||||
|
TransactionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.298082,0,0,0.397877,99.6898,103.96)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g573">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect574" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect575" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="134.030670"
|
||||||
|
y="205.532791"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:10;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text576">
|
||||||
|
<tspan
|
||||||
|
x="134.031006"
|
||||||
|
y="205.533005"
|
||||||
|
id="tspan664">
|
||||||
|
ConnectionProvider</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.14345,0,0,0.729078,-1.67818,38.9539)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g587">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect588" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect589" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="25.6196"
|
||||||
|
y="206.028"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect594" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="24.4229"
|
||||||
|
y="204.135"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect595" />
|
||||||
|
<text
|
||||||
|
x="85.575645"
|
||||||
|
y="282.300354"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text596">
|
||||||
|
<tspan
|
||||||
|
x="85.575600"
|
||||||
|
y="282.299988"
|
||||||
|
id="tspan607">
|
||||||
|
JNDI</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="236.937"
|
||||||
|
y="206.791"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect610" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="235.741"
|
||||||
|
y="204.898"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect611" />
|
||||||
|
<text
|
||||||
|
x="342.093201"
|
||||||
|
y="283.226410"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text612">
|
||||||
|
<tspan
|
||||||
|
x="342.092987"
|
||||||
|
y="283.226013"
|
||||||
|
id="tspan621">
|
||||||
|
JTA</tspan>
|
||||||
|
</text>
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="130.134"
|
||||||
|
y="206.791"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect616" />
|
||||||
|
<rect
|
||||||
|
width="90.951"
|
||||||
|
height="44.4829"
|
||||||
|
x="128.937"
|
||||||
|
y="204.898"
|
||||||
|
style="font-size:12;fill:#b3b3b3;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect617" />
|
||||||
|
<text
|
||||||
|
x="212.445343"
|
||||||
|
y="283.226410"
|
||||||
|
transform="scale(0.823795,0.823795)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text618">
|
||||||
|
<tspan
|
||||||
|
x="212.445007"
|
||||||
|
y="283.226013"
|
||||||
|
id="tspan623">
|
||||||
|
JDBC</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,6.19341)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g637">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.499515,0,0,0.415467,-0.237339,5.61339)"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="33.749969"
|
||||||
|
y="50.589706"
|
||||||
|
style="font-size:11;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="33.750000"
|
||||||
|
y="50.589699"
|
||||||
|
id="tspan635">
|
||||||
|
Transient Objects</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.823795,0,0,0.823795,0.120302,5.25349)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g644">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.297486,0,0,0.516482,230.251,36.9178)"
|
||||||
|
id="g364">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect365" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect366" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="277.123230"
|
||||||
|
y="85.155571"
|
||||||
|
style="font-size:11;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text367">
|
||||||
|
<tspan
|
||||||
|
x="277.122986"
|
||||||
|
y="85.155602"
|
||||||
|
id="tspan631">
|
||||||
|
Persistent</tspan>
|
||||||
|
<tspan
|
||||||
|
x="277.122986"
|
||||||
|
y="96.155602"
|
||||||
|
id="tspan633">
|
||||||
|
Objects</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,334 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="318.898"
|
||||||
|
height="248.031"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<rect
|
||||||
|
width="291.837"
|
||||||
|
height="57.0074"
|
||||||
|
x="17.3169"
|
||||||
|
y="18.646"
|
||||||
|
style="font-size:12;fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="291.837"
|
||||||
|
height="57.0074"
|
||||||
|
x="13.9703"
|
||||||
|
y="16.2302"
|
||||||
|
style="font-size:12;fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.326107,0,0,0.765831,9.59261,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g161">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.02406,0,0,0.652953,0.223384,39.9254)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.449834,0,0,0.338463,-3.15909,9.73319)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="302.277679"
|
||||||
|
y="65.943230"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="302.277954"
|
||||||
|
y="65.943184"
|
||||||
|
id="tspan360">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="36.235924"
|
||||||
|
y="63.796055"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:14;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="36.235950"
|
||||||
|
y="63.796051"
|
||||||
|
id="tspan427">
|
||||||
|
Transient Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="180.416245"
|
||||||
|
y="290.543701"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:18;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="180.415939"
|
||||||
|
y="290.543549"
|
||||||
|
id="tspan421">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="25.037701"
|
||||||
|
y="179.154755"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="25.037655"
|
||||||
|
y="179.154648"
|
||||||
|
id="tspan384">
|
||||||
|
SessionFactory</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.252763,0,0,0.765831,109.104,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g386">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect387" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect388" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.297394,0,0,0.572692,101.502,21.6359)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g364">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect365" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect366" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="202.746506"
|
||||||
|
y="102.992203"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:14;font-weight:normal;stroke-width:1pt;font-family:Helvetica;text-anchor:middle;"
|
||||||
|
id="text367">
|
||||||
|
<tspan
|
||||||
|
x="202.746948"
|
||||||
|
y="102.992249"
|
||||||
|
id="tspan423">
|
||||||
|
Persistent</tspan>
|
||||||
|
<tspan
|
||||||
|
x="202.746948"
|
||||||
|
y="116.992355"
|
||||||
|
id="tspan425">
|
||||||
|
Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="174.458496"
|
||||||
|
y="180.080795"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text389">
|
||||||
|
<tspan
|
||||||
|
x="174.458618"
|
||||||
|
y="180.080338"
|
||||||
|
id="tspan392">
|
||||||
|
Session</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,188.675,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g394">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect395" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect396" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="260.413269"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text397">
|
||||||
|
<tspan
|
||||||
|
x="260.412964"
|
||||||
|
y="179.154343"
|
||||||
|
id="tspan400">
|
||||||
|
JDBC</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,229.156,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g405">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect406" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect407" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="320.606903"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text408">
|
||||||
|
<tspan
|
||||||
|
x="320.606964"
|
||||||
|
y="179.154343"
|
||||||
|
id="tspan417">
|
||||||
|
JNDI</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.127369,0,0,0.765831,269.281,8.98517)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g411">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect412" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect413" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="377.096313"
|
||||||
|
y="179.154739"
|
||||||
|
transform="scale(0.73778,0.73778)"
|
||||||
|
style="font-size:16;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text414">
|
||||||
|
<tspan
|
||||||
|
x="377.096008"
|
||||||
|
y="179.154999"
|
||||||
|
id="tspan145">
|
||||||
|
JTA</tspan>
|
||||||
|
</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1,250 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
|
||||||
|
[
|
||||||
|
<!ATTLIST svg
|
||||||
|
xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
|
||||||
|
]>
|
||||||
|
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="248.031"
|
||||||
|
height="248.031"
|
||||||
|
id="svg1">
|
||||||
|
<defs
|
||||||
|
id="defs3">
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop128" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffffff;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop129" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="1"
|
||||||
|
y2="0"
|
||||||
|
id="linearGradient130"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
<radialGradient
|
||||||
|
cx="0.5"
|
||||||
|
cy="0.5"
|
||||||
|
fx="0.5"
|
||||||
|
fy="0.5"
|
||||||
|
r="0.5"
|
||||||
|
id="radialGradient131"
|
||||||
|
xlink:href="#linearGradient127"
|
||||||
|
gradientUnits="objectBoundingBox"
|
||||||
|
spreadMethod="pad" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,-3.02123)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g158">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="17.3527"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect136" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="15.3883"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect126" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,3.04452)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g161">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="16.6979"
|
||||||
|
y="99.2053"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect137" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="118.523"
|
||||||
|
x="13.4238"
|
||||||
|
y="95.9309"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect132" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,4.36019,8.0993)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g164">
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="16.6979"
|
||||||
|
y="222.966"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect138" />
|
||||||
|
<rect
|
||||||
|
width="285.502"
|
||||||
|
height="77.2688"
|
||||||
|
x="14.7335"
|
||||||
|
y="221.002"
|
||||||
|
transform="translate(-1.30962,-1.30992)"
|
||||||
|
style="fill:#d2d2d2;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect133" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.543505,2.59104,21.1103)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g167">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect134" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect135" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="105.392174"
|
||||||
|
y="56.568123"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text183">
|
||||||
|
<tspan
|
||||||
|
x="105.392273"
|
||||||
|
y="56.568146"
|
||||||
|
id="tspan186">
|
||||||
|
Application</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="81.820183"
|
||||||
|
y="103.149330"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:20;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text188">
|
||||||
|
<tspan
|
||||||
|
x="81.820213"
|
||||||
|
y="103.149727"
|
||||||
|
id="tspan206">
|
||||||
|
Persistent Objects</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="111.548180"
|
||||||
|
y="278.927887"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text197">
|
||||||
|
<tspan
|
||||||
|
x="111.547874"
|
||||||
|
y="278.927551"
|
||||||
|
id="tspan200">
|
||||||
|
Database</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="94.436180"
|
||||||
|
y="153.805740"
|
||||||
|
transform="scale(0.771934,0.771934)"
|
||||||
|
style="font-size:24;font-weight:normal;stroke-width:1pt;font-family:Helvetica;"
|
||||||
|
id="text216">
|
||||||
|
<tspan
|
||||||
|
x="94.436180"
|
||||||
|
y="153.805740"
|
||||||
|
id="tspan221">
|
||||||
|
HIBERNATE</tspan>
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.771934,0,0,0.771934,2.59083,1.02261)"
|
||||||
|
style="font-size:12;"
|
||||||
|
id="g254">
|
||||||
|
<g
|
||||||
|
transform="translate(4.58374,2.61928)"
|
||||||
|
id="g176">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.571429,0,0,0.67347,-10.6174,117.093)"
|
||||||
|
id="g170">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect171" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect172" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.571429,0,0,0.67347,138.682,117.093)"
|
||||||
|
id="g173">
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="61.8805"
|
||||||
|
y="68.4288"
|
||||||
|
style="fill:#757575;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect174" />
|
||||||
|
<rect
|
||||||
|
width="199.065"
|
||||||
|
height="61.5532"
|
||||||
|
x="59.2613"
|
||||||
|
y="65.8095"
|
||||||
|
style="fill:#e0e0e0;fill-rule:evenodd;stroke-width:1pt;"
|
||||||
|
id="rect175" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
x="47.259438"
|
||||||
|
y="182.367538"
|
||||||
|
style="font-weight:bold;stroke-width:1pt;font-family:Courier;"
|
||||||
|
id="text191">
|
||||||
|
<tspan
|
||||||
|
x="47.259399"
|
||||||
|
y="182.367996"
|
||||||
|
id="tspan212">
|
||||||
|
hibernate.</tspan>
|
||||||
|
<tspan
|
||||||
|
x="47.259399"
|
||||||
|
y="194.367996"
|
||||||
|
id="tspan214">
|
||||||
|
properties</tspan>
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
x="198.523010"
|
||||||
|
y="188.260941"
|
||||||
|
style="font-weight:normal;stroke-width:1pt;font-family:helvetica;"
|
||||||
|
id="text194">
|
||||||
|
<tspan
|
||||||
|
id="tspan195">
|
||||||
|
XML Mapping</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.5 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-manual</artifactId>
|
||||||
|
<version>3.3.0.beta1</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>hibernate-manual-${translation}</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>Hibernate Manual (${translation})</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<translation>fr-FR</translation>
|
||||||
|
</properties>
|
||||||
|
</project>
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3CR3//EN"
|
||||||
|
"../support/docbook-dtd/docbookx.dtd"
|
||||||
|
[
|
||||||
|
<!ENTITY tutorial SYSTEM "modules/tutorial.xml">
|
||||||
|
<!ENTITY architecture SYSTEM "modules/architecture.xml">
|
||||||
|
<!ENTITY configuration SYSTEM "modules/configuration.xml">
|
||||||
|
<!ENTITY persistent-classes SYSTEM "modules/persistent_classes.xml">
|
||||||
|
<!ENTITY basic-mapping SYSTEM "modules/basic_mapping.xml">
|
||||||
|
<!ENTITY collection-mapping SYSTEM "modules/collection_mapping.xml">
|
||||||
|
<!ENTITY association-mapping SYSTEM "modules/association_mapping.xml">
|
||||||
|
<!ENTITY component-mapping SYSTEM "modules/component_mapping.xml">
|
||||||
|
<!ENTITY inheritance-mapping SYSTEM "modules/inheritance_mapping.xml">
|
||||||
|
<!ENTITY session-api SYSTEM "modules/session_api.xml">
|
||||||
|
<!ENTITY transactions SYSTEM "modules/transactions.xml">
|
||||||
|
<!ENTITY events SYSTEM "modules/events.xml">
|
||||||
|
<!ENTITY batch SYSTEM "modules/batch.xml">
|
||||||
|
<!ENTITY query-hql SYSTEM "modules/query_hql.xml">
|
||||||
|
<!ENTITY query-criteria SYSTEM "modules/query_criteria.xml">
|
||||||
|
<!ENTITY query-sql SYSTEM "modules/query_sql.xml">
|
||||||
|
<!ENTITY filters SYSTEM "modules/filters.xml">
|
||||||
|
<!ENTITY xml SYSTEM "modules/xml.xml">
|
||||||
|
<!ENTITY performance SYSTEM "modules/performance.xml">
|
||||||
|
<!ENTITY toolset-guide SYSTEM "modules/toolset_guide.xml">
|
||||||
|
<!ENTITY example-parentchild SYSTEM "modules/example_parentchild.xml">
|
||||||
|
<!ENTITY example-weblog SYSTEM "modules/example_weblog.xml">
|
||||||
|
<!ENTITY example-mappings SYSTEM "modules/example_mappings.xml">
|
||||||
|
<!ENTITY best-practices SYSTEM "modules/best_practices.xml">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<book lang="fr">
|
||||||
|
|
||||||
|
<bookinfo>
|
||||||
|
<title>HIBERNATE - Persistance relationnelle en Java standard</title>
|
||||||
|
<subtitle>Documentation de référence d'Hibernate</subtitle>
|
||||||
|
<releaseinfo>3.3.0.beta1</releaseinfo>
|
||||||
|
</bookinfo>
|
||||||
|
|
||||||
|
<toc/>
|
||||||
|
|
||||||
|
<preface id="preface" revision="2">
|
||||||
|
<title>Préface</title>
|
||||||
|
<para>
|
||||||
|
Traducteur(s): Vincent Ricard, Sebastien Cesbron, Michael Courcy, Vincent Giguère, Baptiste Mathus, Emmanuel Bernard, Anthony Patricio
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Travailler dans les deux univers que sont l'orienté objet et la base de données
|
||||||
|
relationnelle peut être lourd et consommateur en temps dans le monde de
|
||||||
|
l'entreprise d'aujourd'hui. Hibernate est un outil de mapping objet/relationnel
|
||||||
|
pour le monde Java. Le terme mapping objet/relationnel (ORM) décrit la technique
|
||||||
|
consistant à faire le lien entre la représentation objet des données
|
||||||
|
et sa représentation relationnelle basée sur un schéma SQL.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Non seulement, Hibernate s'occupe du transfert des classes Java dans les tables
|
||||||
|
de la base de données (et des types de données Java dans les types de données SQL),
|
||||||
|
mais il permet de requêter les données et propose des moyens de les récupérer.
|
||||||
|
Il peut donc réduire de manière significative le temps de développement qui
|
||||||
|
aurait été autrement perdu dans une manipulation manuelle des données via SQL
|
||||||
|
et JDBC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le but d'Hibernate est de libérer le développeur de 95 pourcent des tâches de
|
||||||
|
programmation liées à la persistance des données communes. Hibernate n'est
|
||||||
|
probablement pas la meilleure solution pour les applications centrées sur les
|
||||||
|
données qui n'utilisent que les procédures stockées pour implémenter la logique
|
||||||
|
métier dans la base de données, il est le plus utile dans les modèles métier orientés
|
||||||
|
objets dont la logique métier est implémentée dans la couche Java dite intermédiaire.
|
||||||
|
Cependant, Hibernate vous aidera à supprimer ou à encapsuler le code SQL
|
||||||
|
spécifique à votre base de données et vous aidera sur la tâche commune qu'est
|
||||||
|
la transformation des données d'une représentation tabulaire à une
|
||||||
|
représentation sous forme de graphe d'objets.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si vous êtes nouveau dans Hibernate et le mapping Objet/Relationnel voire même en Java,
|
||||||
|
suivez ces quelques étapes :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Lisez <xref linkend="tutorial"/> pour un didacticiel plus long avec plus d'instructions étape par étape.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Lisez <xref linkend="architecture"/> pour comprendre les environnements dans lesquels
|
||||||
|
Hibernate peut être utilisé.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Regardez le répertoire <literal>eg</literal> de la distribution Hibernate, il contient
|
||||||
|
une application simple et autonome. Copiez votre pilote JDBC dans le répertoire
|
||||||
|
<literal>lib/</literal> et éditez <literal>src/hibernate.properties</literal>, en
|
||||||
|
positionnant correctement les valeurs pour votre base de données. A partir d'une
|
||||||
|
invite de commande dans le répertoire de la distribution, tapez <literal>ant eg</literal>
|
||||||
|
(cela utilise Ant), ou sous Windows tapez <literal>build eg</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Faîtes de cette documentation de référence votre principale source d'information.
|
||||||
|
Pensez à lire <emphasis>Hibernate in Action</emphasis>
|
||||||
|
(http://www.manning.com/bauer) si vous avez besoin de plus d'aide avec le design
|
||||||
|
d'applications ou si vous préférez un tutoriel pas à pas. Visitez aussi
|
||||||
|
http://caveatemptor.hibernate.org et téléchargez l'application exemple
|
||||||
|
pour Hibernate in Action.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Les questions les plus fréquemment posées (FAQs) trouvent leur réponse sur le
|
||||||
|
site web Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Des démos, exemples et tutoriaux de tierces personnes sont référencés sur
|
||||||
|
le site web Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La zone communautaire (Community Area) du site web Hibernate est une
|
||||||
|
bonne source d'information sur les design patterns et sur différentes
|
||||||
|
solutions d'intégration d'Hibernate (Tomcat, JBoss, Spring Framework, Struts,
|
||||||
|
EJB, etc).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si vous avez des questions, utilisez le forum utilisateurs du site web Hibernate.
|
||||||
|
Nous utilisons également l'outil de gestion des incidents JIRA pour tout ce qui
|
||||||
|
est rapports de bogue et demandes d'évolution. Si vous êtes intéressé par le
|
||||||
|
développement d'Hibernate, joignez-vous à la liste de diffusion de développement.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le développement commercial, le support de production et les formations à Hibernate
|
||||||
|
sont proposés par JBoss Inc (voir http://www.hibernate.org/SupportTraining/). Hibernate
|
||||||
|
est un projet Open Source professionnel et un composant critique de la suite de produits
|
||||||
|
JBoss Enterprise Middleware System (JEMS).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</preface>
|
||||||
|
|
||||||
|
&tutorial;
|
||||||
|
|
||||||
|
&architecture;
|
||||||
|
|
||||||
|
&configuration;
|
||||||
|
|
||||||
|
&persistent-classes;
|
||||||
|
|
||||||
|
&basic-mapping;
|
||||||
|
&collection-mapping;
|
||||||
|
&association-mapping;
|
||||||
|
&component-mapping;
|
||||||
|
&inheritance-mapping;
|
||||||
|
|
||||||
|
&session-api;
|
||||||
|
&transactions;
|
||||||
|
&events;
|
||||||
|
&batch;
|
||||||
|
|
||||||
|
&query-hql;
|
||||||
|
&query-criteria;
|
||||||
|
&query-sql;
|
||||||
|
&filters;
|
||||||
|
&xml;
|
||||||
|
|
||||||
|
&performance;
|
||||||
|
|
||||||
|
&toolset-guide;
|
||||||
|
|
||||||
|
&example-parentchild;
|
||||||
|
&example-weblog;
|
||||||
|
&example-mappings;
|
||||||
|
|
||||||
|
&best-practices;
|
||||||
|
|
||||||
|
</book>
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="architecture">
|
||||||
|
|
||||||
|
<title>Architecture</title>
|
||||||
|
|
||||||
|
<sect1 id="architecture-overview" revision="1">
|
||||||
|
<title>Généralités</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Voici une vue (très) haut niveau de l'architecture d'Hibernate :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/overview.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/overview.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ce diagramme montre Hibernate utilisant une base de données et des données
|
||||||
|
de configuration pour fournir un service de persistance (et des objets
|
||||||
|
persistants) à l'application.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous aimerions décrire une vue plus détaillée de l'architecture. Malheureusement,
|
||||||
|
Hibernate est flexible et supporte différentes approches. Nous allons en
|
||||||
|
montrer les deux extrêmes. L'architecture légère laisse l'application fournir
|
||||||
|
ses propres connexions JDBC et gérer ses propres transactions. Cette approche
|
||||||
|
utilise le minimum des APIs Hibernate :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/lite.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/lite.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'architecture la plus complète abstrait l'application des APIs JDBC/JTA
|
||||||
|
sous-jacentes et laisse Hibernate s'occuper des détails.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/full_cream.svg" format="SVG" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/full_cream.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Voici quelques définitions des objets des diagrammes :
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>SessionFactory (<literal>org.hibernate.SessionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Un cache threadsafe (immuable) des mappings vers une (et une seule) base
|
||||||
|
de données. Une factory (fabrique) de <literal>Session</literal> et un client
|
||||||
|
de <literal>ConnectionProvider</literal>. Peut contenir un cache optionnel de
|
||||||
|
données (de second niveau) qui est réutilisable entre les différentes transactions
|
||||||
|
que cela soit au sein du même processus (JVLM) ou par plusieurs n½uds d'un cluster.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Session (<literal>org.hibernate.Session</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Un objet mono-threadé, à durée de vie courte, qui représente une conversation
|
||||||
|
entre l'application et l'entrepôt de persistance. Encapsule une connexion JDBC.
|
||||||
|
Factory (fabrique) des objets <literal>Transaction</literal>. Contient un cache
|
||||||
|
(de premier niveau) des objets persistants, ce cache est obligatoire. Il est
|
||||||
|
utilisé lors de la navigation dans le graphe d'objets ou lors de la récupération
|
||||||
|
d'objets par leur identifiant.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Objets et Collections persistants</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Objets mono-threadés à vie courte contenant l'état de persistance
|
||||||
|
et la fonction métier. Ceux-ci sont en général les objets de type JavaBean
|
||||||
|
(ou POJOs) ; la seule particularité est qu'ils sont associés avec une (et
|
||||||
|
une seule) <literal>Session</literal>. Dès que la <literal>Session</literal>
|
||||||
|
est fermée, ils seront détachés et libres d'être utilisés par n'importe laquelle
|
||||||
|
des couches de l'application (ie. de et vers la présentation en tant que Data
|
||||||
|
Transfer Objects - DTO : objet de transfert de données).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Objets et collections transients</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Instances de classes persistantes qui ne sont actuellement pas associées à
|
||||||
|
une <literal>Session</literal>. Elles ont pu être instanciées par l'application
|
||||||
|
et ne pas avoir (encore) été persistées ou elle ont pu être instanciées par
|
||||||
|
une <literal>Session</literal> fermée.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Transaction (<literal>org.hibernate.Transaction</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optionnel) Un objet mono-threadé à vie courte utilisé par l'application
|
||||||
|
pour définir une unité de travail atomique. Abstrait l'application des
|
||||||
|
transactions sous-jacentes qu'elles soient JDBC, JTA ou CORBA. Une
|
||||||
|
<literal>Session</literal> peut fournir plusieurs <literal>Transaction</literal>s
|
||||||
|
dans certains cas. Toutefois, la délimitation des transactions, via l'API d'Hibernate
|
||||||
|
ou par la <literal>Transaction</literal> sous-jacente, n'est jamais optionnelle!
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>ConnectionProvider (<literal>org.hibernate.connection.ConnectionProvider</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optionnel) Une fabrique de (pool de) connexions JDBC. Abstrait l'application
|
||||||
|
de la <literal>Datasource</literal> ou du <literal>DriverManager</literal> sous-jacent.
|
||||||
|
Non exposé à l'application, mais peut être étendu/implémenté par le développeur.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>TransactionFactory (<literal>org.hibernate.TransactionFactory</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
(Optionnel) Une fabrique d'instances de <literal>Transaction</literal>. Non
|
||||||
|
exposé à l'application, mais peut être étendu/implémenté par le développeur.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><emphasis>Interfaces d'extension</emphasis></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate fournit de nombreuses interfaces d'extensions optionnelles que
|
||||||
|
vous pouvez implémenter pour personnaliser le comportement de votre couche de persistance.
|
||||||
|
Reportez vous à la documentation de l'API pour plus de détails.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dans une architecture légère, l'application n'aura pas à utiliser les APIs
|
||||||
|
<literal>Transaction</literal>/<literal>TransactionFactory</literal>
|
||||||
|
et/ou n'utilisera pas les APIs <literal>ConnectionProvider</literal>
|
||||||
|
pour utiliser JTA ou JDBC.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-states" revision="1">
|
||||||
|
<title>Etats des instances</title>
|
||||||
|
<para>
|
||||||
|
Une instance d'une classe persistante peut être dans l'un des trois états suivants,
|
||||||
|
définis par rapport à un <emphasis>contexte de persistance</emphasis>.
|
||||||
|
L'objet <literal>Session</literal> d'hibernate correspond à ce concept de
|
||||||
|
contexte de persistance :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>passager (transient)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
L'instance n'est pas et n'a jamais été associée à un contexte
|
||||||
|
de persistance. Elle ne possède pas d'identité persistante (valeur de clé primaire)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>persistant</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
L'instance est associée au contexte de persistance.
|
||||||
|
Elle possède une identité persistante (valeur de clé primaire)
|
||||||
|
et, peut-être, un enregistrement correspondant dans la base.
|
||||||
|
Pour un contexte de persistance particulier, Hibernate
|
||||||
|
<emphasis>garantit</emphasis> que l'identité persistante
|
||||||
|
est équivalente à l'identité Java (emplacement mémoire de l'objet)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>détaché</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
L'instance a été associée au contexte de persistance mais ce
|
||||||
|
contexte a été fermé, ou l'instance a été sérialisée vers un
|
||||||
|
autre processus. Elle possède une identité persistante et
|
||||||
|
peut-être un enregistrement correspondant dans la base.
|
||||||
|
Pour des instances détachées, Hibernate ne donne aucune
|
||||||
|
garantie sur la relation entre l'identité persistante et
|
||||||
|
l'identité Java.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jmx" revision="1">
|
||||||
|
<title>Intégration JMX</title>
|
||||||
|
<para>
|
||||||
|
JMX est le standard J2EE de gestion des composants Java.
|
||||||
|
Hibernate peut être géré via un service JMX standard. Nous fournissons une implémentation
|
||||||
|
d'un MBean dans la distribution : <literal>org.hibernate.jmx.HibernateService</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour avoir un exemple sur la manière de déployer Hibernate en tant que service JMX dans le
|
||||||
|
serveur d'application JBoss Application Server, référez vous au guide utilisateur JBoss (JBoss User Guide).
|
||||||
|
Si vous déployez Hibernate via JMX sur JBoss AS, vous aurez également les bénéfices suivants :
|
||||||
|
</para>
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Gestion de la session :</emphasis> Le cycle de vie de la <literal>Session</literal>
|
||||||
|
Hibernate peut être automatiquement limitée à la portée d'une transaction JTA.
|
||||||
|
Cela signifie que vous n'avez plus besoin d'ouvrir et de fermer la <literal>Session</literal>
|
||||||
|
manuellement, cela devient le travail de l'intercepteur EJB de JBoss. Vous n'avez
|
||||||
|
pas non plus à vous occuper des démarcations des transactions dans votre code (sauf
|
||||||
|
si vous voulez écrire une couche de persistance qui soit portable, dans ce cas vous
|
||||||
|
pouvez utiliser l'API optionnelle <literal>Transaction</literal> d'Hibernate).
|
||||||
|
Vous appelez l'<literal>HibernateContext</literal> pour accéder à la <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<emphasis>Déploiement HAR :</emphasis> Habituellement vous déployez le service JMX
|
||||||
|
Hibernate en utilisant le descripteur de déploiement de JBoss (dans un fichier EAR et/ou un SAR),
|
||||||
|
il supporte toutes les options de configuration usuelles d'une <literal>SessionFactory</literal>
|
||||||
|
Hibernate. Cependant, vous devez toujours nommer tous vos fichiers de mapping dans le
|
||||||
|
descripteur de déploiement. Si vous décidez d'utiliser le déploiement optionnel sous forme
|
||||||
|
de HAR, JBoss détectera automatiquement tous vos fichiers de mapping dans votre fichier HAR.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Consultez le guide d'utilisation de JBoss AS pour plus d'informations sur ces options.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les statistiques pendant l'exécution d'Hibernate (au runtime) sont une
|
||||||
|
autre fonctionnalité disponible en tant que service JMX. Voyez pour cela
|
||||||
|
<xref linkend="configuration-optional-statistics"/>.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-jca" revision="1">
|
||||||
|
<title>Support JCA</title>
|
||||||
|
<para>
|
||||||
|
Hibernate peut aussi être configuré en tant que connecteur JCA. Référez-vous au site
|
||||||
|
web pour de plus amples détails. Il est important de noter que le support JCA d'Hibernate
|
||||||
|
est encore considéré comme expérimental.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="architecture-current-session" revision="1">
|
||||||
|
<title>Sessions Contextuelles</title>
|
||||||
|
<para>
|
||||||
|
Certaines applications utilisant Hibernate ont besoin d'une sorte de session "contextuelle", où
|
||||||
|
une session est liée à la portée d'un contexte particulier. Cependant, les applications ne définissent
|
||||||
|
pas toutes la notion de contexte de la même manière, et différents contextes définissent différentes
|
||||||
|
portées à la notion de "courant". Les applications à base d'Hibernate, versions précédentes à la 3.0
|
||||||
|
utilisaient généralement un principe maison de sessions contextuelles basées sur le <literal>ThreadLocal</literal>,
|
||||||
|
ainsi que sur des classes utilitaires comme <literal>HibernateUtil</literal>, ou utilisaient des
|
||||||
|
framework tiers (comme Spring ou Pico) qui fournissaient des sessions contextuelles basées sur
|
||||||
|
l'utilisation de proxy/interception.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
A partir de la version 3.0.1, Hibernate a ajouté la méthode <literal>SessionFactory.getCurrentSession()</literal>.
|
||||||
|
Initialement, cela demandait l'usage de transactions <literal>JTA</literal>, où la
|
||||||
|
transaction <literal>JTA</literal> définissait la portée et le contexte de la session courante.
|
||||||
|
L'équipe Hibernate pense que, étant donnée la maturité des implémentations de <literal>JTA TransactionManager</literal> ,
|
||||||
|
la plupart (sinon toutes) des applications devraient utiliser la gestion des transactions par <literal>JTA</literal>
|
||||||
|
qu'elles soient ou non déployées dans un conteneur <literal>J2EE</literal>. Par conséquent,
|
||||||
|
vous devriez toujours contextualiser vos sessions, si vous en avez besoin, via la méthode basée sur JTA.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Cependant, depuis la version 3.1, la logique derrière
|
||||||
|
<literal>SessionFactory.getCurrentSession()</literal> est désormais branchable.
|
||||||
|
A cette fin, une nouvelle interface d'extension (<literal>org.hibernate.context.CurrentSessionContext</literal>)
|
||||||
|
et un nouveau paramètre de configuration (<literal>hibernate.current_session_context_class</literal>)
|
||||||
|
ont été ajoutés pour permettre de configurer d'autres moyens de définir la portée et le contexte des
|
||||||
|
sessions courantes.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Allez voir les Javadocs de l'interface <literal>org.hibernate.context.CurrentSessionContext</literal>
|
||||||
|
pour une description détaillée de son contrat. Elle définit une seule méthode,
|
||||||
|
<literal>currentSession()</literal>, depuis laquelle l'implémentation est responsable
|
||||||
|
de traquer la session courante du contexte. Hibernate fournit deux implémentation
|
||||||
|
de cette interface.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.context.JTASessionContext</literal> - les sessions courantes sont
|
||||||
|
associées à une transaction <literal>JTA</literal>. La logique est la même que
|
||||||
|
l'ancienne approche basée sur JTA. Voir les javadocs pour les détails.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.context.ThreadLocalSessionContext</literal> - les sessions
|
||||||
|
courantes sont associées au thread d'exécution. Voir les javadocs pour les détails.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les deux implémentations fournissent un modèle de programmation de type "une session - une transaction
|
||||||
|
à la base de données", aussi connu sous le nom de <emphasis>session-per-request</emphasis>.
|
||||||
|
Le début et la fin d'une session Hibernate sont définis par la durée d'une transaction de base de données.
|
||||||
|
Si vous utilisez une démarcation programmatique de la transaction (par exemple sous J2SE ou JTA/UserTransaction/BMT),
|
||||||
|
nous vous conseillons d'utiliser l'API Hibernate <literal>Transaction</literal> pour masquer le système
|
||||||
|
de transaction utilisé. Si vous exécutez sous un conteneur EJB qui supporte CMT, vous n'avez besoin d'aucune
|
||||||
|
opérations de démarcations de session ou transaction dans votre code puisque tout
|
||||||
|
est géré de manière déclarative. Référez vous à <xref linkend="transactions"/> pour plus d'informations
|
||||||
|
et des exemples de code.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le paramètre de configuration <literal>hibernate.current_session_context_class</literal>
|
||||||
|
définit quelle implémentation de <literal>org.hibernate.context.CurrentSessionContext</literal>
|
||||||
|
doit être utilisée. Notez que pour assurer la compatibilité avec les versions précédentes, si
|
||||||
|
ce paramètre n'est pas défini mais qu'un <literal>org.hibernate.transaction.TransactionManagerLookup</literal>
|
||||||
|
est configuré, Hibernate utilisera le <literal>org.hibernate.context.JTASessionContext</literal>.
|
||||||
|
La valeur de ce paramètre devrait juste nommer la classe d'implémentation à utiliser,
|
||||||
|
pour les deux implémentations fournies, il y a cependant deux alias correspondant: "jta" et "thread".
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,623 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="associations">
|
||||||
|
|
||||||
|
<title>Mapper les associations</title>
|
||||||
|
|
||||||
|
<sect1 id="assoc-intro" revision="1">
|
||||||
|
<title>Introduction</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Correctement mapper les associations est souvent la tâche la plus difficile.
|
||||||
|
Dans cette section nous traiterons les cas classiques les uns après les autres.
|
||||||
|
Nous commencerons d'abbord par les mappings unidirectionnels, puis nous aborderons
|
||||||
|
la question des mappings bidirectionnels. Nous illustrerons tous nos exemples
|
||||||
|
avec les classes <literal>Person</literal> et <literal>Address</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous utiliserons deux critères pour classer les associations : le premier
|
||||||
|
sera de savoir si l'association est bâti sur une table supplémentaire d'association
|
||||||
|
et le deuxieme sera basé sur la multiplicité de cette association.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Autoriser une clé étrangère nulle est considéré comme un mauvais choix dans
|
||||||
|
la construction d'un modèle de données. Nous supposerons donc que dans tous
|
||||||
|
les exemples qui vont suivre on aura interdit la valeur nulle pour les clés
|
||||||
|
étrangères. Attention, ceci ne veut pas dire que Hibernate ne supporte pas
|
||||||
|
les clés étrangères pouvant prendre des valeurs nulles, les exemples qui suivent
|
||||||
|
continueront de fonctionner si vous décidiez ne plus imposer la contrainte
|
||||||
|
de non-nullité sur les clés étrangères.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional" revision="1">
|
||||||
|
<title>Association unidirectionnelle</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-m21" >
|
||||||
|
<title>plusieurs à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association plusieurs-à-un (many-to-one) unidirectionnelle </emphasis>
|
||||||
|
est le type que l'on rencontre le plus souvent dans les associations unidirectionnelles.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-121">
|
||||||
|
<title>un à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
une <emphasis>association un-à-un (one-to-one) sur une clé étrangère</emphasis>
|
||||||
|
est presque identique. La seule différence est sur la contrainte d'unicité que
|
||||||
|
l'on impose à cette colonne.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association un-à-un (one-to-one) unidirectionnelle sur une clé primaire</emphasis>
|
||||||
|
utilise un générateur d'identifiant particulier. (Remarquez que nous avons inversé le sens de cette
|
||||||
|
association dans cet exemple.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-12m">
|
||||||
|
<title>un à plusieurs</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association un-à-plusieurs (one-to-many) unidirectionnelle sur une
|
||||||
|
clé étrangère</emphasis> est vraiment inhabituelle, et n'est pas vraiment recommandée.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses">
|
||||||
|
<key column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
<one-to-many class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key, personId bigint not null )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous pensons qu'il est préférable d'utiliser une table de jointure pour ce type d'association.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-unidirectional-join" revision="1">
|
||||||
|
<title>Associations unidirectionnelles avec tables de jointure</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-12m">
|
||||||
|
<title>un à plusieurs</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association unidirectionnelle un-à-plusieurs (one-to-many) avec
|
||||||
|
une table de jointure</emphasis> est un bien meilleur choix.
|
||||||
|
Remarquez qu'en spécifiant <literal>unique="true"</literal>,
|
||||||
|
on a changé la multiplicité plusieurs-à-plusieurs (many-to-many) pour
|
||||||
|
un-à-plusieurs (one-to-many).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m21">
|
||||||
|
<title>plusieurs à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>assiociation plusieurs-à-un (many-to-one) unidirectionnelle sur
|
||||||
|
une table de jointure</emphasis> est très fréquente quand l'association est optionnelle.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId" unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-121">
|
||||||
|
<title>un à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association unidirectionnelle un-à-un (one-to-one) sur une table
|
||||||
|
de jointure</emphasis> est extrèmement rare mais envisageable.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-unidirectional-join-m2m">
|
||||||
|
<title>plusieurs à plusieurs</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finallement, nous avons <emphasis>l'association unidirectionnelle plusieurs-à-plusieurs (many-to-many)</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional" revision="1">
|
||||||
|
<title>Associations bidirectionnelles</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-m21" revision="2">
|
||||||
|
<title>un à plusieurs / plusieurs à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association bidirectionnelle plusieurs à un (many-to-one)</emphasis>
|
||||||
|
est le type d'association que l'on rencontre le plus souvent. (c'est la façon standard de créer
|
||||||
|
des relations parents/enfants.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<one-to-many class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si vous utilisez une <literal>List</literal> (ou toute autre collection indexée) vous devez
|
||||||
|
paramétrer la colonne <literal>key</literal> de la clé étrangère à <literal>not null</literal>,
|
||||||
|
et laisser Hibernate gérer l'association depuis l'extrémité collection pour maintenir l'index
|
||||||
|
de chaque élément (rendant l'autre extrémité virtuellement inverse en paramétrant
|
||||||
|
<literal>update="false"</literal> et <literal>insert="false"</literal>):
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id"/>
|
||||||
|
...
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
insert="false"
|
||||||
|
update="false"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id"/>
|
||||||
|
...
|
||||||
|
<list name="people">
|
||||||
|
<key column="addressId" not-null="true"/>
|
||||||
|
<list-index column="peopleIdx"/>
|
||||||
|
<one-to-many class="Person"/>
|
||||||
|
</list>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-121">
|
||||||
|
<title>Un à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association bidirectionnelle un à un (one-to-one) sur une clé étrangère</emphasis>
|
||||||
|
est aussi très fréquente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
unique="true"
|
||||||
|
not-null="true"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
property-ref="address"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association bidirectionnelle un-à-un (one-to-one) sur une clé primaire</emphasis>
|
||||||
|
utilise un générateur particulier d'id.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="address"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="foreign">
|
||||||
|
<param name="property">person</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<one-to-one name="person"
|
||||||
|
constrained="true"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table Address ( personId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-bidirectional-join" revision="1">
|
||||||
|
<title>Associations bidirectionnelles avec table de jointure</title>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-12m">
|
||||||
|
<title>un à plusieurs / plusieurs à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association bidirectionnelle un-à-plusieurs (one-to-many) sur une table de jointure </emphasis>.
|
||||||
|
Remarquez que <literal>inverse="true"</literal> peut s'appliquer sur les deux extrémités de l'
|
||||||
|
association, sur la collection, ou sur la jointure.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses"
|
||||||
|
table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
unique="true"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
inverse="true"
|
||||||
|
optional="true">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-one name="person"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null primary key )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-121">
|
||||||
|
<title>Un à un</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une <emphasis>association bidirectionnelle un-à-un (one-to-one) sur une table de jointure</emphasis>
|
||||||
|
est extrèmement rare mais envisageable.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true">
|
||||||
|
<key column="personId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="address"
|
||||||
|
column="addressId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<join table="PersonAddress"
|
||||||
|
optional="true"
|
||||||
|
inverse="true">
|
||||||
|
<key column="addressId"
|
||||||
|
unique="true"/>
|
||||||
|
<many-to-one name="person"
|
||||||
|
column="personId"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
</join>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="assoc-bidirectional-join-m2m" revision="1">
|
||||||
|
<title>plusieurs à plusieurs</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Finallement nous avons <emphasis>l'association bidirectionnelle plusieurs à plusieurs</emphasis>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="id" column="personId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="addresses" table="PersonAddress">
|
||||||
|
<key column="personId"/>
|
||||||
|
<many-to-many column="addressId"
|
||||||
|
class="Address"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
<id name="id" column="addressId">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<set name="people" inverse="true" table="PersonAddress">
|
||||||
|
<key column="addressId"/>
|
||||||
|
<many-to-many column="personId"
|
||||||
|
class="Person"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
create table Person ( personId bigint not null primary key )
|
||||||
|
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) )
|
||||||
|
create table Address ( addressId bigint not null primary key )
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="assoc-complex">
|
||||||
|
<title>Des mappings plus complexes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Des associations encore plus complexes sont <emphasis>extrêmement</emphasis> rares.
|
||||||
|
Hibernate permet de gérer des situations plus complexes en utilisant des
|
||||||
|
parties SQL dans les fichiers de mapping. Par exemple, si une table
|
||||||
|
avec l'historiques des informations d'un compte définit les colonnes
|
||||||
|
<literal>accountNumber</literal>, <literal>effectiveEndDate</literal>
|
||||||
|
et <literal>effectiveStartDate</literal>, mappées de telle sorte:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<properties name="currentAccountKey">
|
||||||
|
<property name="accountNumber" type="string" not-null="true"/>
|
||||||
|
<property name="currentAccount" type="boolean">
|
||||||
|
<formula>case when effectiveEndDate is null then 1 else 0 end</formula>
|
||||||
|
</property>
|
||||||
|
</properties>
|
||||||
|
<property name="effectiveEndDate" type="date"/>
|
||||||
|
<property name="effectiveStateDate" type="date" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
alors nous pouvons mapper une association à l'instance <emphasis>courante</emphasis>
|
||||||
|
(celle avec une <literal>effectiveEndDate</literal>) nulle en utilisant:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="currentAccountInfo"
|
||||||
|
property-ref="currentAccountKey"
|
||||||
|
class="AccountInfo">
|
||||||
|
<column name="accountNumber"/>
|
||||||
|
<formula>'1'</formula>
|
||||||
|
</many-to-one>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dans un exemple plus complexe, imaginez qu'une association entre
|
||||||
|
<literal>Employee</literal> et <literal>Organization</literal> est gérée
|
||||||
|
dans une table <literal>Employment</literal> pleines de données historiques.
|
||||||
|
Dans ce cas, une association vers l'employeur <emphasis>le plus récent</emphasis>
|
||||||
|
(celui avec la <literal>startDate</literal> la plus récente) pourrait être mappée comme cela:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<join>
|
||||||
|
<key column="employeeId"/>
|
||||||
|
<subselect>
|
||||||
|
select employeeId, orgId
|
||||||
|
from Employments
|
||||||
|
group by orgId
|
||||||
|
having startDate = max(startDate)
|
||||||
|
</subselect>
|
||||||
|
<many-to-one name="mostRecentEmployer"
|
||||||
|
class="Organization"
|
||||||
|
column="orgId"/>
|
||||||
|
</join>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez être créatif grace à ces possibilités, mais il est généralement plus pratique
|
||||||
|
d'utiliser des requêtes HQL ou criteria dans ce genre de situation.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="batch">
|
||||||
|
<title>Traitement par paquet</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une approche naïve pour insérer 100 000 lignes dans la base de données en utilisant
|
||||||
|
Hibernate pourrait ressembler à ça :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
}
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ceci devrait s'écrouler avec une <literal>OutOfMemoryException</literal> quelque
|
||||||
|
part aux alentours de la 50 000ème ligne. C'est parce qu'Hibernate cache toutes
|
||||||
|
les instances de <literal>Customer</literal> nouvellement insérées dans le cache
|
||||||
|
de second niveau.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dans ce chapitre nous montrerons comment éviter ce problème. D'abord, cependant,
|
||||||
|
si vous faites des traitements par batch, il est absolument critique que vous
|
||||||
|
activiez l'utilisation ds paquet JDBC (NdT : JDBC batching), si vous avez l'intention
|
||||||
|
d'obtenir des performances raisonnables. Configurez la taille du paquet JDBC avec un
|
||||||
|
nombre raisonnable (disons, 10-50) :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pourriez aussi vouloir faire cette sorte de travail dans un traitement où
|
||||||
|
l'interaction avec le cache de second niveau est complètement désactivé :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
|
||||||
|
|
||||||
|
<sect1 id="batch-inserts">
|
||||||
|
<title>Insertions en paquet</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Lorsque vous rendez des nouveaux objets persistants, vous devez régulièrement appeler
|
||||||
|
<literal>flush()</literal> et puis <literal>clear()</literal> sur la session,
|
||||||
|
pour contrôler la taille du cache de premier niveau.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
for ( int i=0; i<100000; i++ ) {
|
||||||
|
Customer customer = new Customer(.....);
|
||||||
|
session.save(customer);
|
||||||
|
if ( i % 20 == 0 ) { //20, même taille que la taille du paquet JDBC
|
||||||
|
//flush un paquet d'insertions et libère la mémoire :
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-update" >
|
||||||
|
<title>Paquet de mises à jour</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour récupérer et mettre à jour des données les mêmes idées s'appliquent. En plus,
|
||||||
|
vous avez besoin d'utiliser <literal>scroll()</literal> pour tirer partie des
|
||||||
|
curseurs côté serveur pour les requêtes qui retournent beaucoup de lignes de données.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||||
|
.setCacheMode(CacheMode.IGNORE)
|
||||||
|
.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
int count=0;
|
||||||
|
while ( customers.next() ) {
|
||||||
|
Customer customer = (Customer) customers.get(0);
|
||||||
|
customer.updateStuff(...);
|
||||||
|
if ( ++count % 20 == 0 ) {
|
||||||
|
//flush un paquet de mises à jour et libère la mémoire :
|
||||||
|
session.flush();
|
||||||
|
session.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-statelesssession">
|
||||||
|
<title>L'interface StatelessSession</title>
|
||||||
|
<para>
|
||||||
|
Alternativement, Hibernate fournit une API orientée commande qui peut être
|
||||||
|
utilisée avec des flux de données pour et en provenance de la base de données
|
||||||
|
sous la forme d'objets détachés. Une <literal>StatelessSession</literal> n'a pas
|
||||||
|
de contexte de persistance associé et ne fournit pas beaucoup de sémantique de
|
||||||
|
durée de vie de haut niveau. En particulier, une session sans état n'implémente
|
||||||
|
pas de cache de premier niveau et n'interagit pas non plus avec un cache de
|
||||||
|
seconde niveau ou un cache de requêtes. Elle n'implémente pas les transactions
|
||||||
|
ou la vérification sale automatique (NdT : automatic dirty checking). Les
|
||||||
|
opérations réalisées avec une session sans état ne sont jamais répercutées
|
||||||
|
en cascade sur les instances associées. Les collections sont ignorées par une
|
||||||
|
session sans état. Les opérations exécutées via une session sans état outrepasse
|
||||||
|
le modèle d'événements d'Hibernate et les intercepteurs. Les sessions sans état sont
|
||||||
|
vulnérables aux effets de modification des données, ceci est dû au manque de cache
|
||||||
|
de premier niveau. Une session sans état est une abstraction bas niveau, plus
|
||||||
|
proche de la couche JDBC sous-jacente.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[StatelessSession session = sessionFactory.openStatelessSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
ScrollableResults customers = session.getNamedQuery("GetCustomers")
|
||||||
|
.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
while ( customers.next() ) {
|
||||||
|
Customer customer = (Customer) customers.get(0);
|
||||||
|
customer.updateStuff(...);
|
||||||
|
session.update(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que dans le code de l'exemple, les intances de <literal>Customer</literal>
|
||||||
|
retournées par la requête sont immédiatement détachées. Elles ne sont jamais
|
||||||
|
associées à un contexte de persistance.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les opérations <literal>insert()</literal>, <literal>update()</literal> et
|
||||||
|
<literal>delete()</literal> définies par l'interface <literal>StatelessSession</literal>
|
||||||
|
sont considérées comme des opérations d'accès direct aux lignes de la base de données,
|
||||||
|
ce qui résulte en une exécution immédiate du SQL <literal>INSERT</literal>, <literal>UPDATE</literal>
|
||||||
|
ou <literal>DELETE</literal> respectif. De là, elles ont des sémantiques tres différentes des
|
||||||
|
opérations <literal>save()</literal>, <literal>saveOrUpdate()</literal>
|
||||||
|
et <literal>delete()</literal> définies par l'interface <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="batch-direct" revision="2">
|
||||||
|
<title>Opérations de style DML</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Comme déjà discuté avant, le mapping objet/relationnel automatique et transparent
|
||||||
|
est intéressé par la gestion de l'état de l'objet. Ceci implique que l'état de l'objet
|
||||||
|
est disponible en mémoire, d'où manipuler (en utilisant des expressions du langage de
|
||||||
|
manipulation de données - <literal>Data Manipulation Language</literal> (DML) - SQL)
|
||||||
|
les données directement dans la base n'affectera pas l'état en mémoire. Pourtant, Hibernate
|
||||||
|
fournit des méthodes pour l'exécution d'expression DML de style SQL lesquelles sont
|
||||||
|
réalisées à travers le langage de requête d'Hibernate (<xref linkend="queryhql">HQL</xref>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La pseudo-syntaxe pour les expressions <literal>UPDATE</literal> et <literal>DELETE</literal>
|
||||||
|
est : <literal>( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?</literal>.
|
||||||
|
Certains points sont à noter :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Dans la clause from, le mot-clef FROM est optionnel
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Il ne peut y avoir qu'une seule entité nommée dans la clause from ; elle peut
|
||||||
|
optionnellement avoir un alias. Si le nom de l'entité a un alias, alors
|
||||||
|
n'importe quelle référence de propriété doit être qualifiée en ayant un alias ;
|
||||||
|
si le nom de l'entité n'a pas d'alias, alors il est illégal pour n'importe quelle
|
||||||
|
référence de propriété d'être qualifiée.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Aucune jointure (implicite ou explicite) ne peut être spécifiée dans une requête HQL.
|
||||||
|
Les sous-requêtes peuvent être utilisées dans la clause where ; les sous-requêtes,
|
||||||
|
elles-mêmes, peuvent contenir des jointures.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
La clause where est aussi optionnelle.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Par exemple, pour exécuter un <literal>UPDATE</literal> HQL, utilisez la méthode
|
||||||
|
<literal>Query.executeUpdate()</literal> (la méthode est données pour ceux
|
||||||
|
qui sont familiers avec <literal>PreparedStatement.executeUpdate()</literal> de
|
||||||
|
JDBC) :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
|
||||||
|
// ou String hqlUpdate = "update Customer set name = :newName where name = :oldName";
|
||||||
|
int updatedEntities = s.createQuery( hqlUpdate )
|
||||||
|
.setString( "newName", newName )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour exécuter un <literal>DELETE</literal> HQL, utilisez la même méthode
|
||||||
|
<literal>Query.executeUpdate()</literal> :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlDelete = "delete Customer c where c.name = :oldName";
|
||||||
|
// or String hqlDelete = "delete Customer where name = :oldName";
|
||||||
|
int deletedEntities = s.createQuery( hqlDelete )
|
||||||
|
.setString( "oldName", oldName )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La valeur du <literal>int</literal> retourné par la méthode <literal>Query.executeUpdate()</literal>
|
||||||
|
indique le nombre d'entités affectées par l'opération. Considérez que cela peut ou pas
|
||||||
|
corréler le nombre de lignes affectés dans la base de données. Une opération HQL
|
||||||
|
pourrait entraîner l'exécution de multiples expressions SQL réelles, pour des classes
|
||||||
|
filles mappées par jointure (NdT: join-subclass), par exemple. Le nombre retourné
|
||||||
|
indique le nombre d'entités réelles affectées par l'expression. Retour à l'exemple de la
|
||||||
|
classe fille mappée par jointure, un effacement d'une des classes filles peut réellement
|
||||||
|
entraîner des suppressions pas seulement dans la table qui mappe la classe fille, mais
|
||||||
|
aussi dans la table "racine" et potentillement dans les tables des classes filles plus bas
|
||||||
|
dans la hiérarchie d'héritage.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La pseudo-syntaxe pour l'expression <literal>INSERT</literal> est :
|
||||||
|
<literal>INSERT INTO EntityName properties_list select_statement</literal>. Quelques
|
||||||
|
points sont à noter :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Seule la forme INSERT INTO ... SELECT ... est supportée ; pas la forme INSERT INTO ... VALUES ... .
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
La properties_list est analogue à la <literal>spécification de la colonne</literal>
|
||||||
|
|
||||||
|
The properties_list is analogous to the <literal>column speficiation</literal> dans
|
||||||
|
l'expression SQL <literal>INSERT</literal>. Pour les entités impliquées dans
|
||||||
|
un héritage mappé, seules les propriétés directement définies à ce niveau de classe
|
||||||
|
donné peuvent être utilisées dans properties_list. Les propriétés de la classe mère
|
||||||
|
ne sont pas permises ; et les propriétés des classes filles n'ont pas de sens. En
|
||||||
|
d'autres mots, les expressions <literal>INSERT</literal> par nature non polymorphiques.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
select_statement peut être n'importe quelle requête de sélection HQl valide, avec
|
||||||
|
l'avertissement que les types de retour doivent correspondre aux types attendus par
|
||||||
|
l'insertion. Actuellement, c'est vérifié durant la compilation de la requête plutôt
|
||||||
|
que la vérification soit reléguée à la base de données. Notez cependant que cela
|
||||||
|
pourrait poser des problèmes entre les <literal>Type</literal>s d'Hibernate qui
|
||||||
|
sont <emphasis>équivalents</emphasis> opposé à <emphasis>égaux</emphasis>. Cela
|
||||||
|
pourrait poser des problèmes avec des disparités entre une propriété définie
|
||||||
|
comme un <literal>org.hibernate.type.DateType</literal> et une propriété définie
|
||||||
|
comme un <literal>org.hibernate.type.TimestampType</literal>, même si la base de données
|
||||||
|
ne ferait pas de distinction ou ne serait pas capable de gérer la conversion.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Pour la propriéte id, l'expression d'insertion vous donne deux options. Vous
|
||||||
|
pouvez soit spécifier explicitement la propriété id dans properties_list
|
||||||
|
(auquel cas sa valeur est extraite de l'expression de sélection correspondante),
|
||||||
|
soit l'omettre de properties_list (auquel cas une valeur générée est utilisée).
|
||||||
|
Cette dernière option est seulement disponible en utilisant le générateur d'identifiant
|
||||||
|
qui opère dans la base de données ; tenter d'utiliser cette option avec n'importe quel
|
||||||
|
type de générateur "en mémoire" causera une exception durant l'analyse. Notez
|
||||||
|
que pour les buts de cette discussion, les générateurs "en base" sont considérés
|
||||||
|
être <literal>org.hibernate.id.SequenceGenerator</literal> (et ses classes filles)
|
||||||
|
et n'importe quelles implémentations de
|
||||||
|
<literal>org.hibernate.id.PostInsertIdentifierGenerator</literal>.
|
||||||
|
L'exception la plus notable ici est <literal>org.hibernate.id.TableHiLoGenerator</literal>,
|
||||||
|
qu ne peut pas être utilisée parce qu'il ne propose pas un moyen de d'exposer ses valeurs
|
||||||
|
par un select.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Pour des propriétés mappées comme <literal>version</literal> ou <literal>timestamp</literal>,
|
||||||
|
l'expression d'insertion vous donne deux options. Vous pouvez soit spécifier la propriété dans
|
||||||
|
properties_list (auquel cas sa valeur est extraite des expressions select correspondantes),
|
||||||
|
soit l'omettre de properties_list (auquel cas la <literal>valeur de graine</literal>
|
||||||
|
(NdT : seed value) définie par le <literal>org.hibernate.type.VersionType</literal> est utilisée).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un exemple d'exécution d'une expression <literal>INSERT</literal> HQL :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sessionFactory.openSession();
|
||||||
|
Transaction tx = session.beginTransaction();
|
||||||
|
|
||||||
|
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
|
||||||
|
int createdEntities = s.createQuery( hqlInsert )
|
||||||
|
.executeUpdate();
|
||||||
|
tx.commit();
|
||||||
|
session.close();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,240 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="best-practices" revision="3">
|
||||||
|
<title>Meilleures pratiques</title>
|
||||||
|
|
||||||
|
<variablelist spacing="compact">
|
||||||
|
<varlistentry>
|
||||||
|
<term>Découpez finement vos classes et mappez les en utilisant <literal><component></literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Utilisez une classe <literal>Adresse</literal> pour encapsuler <literal>Rue</literal>,
|
||||||
|
<literal>Region</literal>, <literal>CodePostal</literal>.
|
||||||
|
Ceci permet la réutilisation du code et simplifie la maintenance.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Déclarez des propriétés d'identifiants dans les classes persistantes.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate rend les propriétés d'identifiants optionnelles. Il existe beaucoup de raisons
|
||||||
|
pour lesquelles vous devriez les utiliser. Nous recommandons que vous utilisiez des identifiants
|
||||||
|
techniques (générés, et sans connotation métier).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Identifiez les clefs naturelles.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Identifiez les clefs naturelles pour toutes les entités, et mappez les avec
|
||||||
|
<literal><natural-id></literal>. Implémentez <literal>equals()</literal> et
|
||||||
|
<literal>hashCode()</literal> pour comparer les propriétés qui composent la clef naturelle.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Placez chaque mapping de classe dans son propre fichier.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
N'utilisez pas un unique document de mapping. Mappez <literal>com.eg.Foo</literal> dans
|
||||||
|
le fichier <literal>com/eg/Foo.hbm.xml</literal>. Cela prend tout son sens lors
|
||||||
|
d'un travail en équipe.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Chargez les mappings comme des ressources.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Déployez les mappings en même temps que les classes qu'ils mappent.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Pensez à externaliser les chaînes de caractères.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Ceci est une bonne habitude si vos requêtes appellent des fonctions SQL qui ne sont
|
||||||
|
pas au standard ANSI. Cette externalisation dans les fichiers de mapping rendra votre
|
||||||
|
application plus portable.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Utilisez les variables "bindées".</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Comme en JDBC, remplacez toujours les valeurs non constantes par "?". N'utilisez jamais
|
||||||
|
la manipulation des chaînes de caractères pour remplacer des valeurs non constantes dans
|
||||||
|
une requête ! Encore mieux, utilisez les paramètres nommés dans les requêtes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Ne gérez pas vous même les connexions JDBC.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate laisse l'application gérer les connexions JDBC. Vous ne devriez gérer vos connexions
|
||||||
|
qu'en dernier recours. Si vous ne pouvez pas utiliser les systèmes de connexions livrés,
|
||||||
|
réfléchissez à l'idée de fournir votre propre implémentation de <literal>org.hibernate.connection.ConnectionProvider</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Pensez à utiliser les types utilisateurs.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Supposez que vous ayez une type Java, de telle bibliothèque, qui a besoin d'être persisté mais
|
||||||
|
qui ne fournit pas les accesseurs nécessaires pour le mapper comme composant. Vous devriez
|
||||||
|
implémenter
|
||||||
|
<literal>org.hibernate.UserType</literal>.Cette approche libère le code de l'application
|
||||||
|
de l'implémentation des transformations vers / depuis les types Hibernate.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Utilisez du JDBC pur dans les goulets d'étranglement.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Dans certaines parties critiques de votre système d'un point de vue performance, quelques opérations
|
||||||
|
peuvent tirer partie d'un appel JDBC natif.
|
||||||
|
Mais attendez de <emphasis>savoir</emphasis>
|
||||||
|
que c'est un goulet d'étranglement. Ne supposez jamais qu'un appel JDBC sera forcément plus
|
||||||
|
rapide. Si vous avez besoin d'utiliser JDBC directement, ouvrez une <literal>Session</literal>
|
||||||
|
Hibernate et utilisez la connexion SQL sous-jacente. Ainsi vous pourrez utiliser la même stratégie
|
||||||
|
de transation et la même gestion des connexions.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Comprendre le flush de <literal>Session</literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
De temps en temps la Session synchronise ses états persistants avec la base de données.
|
||||||
|
Les performances seront affectées si ce processus arrive trop souvent. Vous pouvez parfois
|
||||||
|
minimiser les flush non nécessaires en désactivant le flush automatique ou même en changeant
|
||||||
|
l'ordre des opérations menées dans une transaction particulière.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Dans une architecture à trois couches, pensez à utiliser <literal>saveOrUpdate()</literal>.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Quand vous utilisez une architecture à base de servlet / session bean, vous pourriez passer
|
||||||
|
des objets chargés dans le bean session vers et depuis la couche servlet / JSP. Utilisez
|
||||||
|
une nouvelle session pour traiter chaque requête.
|
||||||
|
Utilisez <literal>Session.merge()</literal> ou <literal>Session.saveOrUpdate()</literal> pour
|
||||||
|
synchroniser les objets avec la base de données.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Dans une architecture à deux couches, pensez à utiliser la déconnexion de session.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Les transactions de bases de données doivent être aussi courtes que possible
|
||||||
|
pour une meilleure montée en charge.Cependant, il est souvent nécessaire d'implémenter
|
||||||
|
de longues <emphasis>transactions applicatives</emphasis>, une simple unité de travail du point de vue de
|
||||||
|
l'utilisateur. Une transaction applicative
|
||||||
|
peut s'étaler sur plusieurs cycles de requêtes/réponses du client.
|
||||||
|
Il est commun d'utiliser des objets détachés pour implémenter des transactions applicatives.
|
||||||
|
Une alternative, extrêmement appropriée dans une architecture à 2 couches, est de
|
||||||
|
maintenir un seul contact de persistance ouvert (session) pour toute la durée de vie
|
||||||
|
de la transaction applicative et simplement se déconnecter de la connexion JDBC à la fin de chaque requête,
|
||||||
|
et se reconnecter au début de la requête suivante. Ne partagez jamais une seule
|
||||||
|
session avec plus d'une transaction applicative, ou vous travaillerez avec des
|
||||||
|
données périmées.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Considérez que les exceptions ne sont pas rattrapables.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Il s'agit plus d'une pratique obligatoire que d'une "meilleure pratique". Quand une exception
|
||||||
|
intervient, il faut faire un rollback de la <literal>Transaction</literal> et
|
||||||
|
fermer la <literal>Session</literal>.
|
||||||
|
Sinon, Hibernate ne peut garantir l'intégrité des états persistants en mémoire. En particulier,
|
||||||
|
n'utilisez pas <literal>Session.load()</literal> pour déterminer si une instance avec un identifiant
|
||||||
|
donné existe en base de données, utilisez <literal>Session.get()</literal> ou un requête.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Préférez le chargement tardif des associations.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Utilisez le chargement complet avec modération.
|
||||||
|
Utilisez les proxies et les collections chargées tardivement
|
||||||
|
pour la plupart des associations vers des classes qui ne sont pas susceptibles
|
||||||
|
d'être complètement retenues dans le cache de second niveau.
|
||||||
|
Pour les assocations de classes en cache, où il y a une extrêmement
|
||||||
|
forte probabilité que l'élément soit en cache, désactivez explicitement le chargement
|
||||||
|
par jointures ouvertes en utilisant <literal>outer-join="false"</literal>.
|
||||||
|
Lorsqu'un chargement par jointure ouverte est approprié pour un cas d'utilisation
|
||||||
|
particulier, utilisez une requête avec un <literal>left join fetch</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Utilisez le pattern <emphasis>d'une ouverture de session dans une vue</emphasis>,
|
||||||
|
ou une <emphasis>phase d'assemblage</emphasis> disciplinée pour éviter des problèmes
|
||||||
|
avec des données non rapatriées.
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Hibernate libère les développeurs de l'écriture fastidieuse des <emphasis>objets de transfert
|
||||||
|
de données (NdT : Data Transfer Objects)</emphasis> (DTO). Dans une architecture EJB traditionnelle,
|
||||||
|
les DTOs ont deux buts : premièrement, ils contournent le problème des "entity bean" qui ne sont pas
|
||||||
|
sérialisables ; deuxièmement, ils définissent implicitement une phase d'assemblage où toutes les
|
||||||
|
données utilisées par la vue sont rapatriées et organisées dans les DTOs avant de retourner sous le
|
||||||
|
contrôle de la couche de présentation. Hibernate élimine le premier but. Pourtant, vous aurez encore
|
||||||
|
besoin d'une phase d'assemblage (pensez vos méthodes métier comme ayant un contrat strict avec la
|
||||||
|
couche de présentation à propos de quelles données sont disponibles dans les objets détachés)
|
||||||
|
à moins que vous soyez préparés à garder le contexte de
|
||||||
|
persistance (la session) ouvert à travers tout le processus de rendu de la vue.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Pensez à abstraite votre logique métier d'Hibernate.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Cachez le mécanisme d'accès aux données (Hibernate) derrière une interface. Combinez les patterns
|
||||||
|
<emphasis>DAO</emphasis> et <emphasis>Thread Local Session</emphasis>. Vous pouvez même avoir quelques
|
||||||
|
classes persistées par du JDBC pur, associées à Hibernate via un <literal>UserType</literal> (ce conseil est
|
||||||
|
valable pour des applications de taille respectables ; il n'est pas valable pour une application
|
||||||
|
avec cinq tables).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>N'utilisez pas d'associations de mapping exotiques.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
De bons cas d'utilisation pour de vraies associations plusieurs-vers-plusieurs
|
||||||
|
sont rares. La plupart du temps vous avez besoin d'informations additionnelles
|
||||||
|
stockées dans la table d'association.
|
||||||
|
Dans ce cas, il est préférable d'utiliser deux associations un-vers-plusieurs vers une classe
|
||||||
|
de liaisons intermédiaire. En fait, nous pensons que la plupart des associations sont
|
||||||
|
de type un-vers-plusieurs ou plusieurs-vers-un, vous devez être très attentifs lorsque
|
||||||
|
vous utilisez autre chose et vous demander si c'est vraiment nécessaire.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Préférez les associations bidirectionnelles.</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Les associations unidirectionnelles sont plus difficiles à questionner.
|
||||||
|
Dans une grande application, la plupart des associations devraient être navigables dans les deux directions dans les requêtes.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,402 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<chapter id="components">
|
||||||
|
<title>Mapping de composants</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La notion de <emphasis>composants</emphasis> est réutilisé dans différents contextes,
|
||||||
|
avec différents objectifs, à travers Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="components-dependentobjects" revision="2" >
|
||||||
|
<title>Objects dépendants</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le composant est un objet inclu dans un autre qui est sauvegardé comme une valeur, et
|
||||||
|
non pas comme une entité.
|
||||||
|
Le composant fait référence à la notion (au sens objet) de composition
|
||||||
|
(et non pas de composant au sens d'architecture de composants).
|
||||||
|
Par exemple on pourrait modélisé l'objet personne de cette façon:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Person {
|
||||||
|
private java.util.Date birthday;
|
||||||
|
private Name name;
|
||||||
|
private String key;
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
private void setKey(String key) {
|
||||||
|
this.key=key;
|
||||||
|
}
|
||||||
|
public java.util.Date getBirthday() {
|
||||||
|
return birthday;
|
||||||
|
}
|
||||||
|
public void setBirthday(java.util.Date birthday) {
|
||||||
|
this.birthday = birthday;
|
||||||
|
}
|
||||||
|
public Name getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
public void setName(Name name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
......
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Name {
|
||||||
|
char initial;
|
||||||
|
String first;
|
||||||
|
String last;
|
||||||
|
public String getFirst() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
void setFirst(String first) {
|
||||||
|
this.first = first;
|
||||||
|
}
|
||||||
|
public String getLast() {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
void setLast(String last) {
|
||||||
|
this.last = last;
|
||||||
|
}
|
||||||
|
public char getInitial() {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
void setInitial(char initial) {
|
||||||
|
this.initial = initial;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Maintenant <literal>Name</literal> peut-être sauvegardé comme un composant de
|
||||||
|
<literal>Person</literal>. Remarquer que <literal>Name</literal> définit des methodes
|
||||||
|
d'accès et de modification pour ses propriétés persistantes, mais il n'a pas besoin
|
||||||
|
des interfaces ou des propriétés d'identification ( par exemple getId() ) qui sont propres aux entités.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous serions alors amené à mapper ce composant de cette façon:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name"> <!-- class attribute optional -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La table person aurai les colonnes <literal>pid</literal>,
|
||||||
|
<literal>birthday</literal>,
|
||||||
|
<literal>initial</literal>,
|
||||||
|
<literal>first</literal> and
|
||||||
|
<literal>last</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Comme tous les types valeurs, les composants ne supportent pas les références partagés.
|
||||||
|
En d'autres mots, deux instances de person peuvent avoir un même nom, mais ces noms sont
|
||||||
|
indépendants, ils peuvent être identiques si on les compare par valeur mais ils représentent
|
||||||
|
deux objets distincts en mémoire. La notion de nullité pour un composant est
|
||||||
|
<emphasis>ad hoc</emphasis>. Quand il recharge l'objet qui contient le composant, Hibernate
|
||||||
|
supposera que si tous les champs du composants sont nuls alors le composant sera positionné
|
||||||
|
à la valeur null. Ce choix programmatif devrait être satisfaisant dans la plupart des cas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les propriétés d'un composant peuvent être de tous les types qu'Hibernate supporte habituellement
|
||||||
|
(collections, many-to-one associations, autres composants, etc). Les composants inclus ne doivent <emphasis>pas</emphasis>
|
||||||
|
être vus comme quelque chose d'exotique. Hibernate a été conçu pour supporter un modèle objet très granulaire.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le <literal><component></literal> peut inclure dans la liste de ses propriétés
|
||||||
|
une référence au <literal><parent></literal> conteneur.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
||||||
|
<id name="Key" column="pid" type="string">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
<property name="birthday" type="date"/>
|
||||||
|
<component name="Name" class="eg.Name" unique="true">
|
||||||
|
<parent name="namedPerson"/> <!-- référence arrière à Person -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</component>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-incollections" revision="1">
|
||||||
|
<title>Collection d'objets dépendants</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les collections d'objets dépendants sont supportés (exemple: un tableau de type
|
||||||
|
<literal>Name</literal>). Déclarer la collection de composants en remplaçant le tag <literal><element></literal>
|
||||||
|
par le tag <literal><composite-element></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="someNames" table="some_names" lazy="true">
|
||||||
|
<key column="id"/>
|
||||||
|
<composite-element class="eg.Name"> <!-- class attribute required -->
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="first"/>
|
||||||
|
<property name="last"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Remarque: Si vous définissez un <literal>Set</literal> d'élément composite,
|
||||||
|
il est très important d'implémenter la méthode <literal>equals()</literal> et
|
||||||
|
<literal>hashCode()</literal> correctement.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les élements composite peuvent aussi contenir des composants mais pas des collections.
|
||||||
|
Si votre élément composite contient aussi des composants, utilisez l'élément <literal><nested-composite-element></literal>
|
||||||
|
. Une collections de composants qui ccontiennent eux-mêmes des composants est un cas très exotique.
|
||||||
|
A ce stade demandez-vous si une association un-à-plusieurs ne serait pas plus approprié.
|
||||||
|
Essayez de re remodeler votre élément composite comme une entité ( Dans ce cas même si le modèle
|
||||||
|
Java est le même la logique de persitence et de relation sont tout de même différentes)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Remarque, le mapping d'éléments composites ne supporte pas la nullité des
|
||||||
|
propriétés lorsqu'on utilise un <literal><set></literal>. Hibernate
|
||||||
|
lorsqu'il supprime un objet utilise chaque colonne pour identifier un objet
|
||||||
|
(on ne peut pas utiliser des clés primaires distinctes dans une table d'éléments composites),
|
||||||
|
ce qui n'est pas possible avec des valeurs nulles. Vous devez donc choisir d'interdire la nullité
|
||||||
|
des propriétés d'un élément composite ou choisir un autre type de collection comme :
|
||||||
|
<literal><list></literal>, <literal><map></literal>,
|
||||||
|
<literal><bag></literal> ou <literal><idbag></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un cas particulier d'élément composite est un élément composite qui inclut un élément
|
||||||
|
<literal><many-to-one></literal>. Un mapping comme celui-ci
|
||||||
|
vous permet d'associer les colonnes d'une table d'association plusieurs à plusieurs (many-to-many)
|
||||||
|
à la classse de l'élément composite. L'exemple suivant est une association plusieurs à plusieurs
|
||||||
|
de <literal>Order</literal> à <literal>Item</literal> à
|
||||||
|
<literal>purchaseDate</literal>, <literal>price</literal> et
|
||||||
|
<literal>quantity</literal> sont des propriétés de l'association.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.Purchase">
|
||||||
|
<property name="purchaseDate"/>
|
||||||
|
<property name="price"/>
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional -->
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Bien sûr, il ne peut pas y avoir de référence à l'achat (purchase) depuis l'article (item), pour
|
||||||
|
pouvoir naviguer de façon bidirectionnelle dans l'association. N'oubliez pas que les composants
|
||||||
|
sont de type valeurs et n'autorise pas les références partagées.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>Même les associations ternaires ou quaternaires sont possibles:</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="eg.Order" .... >
|
||||||
|
....
|
||||||
|
<set name="purchasedItems" table="purchase_items" lazy="true">
|
||||||
|
<key column="order_id">
|
||||||
|
<composite-element class="eg.OrderLine">
|
||||||
|
<many-to-one name="purchaseDetails class="eg.Purchase"/>
|
||||||
|
<many-to-one name="item" class="eg.Item"/>
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les éléments composites peuvent apparaître dans les requêtes en utilisant
|
||||||
|
la même syntaxe que associations
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-asmapindex">
|
||||||
|
<title>Utiliser les composants comme index de map</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
l'élément <literal><composite-map-key></literal>
|
||||||
|
vous permet d'utiliser une classe de composant comme indice de
|
||||||
|
<literal>Map</literal>. Assurez-vous d'avoir surdéfini
|
||||||
|
<literal>hashCode()</literal> et <literal>equals()</literal> dans la
|
||||||
|
classe du composant.
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-compositeid" revision="1">
|
||||||
|
<title>Utiliser un composant comme identifiant</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez utiliser un composant comme identifiant d'une entité.
|
||||||
|
Mais pour cela la classe du composant doit respecter certaines règles.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Elle doit implémenter <literal>java.io.Serializable</literal>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Elle doit redéfinir <literal>equals()</literal> et
|
||||||
|
<literal>hashCode()</literal>, de façon cohérente avec le
|
||||||
|
fait qu'elle définit une clé composite dans la base de
|
||||||
|
données.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>
|
||||||
|
Remarque: avec hibernate3, la seconde règle n'est plus absolument
|
||||||
|
necessaire mais faîtes le quand même.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous ne pouvez pas utiliser de <literal>IdentifierGenerator</literal> pour générer
|
||||||
|
une clé composite, l'application devra définir elle même ses propres identifiants.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Utiliser l'élément <literal><composite-id></literal> (en incluant l'élément
|
||||||
|
<literal><key-property></literal>) à la place de l'habituel déclaration
|
||||||
|
<literal><id></literal>. Par exemple la classe
|
||||||
|
<literal>OrderLine</literal> qui dépend de la clé primaire
|
||||||
|
(composite) de <literal>Order</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
|
||||||
|
<composite-id name="id" class="OrderLineId">
|
||||||
|
<key-property name="lineId"/>
|
||||||
|
<key-property name="orderId"/>
|
||||||
|
<key-property name="customerId"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="name"/>
|
||||||
|
|
||||||
|
<many-to-one name="order" class="Order"
|
||||||
|
insert="false" update="false">
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>
|
||||||
|
....
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Maintenant toutes clés étrangères référençant la table <literal>OrderLine</literal>
|
||||||
|
devra aussi être composite. Vous devez en tenir compte lorsque vous écrivez vos mapping d'association pour les autres classes.
|
||||||
|
Une association à <literal>OrderLine</literal> devrait être mappé de la façon suivante :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="orderLine" class="OrderLine">
|
||||||
|
<!-- the "class" attribute is optional, as usual -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-one>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(Remarque: l'élément <literal><column></literal> est une alternative à l'attribut
|
||||||
|
<literal>column</literal> que l'on utilise partout.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une association <literal>plusieurs-à-plusieurs</literal> (many-to-many) à <literal>OrderLine</literal>
|
||||||
|
utilisera aussi une clé étrangère composite:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="undeliveredOrderLines">
|
||||||
|
<key column name="warehouseId"/>
|
||||||
|
<many-to-many class="OrderLine">
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</many-to-many>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La collection des <literal>OrderLine</literal>s dans <literal>Order</literal>
|
||||||
|
utilisera:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="orderLines" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="OrderLine"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(L'élément <literal><one-to-many></literal>, comme d'habitude, ne déclare pas de colonne.)
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si <literal>OrderLine</literal> lui-même possède une collection, celle-ci aura aussi
|
||||||
|
une clé composite étrangère.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="OrderLine">
|
||||||
|
....
|
||||||
|
....
|
||||||
|
<list name="deliveryAttempts">
|
||||||
|
<key> <!-- a collection inherits the composite key type -->
|
||||||
|
<column name="lineId"/>
|
||||||
|
<column name="orderId"/>
|
||||||
|
<column name="customerId"/>
|
||||||
|
</key>
|
||||||
|
<list-index column="attemptId" base="1"/>
|
||||||
|
<composite-element class="DeliveryAttempt">
|
||||||
|
...
|
||||||
|
</composite-element>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="components-dynamic" revision="1">
|
||||||
|
<title>Composant Dynamique</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez même mapper une propriété de type <literal>Map</literal>:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<dynamic-component name="userAttributes">
|
||||||
|
<property name="foo" column="FOO"/>
|
||||||
|
<property name="bar" column="BAR"/>
|
||||||
|
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
|
||||||
|
</dynamic-component>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La sémantique de l'association à un <literal><dynamic-component></literal>
|
||||||
|
est identique à celle que l'on utilise pour les composants.
|
||||||
|
L'avantage de ce type de mapping est qu'il pemet de déterminer les véritables propriétés
|
||||||
|
du bean au moment su déploiement en éditant simplement le document de mapping.
|
||||||
|
La manipulation du document de mapping pendant l'execution de l'application est aussi
|
||||||
|
possible en utilisant un parser DOM. Il ya même mieux, vous pouvez accéder (et changer)
|
||||||
|
le metamodel de configuration d'hibernate en utilisant l'objet <literal>Configuration</literal>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,263 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="events">
|
||||||
|
<title>Les intercepteurs et les événements</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il est souvent utile pour l'application de réagir à certains événements
|
||||||
|
qui surviennent dans Hibernate. Cela autorise l'implémentation de certaines sortes de
|
||||||
|
fonctionnalités génériques, et d'extensions de fonctionnalités d'Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-interceptors" revision="2">
|
||||||
|
<title>Intercepteurs</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'interface <literal>Interceptor</literal> fournit des "callbacks" de la session vers l'application
|
||||||
|
et permettent à l'application de consulter et/ou de manipuler des propriétés
|
||||||
|
d'un objet persistant avant qu'il soit sauvegardé, mis à jour, supprimé ou chargé.
|
||||||
|
Une utilisation possible de cette fonctionnalité est de tracer l'accès à l'information.
|
||||||
|
Par exemple, l'<literal>Interceptor</literal> suivant positionne
|
||||||
|
<literal>createTimestamp</literal> quand un <literal>Auditable</literal> est créé
|
||||||
|
et met à jour la propriété <literal>lastUpdateTimestamp</literal> quand un
|
||||||
|
<literal>Auditable</literal> est mis à jour.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez soit implémenter <literal>Interceptor</literal> directement ou (mieux)
|
||||||
|
étendre <literal>EmptyInterceptor</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package org.hibernate.test;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.hibernate.EmptyInterceptor;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
public class AuditInterceptor extends EmptyInterceptor {
|
||||||
|
|
||||||
|
private int updates;
|
||||||
|
private int creates;
|
||||||
|
private int loads;
|
||||||
|
|
||||||
|
public void onDelete(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
// ne fait rien
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFlushDirty(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] currentState,
|
||||||
|
Object[] previousState,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
updates++;
|
||||||
|
for ( int i=0; i < propertyNames.length; i++ ) {
|
||||||
|
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
currentState[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
loads++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if ( entity instanceof Auditable ) {
|
||||||
|
creates++;
|
||||||
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
||||||
|
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||||||
|
state[i] = new Date();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void postFlush(Iterator entities) {
|
||||||
|
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterTransactionCompletion(Transaction tx) {
|
||||||
|
if ( tx.wasCommitted() ) {
|
||||||
|
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
|
||||||
|
}
|
||||||
|
updates=0;
|
||||||
|
creates=0;
|
||||||
|
loads=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'intercepteur doit être spécifié quand une session est créée.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez aussi mettre un intercepteur au niveau global, en utilisant l'objet <literal>Configuration</literal>.
|
||||||
|
Dans ce cas, l'intercepteur doit être "threadsafe".
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-events" revision="3">
|
||||||
|
<title>Système d'événements</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si vous devez réagir à des événements particuliers dans votre couche de persistance,
|
||||||
|
vous pouvez aussi utiliser l'architecture d'<emphasis>événements</emphasis> d'Hibernate3.
|
||||||
|
Le système d'événements peut être utilisé en supplément ou en remplacement des interceptors.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Essentiellement toutes les méthodes de l'interface <literal>Session</literal> sont corrélées à
|
||||||
|
un événement. Vous avez un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
|
||||||
|
(consultez la DTD du fichier de configuration XML ou le paquet <literal>org.hibernate.event</literal>
|
||||||
|
pour avoir la liste complète des types d'événement définis).
|
||||||
|
Quand une requête est faite à partir d'une de ces méthodes, la
|
||||||
|
<literal>Session</literal> Hibernate génère un événement approprié et le passe
|
||||||
|
au listener configuré pour ce type.
|
||||||
|
Par défaut, ces listeners implémentent le même traitement dans lequel ces méthodes
|
||||||
|
aboutissent toujours.
|
||||||
|
Cependant, vous êtes libre d'implémenter une version personnalisée d'une de ces
|
||||||
|
interfaces de listener (c'est-à-dire, le <literal>LoadEvent</literal> est traité par
|
||||||
|
l'implémentation de l'interface <literal>LoadEventListener</literal> déclarée), dans
|
||||||
|
quel cas leur implémentation devrait être responsable du traitement des
|
||||||
|
requêtes <literal>load()</literal> faites par la <literal>Session</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les listeners devraient effectivement être considérés comme des singletons ; dans le sens
|
||||||
|
où ils sont partagés entre des requêtes, et donc ne devraient pas sauvegarder des états
|
||||||
|
de variables d'instance.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un listener personnalisé devrait implémenter l'interface appropriée pour l'événement
|
||||||
|
qu'il veut traiter et/ou étendre une des classes de base (ou même l'événement prêt à
|
||||||
|
l'emploi utilisé par Hibernate comme ceux déclarés non-finaux à cette intention). Les
|
||||||
|
listeners personnalisés peuvent être soit inscrits par programmation à travers l'objet
|
||||||
|
<literal>Configuration</literal>, ou spécifiés la configuration XML d'Hibernate
|
||||||
|
(la configuration déclarative à travers le fichier de propriétés n'est pas supportée).
|
||||||
|
Voici un exemple de listener personnalisé pour l'événement de chargement :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
|
||||||
|
// C'est une simple méthode définie par l'interface LoadEventListener
|
||||||
|
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||||||
|
throws HibernateException {
|
||||||
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||||||
|
throw MySecurityException("Unauthorized access");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous avez aussi besoin d'une entrée de configuration disant à Hibernate d'utiliser
|
||||||
|
ce listener en plus du listener par défaut :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-configuration>
|
||||||
|
<session-factory>
|
||||||
|
...
|
||||||
|
<event type="load">
|
||||||
|
<listener class="com.eg.MyLoadListener"/>
|
||||||
|
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
|
||||||
|
</event>
|
||||||
|
</session-factory>
|
||||||
|
</hibernate-configuration>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez aussi l'inscrire par programmation :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||||||
|
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
|
||||||
|
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les listeners inscrits déclarativement ne peuvent pas partager d'instances. Si le même
|
||||||
|
nom de classe est utilisée dans plusieurs éléments <literal><listener/></literal>,
|
||||||
|
chaque référence sera une instance distincte de cette classe. Si vous avez besoin de la
|
||||||
|
faculté de partager des instances de listener entre plusieurs types de listener, vous devez
|
||||||
|
utiliser l'approche d'inscription par programmation.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pourquoi implémenter une interface et définir le type spécifique durant la configuration ?
|
||||||
|
Une implémentation de listener pourrait implémenter plusieurs interfaces de listener
|
||||||
|
d'événements. Avoir en plus le type défini durant l'inscription rend plus facile
|
||||||
|
l'activation ou la désactivation pendant la configuration.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-decl-security" revision="2">
|
||||||
|
<title>Sécurité déclarative d'Hibernate</title>
|
||||||
|
<para>
|
||||||
|
Généralement, la sécurité déclarative dans les applications Hibernate est gérée dans la
|
||||||
|
couche de session. Maintenant, Hibernate3 permet à certaines actions d'être approuvées
|
||||||
|
via JACC, et autorisées via JAAS. Cette fonctionnalité optionnelle est construite
|
||||||
|
au dessus de l'architecture d'événements.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
D'abord, vous devez configurer les listeners d'événements appropriés pour permettre
|
||||||
|
l'utilisation d'autorisations JAAS.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||||||
|
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||||||
|
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||||||
|
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que <literal><listener type="..." class="..."/></literal> est juste un raccourci
|
||||||
|
pour <literal><event type="..."><listener class="..."/></event></literal>
|
||||||
|
quand il y a exactement un listener pour un type d'événement particulier.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ensuite, toujours dans <literal>hibernate.cfg.xml</literal>, lier les permissions aux rôles :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||||||
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les noms de rôle sont les rôles compris par votre fournisseur JAAC.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,657 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="example-mappings">
|
||||||
|
<title>Exemple : quelques mappings</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ce chapitre montre quelques mappings plus complexes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-emp">
|
||||||
|
<title>Employeur/Employé (Employer/Employee)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le modèle suivant de relation entre <literal>Employer</literal> et
|
||||||
|
<literal>Employee</literal> utilise une vraie classe entité (<literal>Employment</literal>)
|
||||||
|
pour représenter l'association. On a fait cela parce qu'il peut y avoir plus d'une période
|
||||||
|
d'emploi pour les deux mêmes parties. Des composants sont utilisés pour modéliser les
|
||||||
|
valeurs monétaires et les noms des employés.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/EmployerEmployee.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Voici un document de mapping possible :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Employer" table="employers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employer_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employment" table="employment_periods">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employment_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="startDate" column="start_date"/>
|
||||||
|
<property name="endDate" column="end_date"/>
|
||||||
|
|
||||||
|
<component name="hourlyRate" class="MonetaryAmount">
|
||||||
|
<property name="amount">
|
||||||
|
<column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
|
||||||
|
</property>
|
||||||
|
<property name="currency" length="12"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<many-to-one name="employer" column="employer_id" not-null="true"/>
|
||||||
|
<many-to-one name="employee" column="employee_id" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Employee" table="employees">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="sequence">
|
||||||
|
<param name="sequence">employee_id_seq</param>
|
||||||
|
</generator>
|
||||||
|
</id>
|
||||||
|
<property name="taxfileNumber"/>
|
||||||
|
<component name="name" class="Name">
|
||||||
|
<property name="firstName"/>
|
||||||
|
<property name="initial"/>
|
||||||
|
<property name="lastName"/>
|
||||||
|
</component>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Et voici le schéma des tables générées par <literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table employers (
|
||||||
|
id BIGINT not null,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employment_periods (
|
||||||
|
id BIGINT not null,
|
||||||
|
hourly_rate NUMERIC(12, 2),
|
||||||
|
currency VARCHAR(12),
|
||||||
|
employee_id BIGINT not null,
|
||||||
|
employer_id BIGINT not null,
|
||||||
|
end_date TIMESTAMP,
|
||||||
|
start_date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table employees (
|
||||||
|
id BIGINT not null,
|
||||||
|
firstName VARCHAR(255),
|
||||||
|
initial CHAR(1),
|
||||||
|
lastName VARCHAR(255),
|
||||||
|
taxfileNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK0 foreign key (employer_id) references employers
|
||||||
|
alter table employment_periods
|
||||||
|
add constraint employment_periodsFK1 foreign key (employee_id) references employees
|
||||||
|
create sequence employee_id_seq
|
||||||
|
create sequence employment_id_seq
|
||||||
|
create sequence employer_id_seq]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-authorwork">
|
||||||
|
<title>Auteur/Travail (Author/Work)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Soit le modèle de la relation entre <literal>Work</literal>, <literal>Author</literal>
|
||||||
|
et <literal>Person</literal>. Nous représentons la relation entre <literal>Work</literal>
|
||||||
|
et <literal>Author</literal> comme une association plusieurs-vers-plusieurs. Nous avons choisi de
|
||||||
|
représenter la relation entre <literal>Author</literal> et <literal>Person</literal>
|
||||||
|
comme une association un-vers-un. Une autre possibilité aurait été que
|
||||||
|
<literal>Author</literal> hérite de <literal>Person</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/AuthorWork.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le mapping suivant représente exactement ces relations :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Work" table="works" discriminator-value="W">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="type" type="character"/>
|
||||||
|
|
||||||
|
<property name="title"/>
|
||||||
|
<set name="authors" table="author_work">
|
||||||
|
<key column name="work_id"/>
|
||||||
|
<many-to-many class="Author" column name="author_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<subclass name="Book" discriminator-value="B">
|
||||||
|
<property name="text"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Song" discriminator-value="S">
|
||||||
|
<property name="tempo"/>
|
||||||
|
<property name="genre"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Author" table="authors">
|
||||||
|
|
||||||
|
<id name="id" column="id">
|
||||||
|
<!-- The Author must have the same identifier as the Person -->
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="alias"/>
|
||||||
|
<one-to-one name="person" constrained="true"/>
|
||||||
|
|
||||||
|
<set name="works" table="author_work" inverse="true">
|
||||||
|
<key column="author_id"/>
|
||||||
|
<many-to-many class="Work" column="work_id"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Person" table="persons">
|
||||||
|
<id name="id" column="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a quatre tables dans ce mapping. <literal>works</literal>,
|
||||||
|
<literal>authors</literal> et <literal>persons</literal> qui contiennent
|
||||||
|
respectivement les données de work, author et person.
|
||||||
|
<literal>author_work</literal> est une table d'association qui lie authors
|
||||||
|
à works. Voici le schéma de tables, généré par <literal>SchemaExport</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table works (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
tempo FLOAT,
|
||||||
|
genre VARCHAR(255),
|
||||||
|
text INTEGER,
|
||||||
|
title VARCHAR(255),
|
||||||
|
type CHAR(1) not null,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table author_work (
|
||||||
|
author_id BIGINT not null,
|
||||||
|
work_id BIGINT not null,
|
||||||
|
primary key (work_id, author_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table authors (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
alias VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table persons (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table authors
|
||||||
|
add constraint authorsFK0 foreign key (id) references persons
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK0 foreign key (author_id) references authors
|
||||||
|
alter table author_work
|
||||||
|
add constraint author_workFK1 foreign key (work_id) references works]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-mappings-customerorderproduct">
|
||||||
|
<title>Client/Commande/Produit (Customer/Order/Product)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Imaginons maintenant le modèle de relation entre <literal>Customer</literal>,
|
||||||
|
<literal>Order</literal>, <literal>LineItem</literal> et <literal>Product</literal>.
|
||||||
|
Il y a une association un-vers-plusieurs entre <literal>Customer</literal> et
|
||||||
|
<literal>Order</literal>, mais comment devrions nous représenter <literal>Order</literal> /
|
||||||
|
<literal>LineItem</literal> / <literal>Product</literal>? J'ai choisi de mapper
|
||||||
|
<literal>LineItem</literal> comme une classe d'association représentant l'association
|
||||||
|
plusieurs-vers-plusieurs entre <literal>Order</literal> et <literal>Product</literal>. Dans
|
||||||
|
Hibernate, on appelle cela un élément composite.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<mediaobject>
|
||||||
|
<imageobject role="fo">
|
||||||
|
<imagedata fileref="images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
<imageobject role="html">
|
||||||
|
<imagedata fileref="../shared/images/CustomerOrderProduct.gif" format="GIF" align="center"/>
|
||||||
|
</imageobject>
|
||||||
|
</mediaobject>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le document de mapping :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class name="Customer" table="customers">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="name"/>
|
||||||
|
<set name="orders" inverse="true">
|
||||||
|
<key column="customer_id"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="orders">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="date"/>
|
||||||
|
<many-to-one name="customer" column="customer_id"/>
|
||||||
|
<list name="lineItems" table="line_items">
|
||||||
|
<key column="order_id"/>
|
||||||
|
<list-index column="line_number"/>
|
||||||
|
<composite-element class="LineItem">
|
||||||
|
<property name="quantity"/>
|
||||||
|
<many-to-one name="product" column="product_id"/>
|
||||||
|
</composite-element>
|
||||||
|
</list>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product" table="products">
|
||||||
|
<id name="id">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="serialNumber"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>customers</literal>, <literal>orders</literal>, <literal>line_items</literal> et
|
||||||
|
<literal>products</literal> contiennent les données de customer, order, order line item et product.
|
||||||
|
<literal>line_items</literal> est aussi la table d'association liant orders à products.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[create table customers (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
name VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table orders (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
customer_id BIGINT,
|
||||||
|
date TIMESTAMP,
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table line_items (
|
||||||
|
line_number INTEGER not null,
|
||||||
|
order_id BIGINT not null,
|
||||||
|
product_id BIGINT,
|
||||||
|
quantity INTEGER,
|
||||||
|
primary key (order_id, line_number)
|
||||||
|
)
|
||||||
|
|
||||||
|
create table products (
|
||||||
|
id BIGINT not null generated by default as identity,
|
||||||
|
serialNumber VARCHAR(255),
|
||||||
|
primary key (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
alter table orders
|
||||||
|
add constraint ordersFK0 foreign key (customer_id) references customers
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK0 foreign key (product_id) references products
|
||||||
|
alter table line_items
|
||||||
|
add constraint line_itemsFK1 foreign key (order_id) references orders]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="misc">
|
||||||
|
<title>Divers mappings d'exemple</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ces exemples sont tous pris de la suite de tests d'Hibernate. Vous en trouverez beaucoup d'autres.
|
||||||
|
Regardez dans le dossier <literal>test</literal> de la distribution d'Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>TODO: put words around this stuff</para>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-typed-onetone">
|
||||||
|
<title>"Typed" one-to-one association</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
<id name="name"/>
|
||||||
|
<one-to-one name="address"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'HOME'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
<one-to-one name="mailingAddress"
|
||||||
|
cascade="all">
|
||||||
|
<formula>name</formula>
|
||||||
|
<formula>'MAILING'</formula>
|
||||||
|
</one-to-one>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address" batch-size="2"
|
||||||
|
check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
|
||||||
|
<composite-id>
|
||||||
|
<key-many-to-one name="person"
|
||||||
|
column="personName"/>
|
||||||
|
<key-property name="type"
|
||||||
|
column="addressType"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="street" type="text"/>
|
||||||
|
<property name="state"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key">
|
||||||
|
<title>Exemple de clef composée</title>
|
||||||
|
<programlisting><![CDATA[<class name="Customer">
|
||||||
|
|
||||||
|
<id name="customerId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" not-null="true" length="100"/>
|
||||||
|
<property name="address" not-null="true" length="200"/>
|
||||||
|
|
||||||
|
<list name="orders"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key column="customerId"/>
|
||||||
|
<index column="orderNumber"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Order" table="CustomerOrder" lazy="true">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
<synchronize table="Product"/>
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="Order$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="orderDate"
|
||||||
|
type="calendar_date"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property name="total">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity*p.price)
|
||||||
|
from LineItem li, Product p
|
||||||
|
where li.productId = p.productId
|
||||||
|
and li.customerId = customerId
|
||||||
|
and li.orderNumber = orderNumber )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<many-to-one name="customer"
|
||||||
|
column="customerId"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<bag name="lineItems"
|
||||||
|
fetch="join"
|
||||||
|
inverse="true"
|
||||||
|
cascade="save-update">
|
||||||
|
<key>
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</key>
|
||||||
|
<one-to-many class="LineItem"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="LineItem">
|
||||||
|
|
||||||
|
<composite-id name="id"
|
||||||
|
class="LineItem$Id">
|
||||||
|
<key-property name="customerId" length="10"/>
|
||||||
|
<key-property name="orderNumber"/>
|
||||||
|
<key-property name="productId" length="10"/>
|
||||||
|
</composite-id>
|
||||||
|
|
||||||
|
<property name="quantity"/>
|
||||||
|
|
||||||
|
<many-to-one name="order"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true">
|
||||||
|
<column name="customerId"/>
|
||||||
|
<column name="orderNumber"/>
|
||||||
|
</many-to-one>
|
||||||
|
|
||||||
|
<many-to-one name="product"
|
||||||
|
insert="false"
|
||||||
|
update="false"
|
||||||
|
not-null="true"
|
||||||
|
column="productId"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Product">
|
||||||
|
<synchronize table="LineItem"/>
|
||||||
|
|
||||||
|
<id name="productId"
|
||||||
|
length="10">
|
||||||
|
<generator class="assigned"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="description"
|
||||||
|
not-null="true"
|
||||||
|
length="200"/>
|
||||||
|
<property name="price" length="3"/>
|
||||||
|
<property name="numberAvailable"/>
|
||||||
|
|
||||||
|
<property name="numberOrdered">
|
||||||
|
<formula>
|
||||||
|
( select sum(li.quantity)
|
||||||
|
from LineItem li
|
||||||
|
where li.productId = productId )
|
||||||
|
</formula>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-composite-key-manytomany">
|
||||||
|
<title>Many-to-many avec une clef composée partagée</title>
|
||||||
|
<programlisting><![CDATA[<class name="User" table="`User`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<set name="groups" table="UserGroup">
|
||||||
|
<key>
|
||||||
|
<column name="userName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="Group">
|
||||||
|
<column name="groupName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Group" table="`Group`">
|
||||||
|
<composite-id>
|
||||||
|
<key-property name="name"/>
|
||||||
|
<key-property name="org"/>
|
||||||
|
</composite-id>
|
||||||
|
<property name="description"/>
|
||||||
|
<set name="users" table="UserGroup" inverse="true">
|
||||||
|
<key>
|
||||||
|
<column name="groupName"/>
|
||||||
|
<column name="org"/>
|
||||||
|
</key>
|
||||||
|
<many-to-many class="User">
|
||||||
|
<column name="userName"/>
|
||||||
|
<formula>org</formula>
|
||||||
|
</many-to-many>
|
||||||
|
</set>
|
||||||
|
</class>
|
||||||
|
]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-content-discrimination">
|
||||||
|
<title>Contenu basé sur une discrimination</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person"
|
||||||
|
discriminator-value="P">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
column="person_id"
|
||||||
|
unsaved-value="0">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
|
||||||
|
<discriminator
|
||||||
|
type="character">
|
||||||
|
<formula>
|
||||||
|
case
|
||||||
|
when title is not null then 'E'
|
||||||
|
when salesperson is not null then 'C'
|
||||||
|
else 'P'
|
||||||
|
end
|
||||||
|
</formula>
|
||||||
|
</discriminator>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
not-null="true"
|
||||||
|
length="80"/>
|
||||||
|
|
||||||
|
<property name="sex"
|
||||||
|
not-null="true"
|
||||||
|
update="false"/>
|
||||||
|
|
||||||
|
<component name="address">
|
||||||
|
<property name="address"/>
|
||||||
|
<property name="zip"/>
|
||||||
|
<property name="country"/>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<subclass name="Employee"
|
||||||
|
discriminator-value="E">
|
||||||
|
<property name="title"
|
||||||
|
length="20"/>
|
||||||
|
<property name="salary"/>
|
||||||
|
<many-to-one name="manager"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
<subclass name="Customer"
|
||||||
|
discriminator-value="C">
|
||||||
|
<property name="comments"/>
|
||||||
|
<many-to-one name="salesperson"/>
|
||||||
|
</subclass>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="example-mappings-association-alternatekeys" revision="2">
|
||||||
|
<title>Associations sur des clefs alternées</title>
|
||||||
|
<programlisting><![CDATA[<class name="Person">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name" length="100"/>
|
||||||
|
|
||||||
|
<one-to-one name="address"
|
||||||
|
property-ref="person"
|
||||||
|
cascade="all"
|
||||||
|
fetch="join"/>
|
||||||
|
|
||||||
|
<set name="accounts"
|
||||||
|
inverse="true">
|
||||||
|
<key column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
<one-to-many class="Account"/>
|
||||||
|
</set>
|
||||||
|
|
||||||
|
<property name="userId" length="8"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Address">
|
||||||
|
|
||||||
|
<id name="id">
|
||||||
|
<generator class="hilo"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="address" length="300"/>
|
||||||
|
<property name="zip" length="5"/>
|
||||||
|
<property name="country" length="25"/>
|
||||||
|
<many-to-one name="person" unique="true" not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Account">
|
||||||
|
<id name="accountId" length="32">
|
||||||
|
<generator class="uuid"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<many-to-one name="user"
|
||||||
|
column="userId"
|
||||||
|
property-ref="userId"/>
|
||||||
|
|
||||||
|
<property name="type" not-null="true"/>
|
||||||
|
|
||||||
|
</class>]]></programlisting>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,372 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="example-parentchild">
|
||||||
|
<title>Exemple : Père/Fils</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'une des premières choses que les nouveaux utilisateurs essaient de faire avec Hibernate est de modéliser
|
||||||
|
une relation père/fils. Il y a deux approches différentes pour cela. Pour un certain nombre de raisons, la méthode la
|
||||||
|
plus courante, en particulier pour les nouveaux utilisateurs, est de modéliser les deux relations <literal>Père</literal>
|
||||||
|
et <literal>Fils</literal> comme des classes entités liées par une association <literal><one-to-many></literal> du
|
||||||
|
<literal>Père</literal> vers le <literal>Fils</literal> (l'autre approche est de déclarer le <literal>Fils</literal>
|
||||||
|
comme un <literal><composite-element></literal>). Il est évident que le sens de l'association un vers plusieurs
|
||||||
|
(dans Hibernate) est bien moins proche du sens habituel d'une relation père/fils que ne l'est celui d'un
|
||||||
|
élément cmposite. Nous allons vous expliquer comment utiliser une association <emphasis>un vers plusieurs bidirectionnelle
|
||||||
|
avec cascade</emphasis> afin de modéliser efficacement et élégamment une relation père/fils, ce n'est vraiment
|
||||||
|
pas difficile !
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-collections">
|
||||||
|
<title>Une note à propos des collections</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les collections Hibernate sont considérées comme étant une partie logique
|
||||||
|
de l'entité dans laquelle elles sont contenues ; jamais des entités qu'elle
|
||||||
|
contient. C'est une distinction crutiale ! Les conséquences sont les suivantes :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Quand nous ajoutons / retirons un objet d'une collection, le numéro de version du
|
||||||
|
propriétaire de la collection est incrémenté.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Si un objet qui a été enlevé d'une collection est une instance de type valeur (ex :
|
||||||
|
élément composite), cet objet cessera d'être persistant et son état sera complètement effacé
|
||||||
|
de la base de données. Par ailleurs, ajouter une instance de type valeur dans une collection
|
||||||
|
aura pour conséquence que son état sera immédiatement persistant.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Si une entité est enlevée d'une collection (association un-vers-plusieurs
|
||||||
|
ou plusieurs-vers-plusieurs), par défaut, elle ne sera pas effacée. Ce comportement
|
||||||
|
est complètement logique - une modification de l'un des états internes d'une entité
|
||||||
|
ne doit pas causer la disparition de l'entité associée !
|
||||||
|
De même, l'ajout d'une entité dans une collection n'engendre pas,
|
||||||
|
par défaut, la persistance de cette entité.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le comportement par défaut est donc que l'ajout d'une entité dans une collection créé
|
||||||
|
simplement le lien entre les deux entités, et qu'effacer une entité supprime ce lien.
|
||||||
|
C'est le comportement le plus approprié dans la plupart des cas. Ce comportement n'est
|
||||||
|
cependant pas approprié lorsque la vie du fils est liée au cycle de vie du père.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-bidir">
|
||||||
|
<title>un-vers-plusieurs bidirectionnel</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supposons que nous ayons une simple association <literal><one-to-many></literal>
|
||||||
|
de <literal>Parent</literal> vers <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si nous executions le code suivant
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = .....;
|
||||||
|
Child c = new Child();
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate exécuterait deux ordres SQL:
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>un <literal>INSERT</literal> pour créer l'enregistrement pour <literal>c</literal></para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
un <literal>UPDATE</literal> pour créer le lien de <literal>p</literal> vers
|
||||||
|
<literal>c</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ceci est non seuleument inefficace, mais viole aussi toute contrainte <literal>NOT NULL</literal> sur
|
||||||
|
la colonne <literal>parent_id</literal>. Nous pouvons réparer la contrainte de nullité
|
||||||
|
en spécifiant <literal>not-null="true"</literal> dans le mapping de la collection :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children">
|
||||||
|
<key column="parent_id" not-null="true"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Cependant ce n'est pas la solution recommandée.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La cause sous jacente à ce comportement est que le lien (la clé étrangère <literal>parent_id</literal>) de
|
||||||
|
<literal>p</literal> vers <literal>c</literal> n'est pas considérée comme faisant partie de l'état
|
||||||
|
de l'objet <literal>Child</literal> et n'est donc pas créé par l'<literal>INSERT</literal>.
|
||||||
|
La solution est donc que ce lien fasse partie du mapping de <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(Nous avons aussi besoin d'ajouter la propriété <literal>parent</literal> dans la classe <literal>Child</literal>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Maintenant que l'état du lien est géré par l'entité <literal>Child</literal>, nous spécifions à la
|
||||||
|
collection de ne pas mettre à jour le lien. Nous utilisons l'attribut <literal>inverse</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le code suivant serait utilisé pour ajouter un nouveau <literal>Child</literal>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
c.setParent(p);
|
||||||
|
p.getChildren().add(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Maintenant, seul un <literal>INSERT</literal> SQL est nécessaire !
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour alléger encore un peu les choses, nous devrions créer une méthode <literal>addChild()</literal>
|
||||||
|
dans <literal>Parent</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public void addChild(Child c) {
|
||||||
|
c.setParent(this);
|
||||||
|
children.add(c);
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le code d'ajout d'un <literal>Child</literal> serait alors
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.save(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-cascades">
|
||||||
|
<title>Cycle de vie en cascade</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'appel explicite de <literal>save()</literal> est un peu fastidieux. Nous pouvons
|
||||||
|
simplifier cela en utilisant les cascades.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Simplifie le code précédent en
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = new Child();
|
||||||
|
p.addChild(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
De la même manière, nous n'avons pas à itérer sur les fils lorsque nous sauvons
|
||||||
|
ou effacons un <literal>Parent</literal>. Le code suivant efface <literal>p</literal>
|
||||||
|
et tous ses fils de la base de données.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
session.delete(p);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Par contre, ce code
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
c.setParent(null);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
n'effacera pas <literal>c</literal> de la base de données, il enlèvera seulement
|
||||||
|
le lien vers <literal>p</literal> (et causera une violation de contrainte
|
||||||
|
<literal>NOT NULL</literal>, dans ce cas).
|
||||||
|
Vous devez explicitement utiliser <literal>delete()</literal> sur <literal>Child</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||||||
|
Child c = (Child) p.getChildren().iterator().next();
|
||||||
|
p.getChildren().remove(c);
|
||||||
|
session.delete(c);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dans notre cas, un <literal>Child</literal> ne peut pas vraiment exister sans son père. Si nous
|
||||||
|
effacons un <literal>Child</literal> de la collection, nous voulons vraiment qu'il soit effacé.
|
||||||
|
Pour cela, nous devons utiliser <literal>cascade="all-delete-orphan"</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||||||
|
<key column="parent_id"/>
|
||||||
|
<one-to-many class="Child"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A noter : même si le mapping de la collection spécifie <literal>inverse="true"</literal>, les cascades
|
||||||
|
sont toujours assurées par l'itération sur les éléments de la collection. Donc, si vous avez besoin
|
||||||
|
qu'un objet soit enregistré, effacé ou mis à jour par cascade, vous devez l'ajouter dans la colleciton.
|
||||||
|
Il ne suffit pas d'appeler explicitement <literal>setParent()</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-update">
|
||||||
|
<title>Cascades et <literal>unsaved-value</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supposons que nous ayons chargé un <literal>Parent</literal> dans une <literal>Session</literal>,
|
||||||
|
que nous l'ayons ensuite modifié et que voulions persiter ces modifications dans une nouvelle session
|
||||||
|
en appelant <literal>update()</literal>.
|
||||||
|
Le <literal>Parent</literal> contiendra une collection de fils et, puisque la cascade est activée,
|
||||||
|
Hibernate a besoin de savoir quels fils viennent d'être instanciés et quels fils proviennent de la base
|
||||||
|
de données. Supposons aussi que <literal>Parent</literal> et <literal>Child</literal> ont tous deux
|
||||||
|
des identifiants du type <literal>Long</literal>.
|
||||||
|
Hibernate utilisera la propriété de l'identifiant et la propriété de la version/horodatage pour déterminer quels fils sont nouveaux
|
||||||
|
(vous pouvez aussi utiliser la propriété version ou timestamp, voir
|
||||||
|
<xref linkend="manipulatingdata-updating-detached"/>).
|
||||||
|
<emphasis>Dans Hibernate3, il n'est plus nécessaire de spécifier
|
||||||
|
une <literal>unsaved-value</literal> explicitement.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le code suivant mettra à jour <literal>parent</literal> et <literal>child</literal>
|
||||||
|
et insérera <literal>newChild</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[//parent et child ont été chargés dans une session précédente
|
||||||
|
parent.addChild(child);
|
||||||
|
Child newChild = new Child();
|
||||||
|
parent.addChild(newChild);
|
||||||
|
session.update(parent);
|
||||||
|
session.flush();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Ceci est très bien pour des identifiants générés, mais qu'en est-il des identifiants assignés et des
|
||||||
|
identifiants composés ? C'est plus difficile,
|
||||||
|
puisqu'Hibernate ne peut pas utiliser la propriété de l'identifiant pour distinguer un objet
|
||||||
|
nouvellement instancié (avec un identifiant assigné par l'utilisateur) d'un objet chargé dans une session précédente.
|
||||||
|
Dans ce cas, Hibernate utilisera soit la propriété de version ou d'horodatage, soit effectuera vraiment une requête au cache
|
||||||
|
de second niveau, soit, dans le pire des cas, à la base de données, pour voir si la ligne existe.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<!-- undocumenting
|
||||||
|
<para>
|
||||||
|
There is one further possibility. The <literal>Interceptor</literal> method named
|
||||||
|
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
||||||
|
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Persistent {
|
||||||
|
private boolean _saved = false;
|
||||||
|
public void onSave() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
public void onLoad() {
|
||||||
|
_saved=true;
|
||||||
|
}
|
||||||
|
......
|
||||||
|
public boolean isSaved() {
|
||||||
|
return _saved;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
(The <literal>saved</literal> property is non-persistent.)
|
||||||
|
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||||||
|
and <literal>onSave()</literal> as follows.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||||||
|
if (entity instanceof Persistent) {
|
||||||
|
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLoad(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSave(Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] state,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
|
||||||
|
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||||||
|
return false;
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
||||||
|
</para>
|
||||||
|
-->
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-parentchild-conclusion">
|
||||||
|
<title>Conclusion</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a quelques principes à maîtriser dans ce chapitre et tout cela peut paraître déroutant la première fois.
|
||||||
|
Cependant, dans la pratique, tout fonctionne parfaitement. La plupart des applications Hibernate utilisent
|
||||||
|
le pattern père / fils.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous avons évoqué une alternative dans le premier paragraphe. Aucun des points traités précédemment n'existe
|
||||||
|
dans le cas d'un mapping <literal><composite-element></literal> qui possède exactement la sémantique
|
||||||
|
d'une relation père / fils. Malheureusement, il y a deux grandes limitations pour les classes éléments
|
||||||
|
composites : les éléments composites ne peuvent contenir de collections, et ils ne peuvent être les fils
|
||||||
|
d'entités autres que l'unique parent.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,432 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="example-weblog">
|
||||||
|
<title>Exemple : application Weblog</title>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-classes">
|
||||||
|
<title>Classes persistantes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les classes persistantes representent un weblog, et un article posté
|
||||||
|
dans un weblog. Il seront modélisés comme une relation père/fils
|
||||||
|
standard, mais nous allons utiliser un "bag" trié au lieu d'un set.
|
||||||
|
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Blog {
|
||||||
|
private Long _id;
|
||||||
|
private String _name;
|
||||||
|
private List _items;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public List getItems() {
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setItems(List list) {
|
||||||
|
_items = list;
|
||||||
|
}
|
||||||
|
public void setName(String string) {
|
||||||
|
_name = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class BlogItem {
|
||||||
|
private Long _id;
|
||||||
|
private Calendar _datetime;
|
||||||
|
private String _text;
|
||||||
|
private String _title;
|
||||||
|
private Blog _blog;
|
||||||
|
|
||||||
|
public Blog getBlog() {
|
||||||
|
return _blog;
|
||||||
|
}
|
||||||
|
public Calendar getDatetime() {
|
||||||
|
return _datetime;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
public String getText() {
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
public String getTitle() {
|
||||||
|
return _title;
|
||||||
|
}
|
||||||
|
public void setBlog(Blog blog) {
|
||||||
|
_blog = blog;
|
||||||
|
}
|
||||||
|
public void setDatetime(Calendar calendar) {
|
||||||
|
_datetime = calendar;
|
||||||
|
}
|
||||||
|
public void setId(Long long1) {
|
||||||
|
_id = long1;
|
||||||
|
}
|
||||||
|
public void setText(String string) {
|
||||||
|
_text = string;
|
||||||
|
}
|
||||||
|
public void setTitle(String string) {
|
||||||
|
_title = string;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-mappings">
|
||||||
|
<title>Mappings Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Le mapping XML doit maintenant être relativement simple à vos yeux.
|
||||||
|
</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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="Blog"
|
||||||
|
table="BLOGS">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="name"
|
||||||
|
column="NAME"
|
||||||
|
not-null="true"
|
||||||
|
unique="true"/>
|
||||||
|
|
||||||
|
<bag
|
||||||
|
name="items"
|
||||||
|
inverse="true"
|
||||||
|
order-by="DATE_TIME"
|
||||||
|
cascade="all">
|
||||||
|
|
||||||
|
<key column="BLOG_ID"/>
|
||||||
|
<one-to-many class="BlogItem"/>
|
||||||
|
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<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 package="eg">
|
||||||
|
|
||||||
|
<class
|
||||||
|
name="BlogItem"
|
||||||
|
table="BLOG_ITEMS"
|
||||||
|
dynamic-update="true">
|
||||||
|
|
||||||
|
<id
|
||||||
|
name="id"
|
||||||
|
column="BLOG_ITEM_ID">
|
||||||
|
|
||||||
|
<generator class="native"/>
|
||||||
|
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="title"
|
||||||
|
column="TITLE"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="text"
|
||||||
|
column="TEXT"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<property
|
||||||
|
name="datetime"
|
||||||
|
column="DATE_TIME"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
<many-to-one
|
||||||
|
name="blog"
|
||||||
|
column="BLOG_ID"
|
||||||
|
not-null="true"/>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="example-weblog-code">
|
||||||
|
<title>Code Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La classe suivante montre quelques utilisations que nous pouvons faire
|
||||||
|
de ces classes.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.Query;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.cfg.Configuration;
|
||||||
|
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||||
|
|
||||||
|
public class BlogMain {
|
||||||
|
|
||||||
|
private SessionFactory _sessions;
|
||||||
|
|
||||||
|
public void configure() throws HibernateException {
|
||||||
|
_sessions = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class)
|
||||||
|
.buildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportTables() throws HibernateException {
|
||||||
|
Configuration cfg = new Configuration()
|
||||||
|
.addClass(Blog.class)
|
||||||
|
.addClass(BlogItem.class);
|
||||||
|
new SchemaExport(cfg).create(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog createBlog(String name) throws HibernateException {
|
||||||
|
|
||||||
|
Blog blog = new Blog();
|
||||||
|
blog.setName(name);
|
||||||
|
blog.setItems( new ArrayList() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.persist(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Blog blog, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setBlog(blog);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
blog.getItems().add(item);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(blog);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogItem createBlogItem(Long blogid, String title, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
BlogItem item = new BlogItem();
|
||||||
|
item.setTitle(title);
|
||||||
|
item.setText(text);
|
||||||
|
item.setDatetime( Calendar.getInstance() );
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Blog blog = (Blog) session.load(Blog.class, blogid);
|
||||||
|
item.setBlog(blog);
|
||||||
|
blog.getItems().add(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(BlogItem item, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
item.setText(text);
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
session.update(item);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateBlogItem(Long itemid, String text)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
BlogItem item = (BlogItem) session.load(BlogItem.class, itemid);
|
||||||
|
item.setText(text);
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listAllBlogNamesAndItemCounts(int max)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"select blog.id, blog.name, count(blogItem) " +
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join blog.items as blogItem " +
|
||||||
|
"group by blog.name, blog.id " +
|
||||||
|
"order by max(blogItem.datetime)"
|
||||||
|
);
|
||||||
|
q.setMaxResults(max);
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Blog getBlogAndAllItems(Long blogid)
|
||||||
|
throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
Blog blog = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"left outer join fetch blog.items " +
|
||||||
|
"where blog.id = :blogid"
|
||||||
|
);
|
||||||
|
q.setParameter("blogid", blogid);
|
||||||
|
blog = (Blog) q.uniqueResult();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List listBlogsAndRecentItems() throws HibernateException {
|
||||||
|
|
||||||
|
Session session = _sessions.openSession();
|
||||||
|
Transaction tx = null;
|
||||||
|
List result = null;
|
||||||
|
try {
|
||||||
|
tx = session.beginTransaction();
|
||||||
|
Query q = session.createQuery(
|
||||||
|
"from Blog as blog " +
|
||||||
|
"inner join blog.items as blogItem " +
|
||||||
|
"where blogItem.datetime > :minDate"
|
||||||
|
);
|
||||||
|
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.roll(Calendar.MONTH, false);
|
||||||
|
q.setCalendar("minDate", cal);
|
||||||
|
|
||||||
|
result = q.list();
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
catch (HibernateException he) {
|
||||||
|
if (tx!=null) tx.rollback();
|
||||||
|
throw he;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="filters">
|
||||||
|
<title>Filtrer les données</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 fournit une nouvelle approche innovatrice pour gérer des données
|
||||||
|
avec des règles de "visibilité". Un <emphasis>filtre Hibernate</emphasis> est un filtre
|
||||||
|
global, nommé, paramétré qui peut être activé ou désactivé pour une session Hibernate
|
||||||
|
particulière.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="objectstate-filters">
|
||||||
|
<title>Filtres Hibernate</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate3 ajoute la capacité de prédéfinir des critères de filtre et d'attacher ces
|
||||||
|
filtres à une classe ou à une collection. Un critère de filtre est la faculté de définir
|
||||||
|
une clause de restriction très similaire à l'attribut "where" existant disponible sur
|
||||||
|
une classe et divers éléments d'une collection. Mis à part que ces conditions de filtre
|
||||||
|
peuvent être paramétrées. L'application peut alors prendre la décision à l'exécution
|
||||||
|
si des filtres donnés devraient être activés et quels devraient être leurs paramètres.
|
||||||
|
Des filtres peuvent être utilisés comme des vues de base de données, mais paramétrées
|
||||||
|
dans l'application.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Afin d'utiliser des filtres, ils doivent d'abord être définis, puis attachés aux éléments
|
||||||
|
de mapping appropriés. Pour définir un filtre, utilisez l'élément <literal><filter-def/></literal>
|
||||||
|
dans un élément <literal><hibernate-mapping/></literal> :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="myFilter">
|
||||||
|
<filter-param name="myFilterParam" type="string"/>
|
||||||
|
</filter-def>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puis, ce filtre peut être attaché à une classe :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="myClass" ...>
|
||||||
|
...
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
ou à une collection :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<set ...>
|
||||||
|
<filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
|
||||||
|
</set>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
ou même aux deux (ou à plusieurs de chaque) en même temps.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les méthodes sur <literal>Session</literal> sont : <literal>enableFilter(String filterName)</literal>,
|
||||||
|
<literal>getEnabledFilter(String filterName)</literal>, et <literal>disableFilter(String filterName)</literal>.
|
||||||
|
Par défaut, les filtres <emphasis>ne sont pas</emphasis> activés pour une session donnée ;
|
||||||
|
ils doivent être explicitement activés en appelant la méthode
|
||||||
|
<literal>Session.enabledFilter()</literal>, laquelle retourne une instance de l'interface
|
||||||
|
<literal>Filter</literal>. Utiliser le simple filtre défini au-dessus ressemblerait à :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que des méthodes sur l'interface org.hibernate.Filter autorisent le chaînage de beaucoup
|
||||||
|
de méthodes communes d'Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Un exemple complet, utilisant des données temporelles avec une structure de date
|
||||||
|
d'enregistrement effectif :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<filter-def name="effectiveDate">
|
||||||
|
<filter-param name="asOfDate" type="date"/>
|
||||||
|
</filter-def>
|
||||||
|
|
||||||
|
<class name="Employee" ...>
|
||||||
|
...
|
||||||
|
<many-to-one name="department" column="dept_id" class="Department"/>
|
||||||
|
<property name="effectiveStartDate" type="date" column="eff_start_dt"/>
|
||||||
|
<property name="effectiveEndDate" type="date" column="eff_end_dt"/>
|
||||||
|
...
|
||||||
|
<!--
|
||||||
|
Note that this assumes non-terminal records have an eff_end_dt set to
|
||||||
|
a max db date for simplicity-sake
|
||||||
|
-->
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="Department" ...>
|
||||||
|
...
|
||||||
|
<set name="employees" lazy="true">
|
||||||
|
<key column="dept_id"/>
|
||||||
|
<one-to-many class="Employee"/>
|
||||||
|
<filter name="effectiveDate"
|
||||||
|
condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>
|
||||||
|
</set>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Puis, afin de s'assurer que vous pouvez toujours récupérer les enregistrements actuellement
|
||||||
|
effectifs, activez simplement le filtre sur la session avant de récupérer des données des
|
||||||
|
employés :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session session = ...;
|
||||||
|
session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date());
|
||||||
|
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
|
||||||
|
.setLong("targetSalary", new Long(1000000))
|
||||||
|
.list();
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Dans le HQL ci-dessus, bien que nous ayons seulement mentionné une contrainte de
|
||||||
|
salaire sur les resultats, à cause du filtre activé, la requête retournera seulement
|
||||||
|
les employés actuellement actifs qui ont un salaire supérieur à un million de dollars.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A noter : si vous prévoyez d'utiliser des filtres avec des jointures externes (soit
|
||||||
|
à travers HQL, soit par le chargement) faites attention à la direction de l'expression
|
||||||
|
de condition. Il est plus sûr de la positionner pour les jointures externes à gauche ;
|
||||||
|
en général, placez le paramètre d'abord, suivi du(des) nom(s) de colonne après l'opérateur.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|
|
@ -0,0 +1,483 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<chapter id="inheritance">
|
||||||
|
<title>Mapping d'héritage de classe</title>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-strategies" revision="3">
|
||||||
|
<title>Les trois stratégies</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate supporte les trois stratégies d'héritage de base :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
une table par hiérarchie de classe (table per class hierarchy)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
une table par classe fille (table per subclass)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
une table par classe concrète (table per concrete class)
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate supporte en plus une quatrièmestratégie, légèrement différente, qui supporte le polymorphisme :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
le polymorphisme implicite
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il est possible d'utiliser différentes stratégies de mapping pour différentes branches d'une même
|
||||||
|
hiérarchie d'héritage, et alors d'employer le polymorphisme implicite pour réaliser le
|
||||||
|
polymorphisme à travers toute la hiérarchie. Pourtant, Hibernate ne supporte pas de mélanger
|
||||||
|
des mappings <literal><subclass></literal> et
|
||||||
|
<literal><joined-subclass></literal> et <literal><union-subclass></literal>
|
||||||
|
pour le même élément <literal><class></literal> racine.
|
||||||
|
Il est possible de mélanger ensemble les stratégies d'une table par hiérarchie et d'une
|
||||||
|
table par sous-classe, pour le même élément <literal><class></literal>, en combinant
|
||||||
|
les éléments <literal><subclass></literal> et <literal><join></literal> (voir dessous).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il est possible de définir des mappings de <literal>subclass</literal>, <literal>union-subclass</literal>,
|
||||||
|
et <literal>joined-subclass</literal> dans des documents de mapping séparés, directement sous
|
||||||
|
<literal>hibernate-mapping</literal>. Ceci vous permet d'étendre une hiérarchie de classe juste en
|
||||||
|
ajoutant un nouveau fichier de mapping. Vous devez spécifier un attribut <literal>extends</literal>
|
||||||
|
dans le mapping de la sous-classe, en nommant une super-classe précédemment mappée. Note :
|
||||||
|
précédemment cette foncionnalité rendait l'ordre des documents de mapping important. Depuis
|
||||||
|
Hibernate3, l'ordre des fichier de mapping n'importe plus lors de l'utilisation du mot-clef "extends".
|
||||||
|
L'ordre à l'intérieur d'un simple fichier de mapping impose encore de définir les classes mères
|
||||||
|
avant les classes filles.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[
|
||||||
|
<hibernate-mapping>
|
||||||
|
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
|
||||||
|
<property name="name" type="string"/>
|
||||||
|
</subclass>
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperclass" >
|
||||||
|
<title>Une table par hiérarchie de classe</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Supposons que nous ayons une interface <literal>Payment</literal>, implémentée
|
||||||
|
par <literal>CreditCardPayment</literal>, <literal>CashPayment</literal>,
|
||||||
|
<literal>ChequePayment</literal>. La stratégie une table par hiérarchie serait :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une seule table est requise. Une grande limitation de cette
|
||||||
|
stratégie est que les colonnes déclarées par les classes filles, telles que <literal>CCTYPE</literal>,
|
||||||
|
ne peuvent avoir de contrainte <literal>NOT NULL</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass">
|
||||||
|
<title>Une table par classe fille</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La stratégie une table par classe fille serait :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Quatre tables sont requises. Les trois tables des classes filles ont
|
||||||
|
une clé primaire associée à la table classe mère (le modèle relationnel
|
||||||
|
est une association un-vers-un).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
||||||
|
<title>Une table par classe fille, en utilisant un discriminant</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que l'implémentation Hibernate de la stratégie un table par
|
||||||
|
classe fille ne nécessite pas de colonne discriminante dans la table
|
||||||
|
classe mère. D'autres implémentations de mappers Objet/Relationnel utilisent
|
||||||
|
une autre implémentation de la stratégie une table par classe fille qui nécessite
|
||||||
|
une colonne de type discriminant dans la table de la classe mère. L'approche
|
||||||
|
prise par Hibernate est plus difficile à implémenter mais plus correcte
|
||||||
|
d'une point de vue relationnel. Si vous aimeriez utiliser
|
||||||
|
une colonne discriminante avec la stratégie d'une table par classe fille, vous pourriez combiner
|
||||||
|
l'utilisation de <literal><subclass></literal> et
|
||||||
|
<literal><join></literal>, comme suit :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
<join table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
<join table="CHEQUE_PAYMENT" fetch="select">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La déclaration optionnelle <literal>fetch="select"</literal> indique à Hibernate
|
||||||
|
de ne pas récupérer les données de la classe fille <literal>ChequePayment</literal> par une jointure externe lors des requêtes sur la classe mère.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
||||||
|
<title>Mélange d'une table par hiérarchie de classe avec une table par classe fille</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous pouvez même mélanger les stratégies d'une table par hiérarchie de classe et d'une table par classe fille en utilisant cette approche :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment" table="PAYMENT">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="PAYMENT_TYPE" type="string"/>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="CreditCardPayment" discriminator-value="CREDIT">
|
||||||
|
<join table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</join>
|
||||||
|
</subclass>
|
||||||
|
<subclass name="CashPayment" discriminator-value="CASH">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
<subclass name="ChequePayment" discriminator-value="CHEQUE">
|
||||||
|
...
|
||||||
|
</subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour importe laquelle de ces stratégies, une association polymorphique vers la classe racine
|
||||||
|
<literal>Payment</literal> est mappée en utilisant <literal><many-to-one></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcrete" revision="2">
|
||||||
|
<title>Une table par classe concrète</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a deux manières d'utiliser la stratégie d'une table par classe concrète. La première
|
||||||
|
est d'employer <literal><union-subclass></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="Payment">
|
||||||
|
<id name="id" type="long" column="PAYMENT_ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="AMOUNT"/>
|
||||||
|
...
|
||||||
|
<union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<property name="creditCardType" column="CCTYPE"/>
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
<union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
...
|
||||||
|
</union-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Trois tables sont nécessaires pour les classes filles. Chaque table définit des colonnes
|
||||||
|
pour toutes les propriétés de la classe, incluant les propriétés héritéés.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La limitation de cette approche est que si une propriété est mappée sur la classe mère, le nom
|
||||||
|
de la colonne doit être le même pour toutes les classes filles. (Nous pourrions être plus souple
|
||||||
|
dans une future version d'Hibernate).
|
||||||
|
La stratégie du générateur d'identifiant n'est pas permise dans l'héritage de classes filles par
|
||||||
|
union, en effet la valeur (NdT : seed) de la clef primaire
|
||||||
|
doit être partagée par toutes les classes filles "union" d'une hiérarchie.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Si votre classe mère est abstraite, mappez la avec <literal>abstract="true"</literal>.
|
||||||
|
Bien sûr, si elle n'est pas abstraite, une table supplémentaire (par défaut,
|
||||||
|
<literal>PAYMENT</literal> dans l'exemple ci-dessus) est requise pour contenir des instances
|
||||||
|
de la classe mère.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
||||||
|
<title>Une table par classe concrète, en utilisant le polymorphisme implicite</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une approche alternative est l'emploi du polymorphisme implicite :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CASH_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CHEQUE_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que nulle part nous ne mentionnons l'interface <literal>Payment</literal> explicitement.
|
||||||
|
Notez aussi que des propriétés de <literal>Payment</literal> sont mappées dans
|
||||||
|
chaque classe fille. Si vous voulez éviter des duplications, considérez l'utilisation des
|
||||||
|
entités XML (cf. <literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
||||||
|
dans la déclaration du <literal>DOCTYPE</literal> et <literal>&allproperties;</literal> dans le mapping).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
L'inconvénient de cette approche est qu'Hibernate ne génère pas d'<literal>UNION</literal>s SQL
|
||||||
|
lors de l'exécution des requêtes polymorphiques.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Pour cette stratégie de mapping, une association polymorphique pour <literal>Payment</literal>
|
||||||
|
est habituellement mappée en utilisant <literal><any></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<any name="payment" meta-type="string" id-type="long">
|
||||||
|
<meta-value value="CREDIT" class="CreditCardPayment"/>
|
||||||
|
<meta-value value="CASH" class="CashPayment"/>
|
||||||
|
<meta-value value="CHEQUE" class="ChequePayment"/>
|
||||||
|
<column name="PAYMENT_CLASS"/>
|
||||||
|
<column name="PAYMENT_ID"/>
|
||||||
|
</any>]]></programlisting>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="inheritace-mixingpolymorphism">
|
||||||
|
<title>Mélange du polymorphisme implicite avec d'autres mappings d'héritage</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a une chose supplémentaire à noter à propos de ce mapping. Puisque les classes filles sont
|
||||||
|
chacune mappées avec leur propre élément <literal><class></literal> (et puisque
|
||||||
|
<literal>Payment</literal> est juste une interface), chaque classe fille pourrait
|
||||||
|
facilement faire partie d'une autre hiérarchie
|
||||||
|
d'héritage ! (Et vous pouvez encore faire des requêtes polymorphiques pour l'interface <literal>Payment</literal>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
|
||||||
|
<id name="id" type="long" column="CREDIT_PAYMENT_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
<discriminator column="CREDIT_CARD" type="string"/>
|
||||||
|
<property name="amount" column="CREDIT_AMOUNT"/>
|
||||||
|
...
|
||||||
|
<subclass name="MasterCardPayment" discriminator-value="MDC"/>
|
||||||
|
<subclass name="VisaPayment" discriminator-value="VISA"/>
|
||||||
|
</class>
|
||||||
|
|
||||||
|
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
|
||||||
|
<id name="id" type="long" column="TXN_ID">
|
||||||
|
<generator class="native"/>
|
||||||
|
</id>
|
||||||
|
...
|
||||||
|
<joined-subclass name="CashPayment" table="CASH_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CASH_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
<joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
|
||||||
|
<key column="PAYMENT_ID"/>
|
||||||
|
<property name="amount" column="CHEQUE_AMOUNT"/>
|
||||||
|
...
|
||||||
|
</joined-subclass>
|
||||||
|
</class>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Encore une fois, nous ne mentionnons pas explicitement <literal>Payment</literal>.
|
||||||
|
Si nous exécutons une requête sur l'interface <literal>Payment</literal> - par
|
||||||
|
exemple, <literal>from Payment</literal> - Hibernate retournera
|
||||||
|
automatiquement les instances de <literal>CreditCardPayment</literal>
|
||||||
|
(et ses classes filles puisqu'elles implémentent aussi <literal>Payment</literal>),
|
||||||
|
<literal>CashPayment</literal> et <literal>ChequePayment</literal> mais pas
|
||||||
|
les instances de <literal>NonelectronicTransaction</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="inheritance-limitations">
|
||||||
|
<title>Limitations</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a certaines limitations à l'approche du "polymorphisme implicite"
|
||||||
|
pour la stratégie de mapping d'une table par classe concrète.
|
||||||
|
Il y a plutôt moins de limitations restrictives aux mappings <literal><union-subclass></literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La table suivante montre les limitations des mappings d'une table par classe concrète, et du polymorphisme implicite, dans Hibernate.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<table frame="topbot">
|
||||||
|
<title>Caractéristiques du mapping d'héritage</title>
|
||||||
|
<tgroup cols='8' align='left' colsep='1' rowsep='1'>
|
||||||
|
<colspec colname='c1' colwidth="1*"/>
|
||||||
|
<colspec colname='c2' colwidth="1*"/>
|
||||||
|
<colspec colname='c3' colwidth="1*"/>
|
||||||
|
<colspec colname='c4' colwidth="1*"/>
|
||||||
|
<colspec colname='c5' colwidth="1*"/>
|
||||||
|
<colspec colname='c6' colwidth="1*"/>
|
||||||
|
<colspec colname='c7' colwidth="1*"/>
|
||||||
|
<colspec colname='c8' colwidth="1*"/>
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Stratégie d'héritage</entry>
|
||||||
|
<entry>many-to-one polymorphique</entry>
|
||||||
|
<entry>one-to-one polymorphique</entry>
|
||||||
|
<entry>one-to-many polymorphique</entry>
|
||||||
|
<entry>many-to-many polymorphique</entry>
|
||||||
|
<entry><literal>load()/get()</literal> polymorphique</entry>
|
||||||
|
<entry>Requêtes polymorphiques</entry>
|
||||||
|
<entry>Jointures polymorphiques</entry>
|
||||||
|
<entry>Récupération par jointure externe</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>une table par hiérarchie de classe</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supportée</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>une table par classe fille</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal></entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supportée</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>une table par classe concrète (union-subclass)</entry>
|
||||||
|
<entry><literal><many-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-one></literal></entry>
|
||||||
|
<entry><literal><one-to-many></literal> (pour <literal>inverse="true"</literal> seulement)</entry>
|
||||||
|
<entry><literal><many-to-many></literal></entry>
|
||||||
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
||||||
|
<entry><emphasis>supportée</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>une table par classe concrète (polymorphisme implicite)</entry>
|
||||||
|
<entry><literal><any></literal></entry>
|
||||||
|
<entry><emphasis>non supporté</emphasis></entry>
|
||||||
|
<entry><emphasis>non supporté</emphasis></entry>
|
||||||
|
<entry><literal><many-to-any></literal></entry>
|
||||||
|
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
|
||||||
|
<entry><literal>from Payment p</literal></entry>
|
||||||
|
<entry><emphasis>non supportées</emphasis></entry>
|
||||||
|
<entry><emphasis>non supportée</emphasis></entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
</chapter>
|
|
@ -0,0 +1,536 @@
|
||||||
|
<?xml version='1.0' encoding="iso-8859-1"?>
|
||||||
|
<chapter id="persistent-classes" revision="2">
|
||||||
|
<title>Classes persistantes</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les classes persistantes sont les classes d'une application qui implémentent
|
||||||
|
les entités d'un problème métier (ex. Client et Commande dans une application
|
||||||
|
de commerce électronique).
|
||||||
|
Toutes les instances d'une classe persistante ne sont pas forcément
|
||||||
|
dans l'état persistant - au lieu de cela, une instance peut être éphémère (NdT : transient) ou détachée.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate fonctionne de manière optimale lorsque ces classes suivent quelques règles
|
||||||
|
simples, aussi connues comme le modèle de programmation Plain Old Java Object
|
||||||
|
(POJO). Cependant, aucune de ces règles ne sont des besoins absolus. En effet, Hibernate3 suppose très peu de choses à propos
|
||||||
|
de la nature de vos objets persistants. Vous pouvez exprimer un modèle de domaine par d'autres moyens : utiliser des arbres
|
||||||
|
d'instances de <literal>Map</literal>, par exemple.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-pojo">
|
||||||
|
<title>Un exemple simple de POJO</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Toute bonne application Java nécessite une classe persistante
|
||||||
|
représentant les félins.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Cat {
|
||||||
|
private Long id; // identifier
|
||||||
|
|
||||||
|
private Date birthdate;
|
||||||
|
private Color color;
|
||||||
|
private char sex;
|
||||||
|
private float weight;
|
||||||
|
private int litterId;
|
||||||
|
|
||||||
|
private Cat mother;
|
||||||
|
private Set kittens = new HashSet();
|
||||||
|
|
||||||
|
private void setId(Long id) {
|
||||||
|
this.id=id;
|
||||||
|
}
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBirthdate(Date date) {
|
||||||
|
birthdate = date;
|
||||||
|
}
|
||||||
|
public Date getBirthdate() {
|
||||||
|
return birthdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setWeight(float weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
public float getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
void setColor(Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSex(char sex) {
|
||||||
|
this.sex=sex;
|
||||||
|
}
|
||||||
|
public char getSex() {
|
||||||
|
return sex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLitterId(int id) {
|
||||||
|
this.litterId = id;
|
||||||
|
}
|
||||||
|
public int getLitterId() {
|
||||||
|
return litterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMother(Cat mother) {
|
||||||
|
this.mother = mother;
|
||||||
|
}
|
||||||
|
public Cat getMother() {
|
||||||
|
return mother;
|
||||||
|
}
|
||||||
|
void setKittens(Set kittens) {
|
||||||
|
this.kittens = kittens;
|
||||||
|
}
|
||||||
|
public Set getKittens() {
|
||||||
|
return kittens;
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKitten not needed by Hibernate
|
||||||
|
public void addKitten(Cat kitten) {
|
||||||
|
kitten.setMother(this);
|
||||||
|
kitten.setLitterId( kittens.size() );
|
||||||
|
kittens.add(kitten);
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Il y a quatre règles à suivre ici :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-constructor" revision="1">
|
||||||
|
<title>Implémenter un constructeur sans argument</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> a un constructeur sans argument. Toutes les classes persistantes doivent avoir un
|
||||||
|
constructeur par défaut (lequel peut ne pas être public) pour qu'Hibernate puissent les instancier en utilisant
|
||||||
|
<literal>Constructor.newInstance()</literal>. Nous recommandons fortement d'avoir un constructeur par défaut avec
|
||||||
|
au moins une visibilité <emphasis>paquet</emphasis> pour la génération du proxy à l'exécution dans Hibernate.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-identifier" revision="2">
|
||||||
|
<title>Fournir une propriété d'indentifiant (optionnel)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> possède une propriété appelée <literal>id</literal>.
|
||||||
|
Cette propriété mappe la valeur de la colonne de clé primaire de la table
|
||||||
|
d'une base de données.La propriété aurait pu s'appeler complètement autrement,
|
||||||
|
et son type aurait pu être n'importe quel type primitif, n'importe quel "encapsuleur"
|
||||||
|
de type primitif, <literal>java.lang.String</literal> ou <literal>java.util.Date</literal>.
|
||||||
|
(Si votre base de données héritée possède des clés composites, elles peuvent être mappées
|
||||||
|
en utilisant une classe définie par l'utilisateur et possédant les propriétés associées aux
|
||||||
|
types de la clé composite - voir la section concernant les identifiants composites plus tard).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La propriété d'identifiant est strictement optionnelle. Vous pouver l'oublier et laisser Hibernate
|
||||||
|
s'occuper des identifiants de l'objet en interne. Toutefois, nous ne le recommandons pas.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
En fait, quelques fonctionnalités ne sont disponibles que pour les classes
|
||||||
|
déclarant un identifiant de propriété :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Les réattachements transitifs pour les objets détachés (mise à jour en cascade ou fusion en cascade) -
|
||||||
|
voir <xref linkend="objectstate-transitive"/>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.saveOrUpdate()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>Session.merge()</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous recommandons que vous déclariez les propriétés d'identifiant de manière
|
||||||
|
uniforme. Nous recommandons également que vous utilisiez un type nullable
|
||||||
|
(ie. non primitif).
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-final">
|
||||||
|
<title>Favoriser les classes non finales (optionnel)</title>
|
||||||
|
<para>
|
||||||
|
Une fonctionnalité clef d'Hibernate, les <emphasis>proxies</emphasis>, nécessitent
|
||||||
|
que la classe persistente soit non finale ou qu'elle soit l'implémentation d'une
|
||||||
|
interface qui déclare toutes les méthodes publiques.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Vous pouvez persister, grâce à Hibernate, les classes <literal>final</literal>
|
||||||
|
qui n'implémentent pas d'interface, mais vous ne pourrez pas utiliser les proxies pour les chargements d'associations paresseuses
|
||||||
|
- ce qui limitera vos possibilités d'ajustement des performances.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Vous devriez aussi éviter de déclarer des méthodes <literal>public final</literal> sur des classes
|
||||||
|
non-finales. Si vous voulez utiliser une classe avec une méthode <literal>public final</literal>, vous devez
|
||||||
|
explicitement désactiver les proxies en paramétrant
|
||||||
|
<literal>lazy="false"</literal>.
|
||||||
|
</para>
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
<sect2 id="persistent-classes-pojo-accessors" revision="2">
|
||||||
|
<title>Déclarer les accesseurs et mutateurs des attributs persistants (optionnel)</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>Cat</literal> déclare des mutateurs pour toutes ses champs persistants. Beaucoup d'autres
|
||||||
|
solutions de mapping Objet/relationnel persistent directement les variables d'instance. Nous pensons
|
||||||
|
qu'il est bien mieux de fournir une indirection entre le schéma relationnel et les structures de données internes de la classe.
|
||||||
|
Par défaut, Hibernate persiste les propriétés suivant le style JavaBean, et reconnaît les noms de méthodes de la forme <literal>
|
||||||
|
getFoo</literal>, <literal>isFoo</literal> et
|
||||||
|
<literal>setFoo</literal>. Nous pouvons changer pour un accès direct aux champs pour des propriétés particulières, si besoin est.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les propriétés <emphasis>n'ont pas</emphasis> à être déclarées publiques -
|
||||||
|
Hibernate peut persister une propriété avec un paire de getter/setter de
|
||||||
|
visibilité par défault, <literal>protected</literal> ou <literal>
|
||||||
|
private</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect2>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-inheritance">
|
||||||
|
<title>Implémenter l'héritage</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Une sous-classe doit également suivre la première et la seconde règle.
|
||||||
|
Elle hérite sa propriété d'identifiant de <literal>Cat</literal>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[package eg;
|
||||||
|
|
||||||
|
public class DomesticCat extends Cat {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name=name;
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-equalshashcode" revision="1">
|
||||||
|
<title>Implémenter <literal>equals()</literal> et <literal>hashCode()</literal></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Vous devez surcharger les méthodes <literal>equals()</literal> et
|
||||||
|
<literal>hashCode()</literal> si vous
|
||||||
|
</para>
|
||||||
|
<itemizedlist spacing="compact">
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
avez l'intention de mettre des instances de classes persistantes dans un <literal>Set</literal>
|
||||||
|
(la manière recommandée pour représenter des associations pluri-valuées)
|
||||||
|
<emphasis>et</emphasis>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
avez l'intention d'utiliser le réattachement d'instances détachées
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Hibernate garantit l'équivalence de l'identité persistante (ligne de base de données) et l'identité Java seulement
|
||||||
|
à l'intérieur de la portée d'une session particulière. Donc dès que nous mélangeons des instances venant de différentes
|
||||||
|
sessions, nous devons implémenter <literal>equals()</literal> et
|
||||||
|
<literal>hashCode()</literal> si nous souhaitons avoir une sémantique correcte pour les <literal>Set</literal>s.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
La manière la plus évidente est d'implémenter <literal>equals()</literal>/<literal>hashCode()</literal>
|
||||||
|
en comparant la valeur de l'identifiant des deux objets. Si cette valeur est identique, les deux
|
||||||
|
doivent représenter la même ligne de base de données, ils sont donc égaux (si les deux sont
|
||||||
|
ajoutés à un <literal>Set</literal>, nous n'aurons qu'un seul élément dans le
|
||||||
|
<literal>Set</literal>). Malheureusement, nous ne pouvons pas utiliser cette approche avec
|
||||||
|
des identifiants générés ! Hibernate n'assignera de
|
||||||
|
valeur d'identifiant qu'aux objets qui sont persistants, une instance nouvellement créée n'aura
|
||||||
|
donc pas de valeur d'identifiant ! De plus, si une instance est non sauvegardée et actuellement dans un <literal>Set</literal>,
|
||||||
|
le sauvegarder assignera une valeur d'identifiant à l'objet. Si <literal>equals()</literal> et <literal>hashCode()</literal>
|
||||||
|
sont basées sur la valeur de l'identifiant, le code de hachage devrait changer, rompant le contrat du <literal>Set</literal>.
|
||||||
|
Regardez sur le site web d'Hibernate pour une discussion complète de ce problème.
|
||||||
|
Notez que ceci n'est pas un problème d'Hibernate, mais la sémantique normale de Java pour l'identité d'un objet et l'égalité.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Nous recommandons donc d'implémenter
|
||||||
|
<literal>equals()</literal> et <literal>hashCode()</literal> en utilisant <emphasis>
|
||||||
|
l'égalité par clé métier</emphasis>.L'égalité par clé métier signifie que la méthode <literal>equals()</literal>
|
||||||
|
compare uniquement les propriétés qui forment une clé métier, une clé qui
|
||||||
|
identifierait notre instance dans le monde réel (une clé candidate
|
||||||
|
<emphasis>naturelle</emphasis>) :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[public class Cat {
|
||||||
|
|
||||||
|
...
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) return true;
|
||||||
|
if ( !(other instanceof Cat) ) return false;
|
||||||
|
|
||||||
|
final Cat cat = (Cat) other;
|
||||||
|
|
||||||
|
if ( !cat.getLitterId().equals( getLitterId() ) ) return false;
|
||||||
|
if ( !cat.getMother().equals( getMother() ) ) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int result;
|
||||||
|
result = getMother().hashCode();
|
||||||
|
result = 29 * result + getLitterId();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez qu'une clef métier ne doit pas être solide comme une clef primaire de base de données
|
||||||
|
(voir <xref linkend="transactions-basics-identity"/>). Les propriétés
|
||||||
|
immuables ou uniques sont généralement de bonnes candidates pour une clef métier.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-dynamicmodels">
|
||||||
|
<title>Modèles dynamiques</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<emphasis>Notez que la fonctionnalités suivantes sont actuellement considérées
|
||||||
|
comme expérimentales et peuvent changer dans un futur proche.</emphasis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les entités persistantes ne doivent pas nécessairement être représentées comme
|
||||||
|
des classes POJO ou des objets JavaBean à l'exécution. Hibernate supporte aussi les
|
||||||
|
modèles dynamiques (en utilisant des <literal>Map</literal>s de <literal>Map</literal>s
|
||||||
|
à l'exécution) et la représentation des entités comme des arbres DOM4J. Avec cette
|
||||||
|
approche, vous n'écrivez pas de classes persistantes, seulement des fichiers de mapping.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Par défaut, Hibernate fonctionne en mode POJO normal. Vous pouvez paramétrer
|
||||||
|
un mode de représentation d'entité par défaut pour une <literal>SessionFactory</literal>
|
||||||
|
particulière en utilisant l'option de configuration <literal>default_entity_mode</literal>
|
||||||
|
(voir <xref linkend="configuration-optional-properties"/>).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les exemples suivants démontrent la représentation utilisant des <literal>Map</literal>s.
|
||||||
|
D'abord, dans le fichier de mapping, un <literal>entity-name</literal> doit être déclaré
|
||||||
|
au lieu (ou en plus) d'un nom de classe :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
|
||||||
|
<class entity-name="Customer">
|
||||||
|
|
||||||
|
<id name="id"
|
||||||
|
type="long"
|
||||||
|
column="ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<property name="name"
|
||||||
|
column="NAME"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<property name="address"
|
||||||
|
column="ADDRESS"
|
||||||
|
type="string"/>
|
||||||
|
|
||||||
|
<many-to-one name="organization"
|
||||||
|
column="ORGANIZATION_ID"
|
||||||
|
class="Organization"/>
|
||||||
|
|
||||||
|
<bag name="orders"
|
||||||
|
inverse="true"
|
||||||
|
lazy="false"
|
||||||
|
cascade="all">
|
||||||
|
<key column="CUSTOMER_ID"/>
|
||||||
|
<one-to-many class="Order"/>
|
||||||
|
</bag>
|
||||||
|
|
||||||
|
</class>
|
||||||
|
|
||||||
|
</hibernate-mapping>]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Notez que même si des associations sont déclarées en utilisant des noms de classe cible,
|
||||||
|
le type de cible d'une association peut aussi être une entité dynamique au lieu d'un POJO.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Après avoir configuré le mode d'entité par défaut à <literal>dynamic-map</literal>
|
||||||
|
pour la <literal>SessionFactory</literal>, nous pouvons lors de l'exécution fonctionner
|
||||||
|
avec des <literal>Map</literal>s de <literal>Map</literal>s :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session s = openSession();
|
||||||
|
Transaction tx = s.beginTransaction();
|
||||||
|
Session s = openSession();
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
|
||||||
|
// Create an organization
|
||||||
|
Map foobar = new HashMap();
|
||||||
|
foobar.put("name", "Foobar Inc.");
|
||||||
|
|
||||||
|
// Link both
|
||||||
|
david.put("organization", foobar);
|
||||||
|
|
||||||
|
// Save both
|
||||||
|
s.save("Customer", david);
|
||||||
|
s.save("Organization", foobar);
|
||||||
|
|
||||||
|
tx.commit();
|
||||||
|
s.close();]]></programlisting>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les avantages d'un mapping dynamique sont un gain de temps pour le prototypage
|
||||||
|
sans la nécessité d'implémenter les classes d'entité. Pourtant, vous perdez la
|
||||||
|
vérification du typage au moment de la compilation et aurez plus d'exceptions à
|
||||||
|
gérer lors de l'exécution. Grâce au mapping d'Hibernate, le schéma de la base de
|
||||||
|
données peut facilement être normalisé et solidifié, permettant de rajouter une
|
||||||
|
implémentation propre du modèle de domaine plus tard.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les modes de représentation d'une entité peut aussi être configuré par <literal>Session</literal> :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
|
||||||
|
|
||||||
|
// Create a customer
|
||||||
|
Map david = new HashMap();
|
||||||
|
david.put("name", "David");
|
||||||
|
dynamicSession.save("Customer", david);
|
||||||
|
...
|
||||||
|
dynamicSession.flush();
|
||||||
|
dynamicSession.close()
|
||||||
|
...
|
||||||
|
// Continue on pojoSession
|
||||||
|
]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Veuillez noter que l'appel à <literal>getSession()</literal> en utilisant un
|
||||||
|
<literal>EntityMode</literal> se fait sur l'API <literal>Session</literal>, pas
|
||||||
|
<literal>SessionFactory</literal>. De cette manière, la nouvelle <literal>Session</literal>
|
||||||
|
partage les connexions JDBC, transactions et autres informations de contexte sous-jacentes.
|
||||||
|
Cela signifie que vous n'avez pas à appeler <literal>flush()</literal> et <literal>close()</literal>
|
||||||
|
sur la <literal>Session</literal> secondaire, et laissez aussi la gestion de la transaction
|
||||||
|
et de la connexion à l'unité de travail primaire.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Plus d'informations à propos de la représentation XML peuvent être trouvées dans
|
||||||
|
<xref linkend="xml"/>.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="persistent-classes-tuplizers" revision="0">
|
||||||
|
<title>Tuplizers</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<literal>org.hibernate.tuple.Tuplizer</literal>, et ses sous-interfaces, sont responsables
|
||||||
|
de la gestion d'une représentation particulière d'un morceau de données, en fonction du
|
||||||
|
<literal>org.hibernate.EntityMode</literal> de réprésentation. Si un morceau donné de données
|
||||||
|
est pensé comme une structure de données, alors un tuplizer est la chose qui sait comment
|
||||||
|
créer une telle structure de données, comment extraire des valeurs et injecter des valeurs dans
|
||||||
|
une telle structure de données. Par exemple, pour le mode d'entité POJO, le tuplizer correspondant
|
||||||
|
sait comment créer le POJO à travers son constructeur et comment accéder aux propriétés du POJO
|
||||||
|
utilisant les accesseurs de la propriété définie. Il y a deux types de Tuplizers haut niveau,
|
||||||
|
représenté par les interfaces <literal>org.hibernate.tuple.EntityTuplizer</literal> et
|
||||||
|
<literal>org.hibernate.tuple.ComponentTuplizer</literal>. Les <literal>EntityTuplizer</literal>s
|
||||||
|
sont responsables de la gestion des contrats mentionnés ci-dessus pour les entités, alors que
|
||||||
|
les <literal>ComponentTuplizer</literal>s s'occupent des composants.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Les utilisateurs peuvent aussi brancher leurs propres tuplizers. Peut-être vous est-il nécessaire qu'une
|
||||||
|
implémentation de <literal>java.util.Map</literal> autre que <literal>java.util.HashMap</literal>
|
||||||
|
soit utilisée dans le mode d'entité dynamic-map ; ou peut-être avez-vous besoin de définir une
|
||||||
|
statégie de génération de proxy différente de celle utilisée par défaut. Les deux devraient être
|
||||||
|
effectuées en définissant une implémentation de tuplizer utilisateur. Les définitions de tuplizers
|
||||||
|
sont attachées au mapping de l'entité ou du composant qu'ils sont censés gérer. Retour à l'exemple de
|
||||||
|
notre entité utilisateur :
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||||||
|
<class entity-name="Customer">
|
||||||
|
<!--
|
||||||
|
Override the dynamic-map entity-mode
|
||||||
|
tuplizer for the customer entity
|
||||||
|
-->
|
||||||
|
<tuplizer entity-mode="dynamic-map"
|
||||||
|
class="CustomMapTuplizerImpl"/>
|
||||||
|
|
||||||
|
<id name="id" type="long" column="ID">
|
||||||
|
<generator class="sequence"/>
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<!-- other properties -->
|
||||||
|
...
|
||||||
|
</class>
|
||||||
|
</hibernate-mapping>
|
||||||
|
|
||||||
|
|
||||||
|
public class CustomMapTuplizerImpl
|
||||||
|
extends org.hibernate.tuple.DynamicMapEntityTuplizer {
|
||||||
|
// override the buildInstantiator() method to plug in our custom map...
|
||||||
|
protected final Instantiator buildInstantiator(
|
||||||
|
org.hibernate.mapping.PersistentClass mappingInfo) {
|
||||||
|
return new CustomMapInstantiator( mappingInfo );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CustomMapInstantiator
|
||||||
|
extends org.hibernate.tuple.DynamicMapInstantitor {
|
||||||
|
// override the generateMap() method to return our custom map...
|
||||||
|
protected final Map generateMap() {
|
||||||
|
return new CustomMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]]></programlisting>
|
||||||
|
|
||||||
|
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
TODO: Document user-extension framework in the property and proxy packages
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</chapter>
|
||||||
|
|