Match to latest English XML

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14153 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
xhuang 2007-10-27 12:53:39 +00:00
parent 9817470c54
commit 9fdf63d3cb
15 changed files with 1799 additions and 333 deletions

View File

@ -332,6 +332,45 @@ create table Person ( personId bigint not null primary key, addressId bigint not
create table Address ( addressId bigint not null primary key )
]]></programlisting>
<para> UNTRANSLATED!
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>UNTRANSLATED!
It is important that you define <literal>not-null="true"</literal> on the
<literal>&lt;key&gt;</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>&lt;column&gt;</literal> element, but on the <literal>&lt;key&gt;</literal>
element.
</para>
</sect2>
<sect2 id="assoc-bidirectional-121">

View File

@ -111,7 +111,59 @@
b&#x00fa;squedas del DTD usando una conexi&#x00f3;n de Internet, chequea tu declaraci&#x00f3;n
de DTD contra la contenida en el classpath.
</para>
</sect2>
<sect3 id="mapping-declaration-entity-resolution">
<title>UNTRANSLATED! EntityResolver</title>
<para>
As mentioned previously, Hibernate will first attempt to resolve DTDs in its classpath. The
manner in which it does this is by registering a custom <literal>org.xml.sax.EntityResolver</literal>
implementation with the SAXReader it uses to read in the xml files. This custom
<literal>EntityResolver</literal> recognizes two different systemId namespaces.
</para>
<itemizedlist>
<listitem>
<para>
a <literal>hibernate namespace</literal> is recognized whenever the
resolver encounteres a systemId starting with
<literal>http://hibernate.sourceforge.net/</literal>; the resolver
attempts to resolve these entities via the classlaoder which loaded
the Hibernate classes.
</para>
</listitem>
<listitem>
<para>
a <literal>user namespace</literal> is recognized whenever the
resolver encounteres a systemId using a <literal>classpath://</literal>
URL protocol; the resolver will attempt to resolve these entities
via (1) the current thread context classloader and (2) the
classloader which loaded the Hibernate classes.
</para>
</listitem>
</itemizedlist>
<para>
An example of utilizing user namespacing:
</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" [
<!ENTITY types SYSTEM "classpath://your/domain/types.xml">
]>
<hibernate-mapping package="your.domain">
<class name="MyEntity">
<id name="id" type="my-custom-id-type">
...
</id>
<class>
&types;
</hibernate-mapping>]]></programlisting>
<para>
Where <literal>types.xml</literal> is a resource in the <literal>your.domain</literal>
package and contains a custom <link linkend="mapping-types-custom">typedef</link>.
</para>
</sect3>
</sect2>
<sect2 id="mapping-declaration-mapping" revision="3">
<title>hibernate-mapping</title>
@ -758,6 +810,21 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>UNTRANSLATED! sequence-identity</literal></term>
<listitem>
<para>
a specialized sequence generation strategy which utilizes a
database sequence for the actual value generation, but combines
this with JDBC3 getGeneratedKeys to actually return the generated
identifier value as part of the insert statement execution. This
strategy is only known to be supported on Oracle 10g drivers
targetted for JDK 1.4. Note comments on these insert statements
are disabled due to a bug in the Oracle drivers.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
@ -877,6 +944,172 @@
</sect3>
</sect2>
<sect2 id="mapping-declaration-id-enhanced">
<title>Enhanced identifier generators</title>
<para>
Starting with release 3.2.3, there are 2 new generators which represent a re-thinking of 2 different
aspects of identifier generation. The first aspect is database portability; the second is optimization
(not having to query the database for every request for a new identifier value). These two new
generators are intended to take the place of some of the named generators described above (starting
in 3.3.x); however, they are included in the current releases and can be referenced by FQN.
</para>
<para>
The first of these new generators is <literal>org.hibernate.id.enhanced.SequenceStyleGenerator</literal>
which is intended firstly as a replacement for the <literal>sequence</literal> generator and secondly as
a better portability generator than <literal>native</literal> (because <literal>native</literal>
(generally) chooses between <literal>identity</literal> and <literal>sequence</literal> which have
largely different semantics which can cause subtle isssues in applications eyeing portability).
<literal>org.hibernate.id.enhanced.SequenceStyleGenerator</literal> however achieves portability in
a different manner. It chooses between using a table or a sequence in the database to store its
incrementing values depending on the capabilities of the dialect being used. The difference between this
and <literal>native</literal> is that table-based and sequence-based storage have the same exact
semantic (in fact sequences are exactly what Hibernate tries to emmulate with its table-based
generators). This generator has a number of configuration parameters:
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>sequence_name</literal> (optional, defaults to <literal>hibernate_sequence</literal>):
The name of the sequence (or table) to be used.
</para>
</listitem>
<listitem>
<para>
<literal>initial_value</literal> (optional, defaults to <literal>1</literal>): The initial
value to be retrieved from the sequence/table. In sequence creation terms, this is analogous
to the clause typical named "STARTS WITH".
</para>
</listitem>
<listitem>
<para>
<literal>increment_size</literal> (optional, defaults to <literal>1</literal>): The value by
which subsequent calls to the sequence/table should differ. In sequence creation terms, this
is analogous to the clause typical named "INCREMENT BY".
</para>
</listitem>
<listitem>
<para>
<literal>force_table_use</literal> (optional, defaults to <literal>false</literal>): Should
we force the use of a table as the backing structure even though the dialect might support
sequence?
</para>
</listitem>
<listitem>
<para>
<literal>value_column</literal> (optional, defaults to <literal>next_val</literal>): Only
relevant for table structures! The name of the column on the table which is used to
hold the value.
</para>
</listitem>
<listitem>
<para>
<literal>optimizer</literal> (optional, defaults to <literal>none</literal>):
See <xref linkend="mapping-declaration-id-enhanced-optimizers"/>
</para>
</listitem>
</itemizedlist>
</para>
<para>
The second of these new generators is <literal>org.hibernate.id.enhanced.TableGenerator</literal> which
is intended firstly as a replacement for the <literal>table</literal> generator (although it actually
functions much more like <literal>org.hibernate.id.MultipleHiLoPerTableGenerator</literal>) and secondly
as a re-implementation of <literal>org.hibernate.id.MultipleHiLoPerTableGenerator</literal> utilizing the
notion of pluggable optimiziers. Essentially this generator defines a table capable of holding
a number of different increment values simultaneously by using multiple distinctly keyed rows. This
generator has a number of configuration parameters:
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>table_name</literal> (optional, defaults to <literal>hibernate_sequences</literal>):
The name of the table to be used.
</para>
</listitem>
<listitem>
<para>
<literal>value_column_name</literal> (optional, defaults to <literal>next_val</literal>):
The name of the column on the table which is used to hold the value.
</para>
</listitem>
<listitem>
<para>
<literal>segment_column_name</literal> (optional, defaults to <literal>sequence_name</literal>):
The name of the column on the table which is used to hold the "segement key". This is the
value which distinctly identifies which increment value to use.
</para>
</listitem>
<listitem>
<para>
<literal>segment_value</literal> (optional, defaults to <literal>default</literal>):
The "segment key" value for the segment from which we want to pull increment values for
this generator.
</para>
</listitem>
<listitem>
<para>
<literal>segment_value_length</literal> (optional, defaults to <literal>255</literal>):
Used for schema generation; the column size to create this segment key column.
</para>
</listitem>
<listitem>
<para>
<literal>initial_value</literal> (optional, defaults to <literal>1</literal>):
The initial value to be retrieved from the table.
</para>
</listitem>
<listitem>
<para>
<literal>increment_size</literal> (optional, defaults to <literal>1</literal>):
The value by which subsequent calls to the table should differ.
</para>
</listitem>
<listitem>
<para>
<literal>optimizer</literal> (optional, defaults to <literal></literal>):
See <xref linkend="mapping-declaration-id-enhanced-optimizers"/>
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
<sect2 id="mapping-declaration-id-enhanced-optimizers">
<title>Identifier generator optimization</title>
<para>
For identifier generators which store values in the database, it is inefficient for them to hit the
database on each and every call to generate a new identifier value. Instead, you'd ideally want to
group a bunch of them in memory and only hit the database when you have exhausted your in-memory
value group. This is the role of the pluggable optimizers. Currently only the two enhanced generators
(<xref linkend="mapping-declaration-id-enhanced"/> support this notion.
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>none</literal> (generally this is the default if no optimizer was specified): This
says to not perform any optimizations, and hit the database each and every request.
</para>
</listitem>
<listitem>
<para>
<literal>hilo</literal>: applies a hi/lo algorithm around the database retrieved values. The
values from the database for this optimizer are expected to be sequential. The values
retrieved from the database structure for this optimizer indicates the "group number"; the
<literal>increment_size</literal> is multiplied by that value in memory to define a group
"hi value".
</para>
</listitem>
<listitem>
<para>
<literal>pooled</literal>: like was discussed for <literal>hilo</literal>, this optimizers
attempts to minimize the number of hits to the database. Here, however, we simply store
the starting value for the "next group" into the database structure rather than a sequential
value in combination with an in-memory grouping algorithm. <literal>increment_size</literal>
here refers to the values coming from the database.
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
<sect2 id="mapping-declaration-compositeid" revision="2">
<title>composite-id</title>
@ -920,6 +1153,53 @@
compuesto est&#x00e1; implementado como una clase separada en <xref linkend="components-compositeid"/>.
Los atributos descriptos debajo solamente se aplican a este enfoque alternativo:
</para>
<para>
A second approach is what we call a <emphasis>mapped</emphasis> composite identifier,
where the identifier properties named inside the <literal>&lt;composite-id&gt;</literal>
element are duplicated on both the persistent class and a separate identifier class.
</para>
<programlisting><![CDATA[<composite-id class="MedicareId" mapped="true">
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>]]></programlisting>
<para>
In this example, both the composite identifier class, <literal>MedicareId</literal>,
and the entity class itself have properties named <literal>medicareNumber</literal>
and <literal>dependent</literal>. The identifier class must override
<literal>equals()</literal> and <literal>hashCode()</literal> and implement.
<literal>Serializable</literal>. The disadvantage of this approach is quite
obvious&mdash;code duplication.
</para>
<para>
The following attributes are used to specify a mapped composite identifier:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>mapped</literal> (optional, defaults to <literal>false</literal>):
indicates that a mapped composite identifier is used, and that the contained
property mappings refer to both the entity class and the composite identifier
class.
</para>
</listitem>
<listitem>
<para>
<literal>class</literal> (optional, but required for a mapped composite identifier):
The class used as a composite identifier.
</para>
</listitem>
</itemizedlist>
<para>
We will describe a third, even more convenient approach where the composite identifier
is implemented as a component class in <xref linkend="components-compositeid"/>. The
attributes described below apply only to this alternative approach:
</para>
<itemizedlist spacing="compact">
<listitem>
@ -927,6 +1207,11 @@
<literal>name</literal> (opcional): Una propiedad de tipo componente que tiene el identificador
compuesto (ver siguiente secci&#x00f3;n).
</para>
</listitem> <listitem>
<para>
<literal>access</literal> (optional - defaults to <literal>property</literal>):
The strategy Hibernate should use for accessing the property value.
</para>
</listitem>
<listitem>
<para>
@ -934,20 +1219,12 @@
por reflecci&#x00f3;n): La clase del componente usado como identificador compuesto (ver siguiente secci&#x00f3;n).
</para>
</listitem>
<listitem>
<para>
<literal>unsaved-value</literal> (opcional - por defecto a <literal>undefined</literal>):
Indica que las instancias transitorias deben ser consideradas como reci&#x00e9;n instanciadas,
si se establece a <literal>any</literal>, o separadas, si se establece a <literal>none</literal>.
Lo mejor
Indicates that transient instances should be considered newly instantiated, if set
to <literal>any</literal>, or detached, if set to <literal>none</literal>.
Lo mejor en todos los casos es dejar el valor por defecto.
</para>
</listitem>
</itemizedlist>
<para>
This third approach, an <emphasis>identifier component</emphasis> is the one we recommend
for almost all applications.
</para>
</sect2>
<sect2 id="mapping-declaration-discriminator" revision="3">
@ -1099,6 +1376,21 @@
propiedad identificadora.)
</para>
</callout>
<callout arearefs="version6">
<para> UNTRANSLATED!
<literal>generated</literal> (optional - defaults to <literal>never</literal>):
Specifies that this version property value is actually generated by the database.
See the discussion of <link linkend="mapping-generated">generated properties</link>.
</para>
</callout>
<callout arearefs="version7">
<para>UNTRANSLATED!
<literal>insert</literal> (optional - defaults to <literal>true</literal>):
Specifies whether the version column should be included in SQL insert statements.
May be set to <literal>false</literal> if and only if the database column
is defined with a default value of <literal>0</literal>.
</para>
</callout>
</calloutlist>
</programlistingco>
@ -1172,7 +1464,26 @@
(<literal>undefined</literal> especifica que debe usarse el valor de la propiedad
identificadora.)
</para>
</callout>
</callout> <callout arearefs="timestamp5">
<para> UNTRANSLATED!
<literal>source</literal> (optional - defaults to <literal>vm</literal>):
From where should Hibernate retrieve the timestamp value? From the database,
or from the current JVM? Database-based timestamps incur an overhead because
Hibernate must hit the database in order to determine the "next value",
but will be safer for use in clustered environments. Note also, that not
all <literal>Dialect</literal>s are known to support retrieving of the
database's current timestamp, while others might be unsafe for usage
in locking due to lack of precision (Oracle 8 for example).
</para>
</callout>
<callout arearefs="timestamp6">
<para> UNTRANSLATED!
<literal>generated</literal> (optional - defaults to <literal>never</literal>):
Specifies that this timestamp property value is actually generated by the database.
See the discussion of <link linkend="mapping-generated">generated properties</link>.
</para>
</callout>
</calloutlist>
</programlistingco>
@ -1295,6 +1606,12 @@
de un bloqueo optimista. En otras palabras, determina si debe ocurrir un incremento de versi&#x00f3;n
cuando la propiedad este sucia (desactualizada).
</para>
</callout> <callout arearefs="property12">
<para> UNTRANSLATED!
<literal>generated</literal> (optional - defaults to <literal>never</literal>):
Specifies that this property value is actually generated by the database.
See the discussion of <link linkend="mapping-generated">generated properties</link>.
</para>
</callout>
</calloutlist>
</programlistingco>
@ -1580,6 +1897,9 @@
Si la clave &#x00fa;nica referenciada abarca m&#x00fa;ltiples propiedades de la entidad asociada,
debes mapear las propiedades dentro de un elemento <literal>&lt;properties&gt;</literal>.
</para>
<para>UNTRANSLATED!
If the referenced unique key is the property of a component, you may specify a property path:
</para>
</sect2>
@ -1686,6 +2006,10 @@
la aplicaci&#x00f3;n de proxies es imposible e Hibernate traer&#x00e1; temprano la
asociaci&#x00f3;n!</emphasis>
</para>
</callout> <callout arearefs="onetoone10">
<para>
<literal>entity-name</literal> (optional): The entity name of the associated class.
</para>
</callout>
</calloutlist>
</programlistingco>
@ -2075,33 +2399,13 @@
</calloutlist>
</programlistingco>
<para>
Cada subclase debe declarar sus propias propiedades persistentes y subclases.
Se asume que las propiedades <literal>&lt;version&gt;</literal> y <literal>&lt;id&gt;</literal>
son heredadas de la clase ra&#x00ed;z. Cada subclase en una jerarqu&#x00ed;a debe
definir un <literal>discriminator-value</literal> &#x00fa;nico. Si no se especifica ninguno,
se usa el nombre completamente cualificado de clase Java.
<para> UNTRANSLATED!
Each subclass should declare its own persistent properties and subclasses.
<literal>&lt;version&gt;</literal> and <literal>&lt;id&gt;</literal> properties
are assumed to be inherited from the root class. Each subclass in a heirarchy must
define a unique <literal>discriminator-value</literal>. If none is specified, the
fully qualified Java class name is used.
</para>
<para>
Es posible definir mapeos <literal>subclass</literal>, <literal>union-subclass</literal>,
y <literal>joined-subclass</literal> en documentos de mapeo separados, directamente debajo
de <literal>hibernate-mapping</literal>. Esto te permite extender una jerarqu&#x00ed;a de clases
con s&#x00f3;lo agregar un nuevo fichero de mapeo. Debes especificar un atributo
<literal>extends</literal> en el mapeo de la subclase, mencionando una superclase previamente mapeada.
Nota: Previamente esta funcionalidad hac&#x00ed;a importante el orden de los documentos de mapeo.
Desde Hibernate3, el orden de los ficheros de mapeo no importa cuando se usa la palabra reservada
extends. El orden dentro de un mismo fichero de mapeo todav&#x00ed;a necesita ser definido como
superclases antes de subclases.
</para>
<programlisting><![CDATA[
<hibernate-mapping>
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
<property name="name" type="string"/>
</subclass>
</hibernate-mapping>]]></programlisting>
<para>
Para informaci&#x00f3;n acerca de mapeos de herencia, ver <xref linkend="inheritance"/>.
</para>
@ -2871,7 +3175,24 @@
ser malo e inconsistente).
</para>
</listitem>
</varlistentry>
</varlistentry> <varlistentry>
<term>
<literal>UNTRANSLATED! imm_date, imm_time, imm_timestamp, imm_calendar, imm_calendar_date,
imm_serializable, imm_binary</literal>
</term>
<listitem>
<para>
Type mappings for what are usually considered mutable Java types, where
Hibernate makes certain optimizations appropriate only for immutable
Java types, and the application treats the object as immutable. For
example, you should not call <literal>Date.setTime()</literal> for an
instance mapped as <literal>imm_timestamp</literal>. To change the
value of the property, and have that change made persistent, the
application must assign a new (nonidentical) object to the property.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>

View File

@ -31,14 +31,20 @@ session.close();]]></programlisting>
</para>
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
<para id="disablebatching" revision="1">
UNTRANSLATED! Note that Hibernate disables insert batching at the JDBC level transparently if you
use an <literal>identiy</literal> identifier generator.
</para>
<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>
<para> UNTRANSLATED!
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>Inserciones en lote</title>
@ -96,7 +102,57 @@ tx.commit();
session.close();]]></programlisting>
</sect1>
<sect1 id="batch-statelesssession">
<title>UNTRANSLATED! 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 life cycle 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">
<title>update/delete en masa</title>
@ -125,14 +181,17 @@ session.close();]]></programlisting>
</listitem>
<listitem>
<para>
Puede haber sólo una clase mencionada en la cláusula-from, y <emphasis>no puede</emphasis>
tener un alias.
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 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.
No <link linkend="queryhql-joins-forms">joins</link> (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>
@ -144,24 +203,50 @@ session.close();]]></programlisting>
<para>
Como un ejemplo, para ejecutar un <literal>UPDATE</literal> HQL, usa el
método <literal>Query.executeUpdate()</literal>:
método <literal>Query.executeUpdate()</literal>(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 set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<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
<link linkend="mapping-declaration-version">version</link>
or the <link linkend="mapping-declaration-timestamp">timestamp</link> 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>
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 ejecutar un <literal>DELETE</literal> HQL, usa el mismo método <literal>Query.executeUpdate()</literal>:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();

View File

@ -230,13 +230,20 @@ kittens = cat.getKittens(); // Okay, kittens collection is a Set
propiedad.
</para>
</callout>
<callout arearefs="mappingcollection12">
<callout arearefs="mappingcollection13">
<para>
<literal>optimistic-lock</literal> (opcional - por defecto a <literal>true</literal>):
Especifica que los cambios de estado de la colecci&#x00f3;n resultan en
incrementos de versi&#x00f3;n de la entidad due&#x00f1;a. (Para asociaciones
uno a muchos, frecuentemente es razonable deshabilitar esta opci&#x00f3;n.)
</para>
</callout>
<callout arearefs="mappingcollection14">
<para>
<literal>mutable</literal> (optional - defaults to <literal>true</literal>):
A value of <literal>false</literal> specifies that the elements of the
collection never change (a minor performance optimization in some cases).
</para>
</callout>
</calloutlist>
</programlistingco>
@ -555,7 +562,13 @@ kittens = cat.getKittens(); // Okay, kittens collection is a Set
<literal>entity-name</literal> (opcional): El nombre de entidad de la clase
asociada, como una alternativa a <literal>class</literal>.
</para>
</callout>
</callout> <callout arearefs="manytomany8">
<para> UNTRANSLATED!
<literal>property-ref</literal>: (optional) The name of a property of the associated
class that is joined to this foreign key. If not specified, the primary key of
the associated class is used.
</para>
</callout>
</calloutlist>
</programlistingco>
@ -841,7 +854,7 @@ kittens = cat.getKittens(); // Okay, kittens collection is a Set
</class>
<class name="Item">
<id name="id" column="ITEM_ID"/>
<id name="id" column="ITEM_ID"/>
...
<!-- inverse end -->

View File

@ -392,11 +392,25 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
</entry>
<entry>
Escribe todas las sentencias SQL a la consola.
<para>
<emphasis role="strong">ej.</emphasis>
<literal>true</literal> | <literal>false</literal>
This is an alternative
to setting the log category <literal>org.hibernate.SQL</literal>
to <literal>debug</literal>.
<para>
<emphasis role="strong">eg.</emphasis>
<literal>true</literal> | <literal>false</literal>
</para>
</entry>
</row> <row>
<entry>
<literal>hibernate.format_sql</literal>
</entry>
<entry>
Pretty print the SQL in the log and console.
<para>
<emphasis role="strong">eg.</emphasis>
<literal>true</literal> | <literal>false</literal>
</para>
</entry>
</row>
<row>
<entry>
@ -715,6 +729,13 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<emphasis role="strong">ej.</emphasis>
<literal>on_close</literal> (por defecto)| <literal>after_transaction</literal> |
<literal>after_statement</literal> | <literal>auto</literal>
</para> <para>
Note that this setting only affects <literal>Session</literal>s returned from
<literal>SessionFactory.openSession</literal>. For <literal>Session</literal>s
obtained through <literal>SessionFactory.getCurrentSession</literal>, the
<literal>CurrentSessionContext</literal> implementation configured for use
controls the connection release mode for those <literal>Session</literal>s.
See <xref linkend="architecture-current-session"/>
</para>
</entry>
</row>
@ -928,7 +949,7 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<entry>
De habilitarse, la sesi&#x00f3;n ser&#x00e1; cerrada autom&#x00e1;ticamente
durante la fase posterior a la compleci&#x00f3;n de la transacci&#x00f3;n.
(Muy &#x00fa;til cuando se usa Hibernate con CMT).
(Muy &#x00fa;til cuando se usa Hibernate con CMT).<xref linkend="architecture-current-session"/>
<para>
<emphasis role="strong">ej.</emphasis>
<literal>true</literal> | <literal>false</literal>
@ -950,7 +971,22 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<entry>Prop&#x00f3;sito</entry>
</row>
</thead>
<tbody>
<tbody> <row>
<entry>
<literal>hibernate.current_session_context_class</literal>
</entry>
<entry>
Supply a (custom) strategy for the scoping of the "current"
<literal>Session</literal>. See
<xref linkend="architecture-current-session"/> for more
information about the built-in strategies.
<para>
<emphasis role="strong">eg.</emphasis>
<literal>jta</literal> | <literal>thread</literal> |
<literal>managed</literal> | <literal>custom.Class</literal>
</para>
</entry>
</row>
<row>
<entry>
<literal>hibernate.query.factory_class</literal>
@ -1636,7 +1672,10 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
de arranque (o clase de utilidad) en tu aplicaci&#x00f3;n, a menos qie uses el despliegue
JMX con el <literal>HibernateService</literal> (discutido luego).
</para>
<para> UNTRANSLATED!
If you use a JNDI <literal>SessionFactory</literal>, an EJB or any other class may
obtain the <literal>SessionFactory</literal> using a JNDI lookup.
</para>
<para>
Si usas una <literal>SessionFactory</literal> de JNDI, un EJB o cualquier otra
clase puede obtener la <literal>SessionFactory</literal> usando una b&#x00fa;squeda
@ -1651,36 +1690,27 @@ hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect]]></programlisting>
<sect2 id="configuration-j2ee-currentsession" revision="1">
<title>Ligado autom&#x00e1;tico de JTA y Session</title>
<para>
Para entornos no manejados hemos sugerido <literal>HibernateUtil</literal> con una
<literal>SessionFactory</literal> est&#x00e1;tica, y administraci&#x00f3;n de la
<literal>Session</literal> de Hibernate. Este enfoque no es f&#x00e1;cil de usar
en un entorno EJB, al poder ejecutarse muchos EJBs dentro de la misma transacci&#x00f3;n
pero no en la misma hebra. Recomendados que ligues la <literal>SessionFactory</literal>
a JNDI en un entorno manejado.
<para> UNTRANSLATED!
The easiest way to handle <literal>Session</literal>s and transactions is
Hibernates automatic "current" <literal>Session</literal> management.
See the discussion of <link linkend="architecture-current-session">current sessions</link>.
Using the <literal>"jta"</literal> session context, if there is no Hibernate
<literal>Session</literal> associated with the current JTA transaction, one will
be started and associated with that JTA transaction the first time you call
<literal>sessionFactory.getCurrentSession()</literal>. The <literal>Session</literal>s
retrieved via <literal>getCurrentSession()</literal> in <literal>"jta"</literal> context
will be set to automatically flush before the transaction completes, close
after the transaction completes, and aggressively release JDBC connections
after each statement. This allows the <literal>Session</literal>s to
be managed by the life cycle of the JTA transaction to which it is associated,
keeping user code clean of such management concerns. Your code can either use
JTA programmatically through <literal>UserTransaction</literal>, or (recommended
for portable code) use the Hibernate <literal>Transaction</literal> API to set
transaction boundaries. If you run in an EJB container, declarative transaction
demarcation with CMT is preferred.
</para>
<para>
En vez de rodar tu propia utilidad de <literal>ThreadLocal</literal>,
usa el m&#x00e9;todo <literal>getCurrentSession()</literal> en la
<literal>SessionFactory</literal> para obtener una <literal>Session</literal>
de Hibernate. Si no hubiese una <literal>Session</literal> de Hibernate en la
transacci&#x00f3;n JTA actual, se arrancar&#x00e1; y asignar&#x00e1; una.
Ambas opciones de configuraci&#x00f3;n <literal>hibernate.transaction.flush_before_completion</literal>
y <literal>hibernate.transaction.auto_close_session</literal>, ser&#x00e1;n establecidas
autom&#x00e1;ticamente para cada <literal>Session</literal> que obtengas con
<literal>getCurrentSession()</literal>, de modo que &#x00e9;stas ser&#x00e1;n
limpiadas (flushed) y cerradas autom&#x00e1;ticamente cuando el contenedor complete
las transacciones JTA.
</para>
<para>
Si tu, por ejemplo, usas el patr&#x00f3;n de dise&#x00f1;o DAO para escribir tu
capa de persistencia, todos los DAO's buscan la <literal>SessionFactory</literal>
cuando se necesite y abren la sesi&#x00f3;n "actual". No hay necesidad de pasar
las instancias de <literal>SessionFactory</literal> o <literal>Session</literal>
alrededor entre el c&#x00f3;digo de control y el c&#x00f3;digo DAO.
</para>
</sect2>

View File

@ -22,7 +22,10 @@
creado y actualiza la propiedad <literal>lastUpdateTimestamp</literal> cuando un
<literal>Auditable</literal> es acutalizado.
</para>
<para> UNTRANSLATED!
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;
@ -103,14 +106,24 @@ public class AuditInterceptor implements Interceptor, Serializable {
}]]></programlisting>
<para>
El interceptor podr&#x00ed;a ser especificado cuando se crea la sesi&#x00f3;n:
<para> UNTRANSLATED!
Interceptors come in two flavors: <literal>Session</literal>-scoped and
<literal>SessionFactory</literal>-scoped.
</para>
<para> UNTRANSLATED!
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>
Puedes adem&#x00e1;s establecer un interceptor a un nivel global, usando la <literal>Configuration</literal>:
<para> UNTRANSLATED!
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>
@ -218,7 +231,11 @@ cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() )
<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> UNTRANSLATED!
Note that <literal>&lt;listener type="..." class="..."/&gt;</literal> is just a shorthand
for <literal>&lt;event type="..."&gt;&lt;listener class="..."/&gt;&lt;/event&gt;</literal>
when there is exactly one listener for a particular event type.
</para>
<para>
Seguido, a&#x00fa;n en <literal>hibernate.cfg.xml</literal>, liga los permisos a roles:
</para>

View File

@ -42,6 +42,24 @@
</listitem>
</itemizedlist>
<para> UNTRANSLATED!
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>
<para>
Es posible usar estrategias de mapeo diferentes para diferentes
ramificaciones de la misma jerarqu&#x00ed;a de herencia, y entonces usar
@ -258,7 +276,12 @@
de uni&#x00f3;n de subclase, de hecho la semilla de clave primaria tiene que ser
compartida a trav&#x00e9;s de todas las subclases unidas de una jerarqu&#x00ed;a.
</para>
<para> UNTRANSLATED!
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">

View File

@ -68,6 +68,20 @@
<emphasis>Recuperaci&#x00f3;n perezosa de colecciones</emphasis> - se recupera una colecci&#x00f3;n cuando la
aplicaci&#x00f3;n invoca una operaci&#x00f3;n sobre la colecci&#x00f3;n. (Esto es por defecto para las colecciones.)
</para>
</listitem> <listitem>
<para>
<emphasis>"Extra-lazy" collection fetching</emphasis> - individual
elements of the collection are accessed from the database as needed.
Hibernate tries not to fetch the whole collection into memory unless
absolutely needed (suitable for very large collections)
</para>
</listitem>
<listitem>
<para>
<emphasis>Proxy fetching</emphasis> - a single-valued association is
fetched when a method other than the identifier getter is invoked
upon the associated object.
</para>
</listitem>
<listitem>
<para>
@ -186,9 +200,17 @@ Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></prog
<para>
las consultas de <literal>Criteria</literal>
</para>
</listitem> <listitem>
<para>
HQL queries if <literal>subselect</literal> fetching is used
</para>
</listitem>
</itemizedlist>
<para>
No matter what fetching strategy you use, the defined non-lazy graph is guaranteed
to be loaded into memory. Note that this might result in several immediate selects
being used to execute a particular HQL query.
</para>
<para>
Usualmente, no usamos el documento de mapeo para personalizar la recuperaci&#x00f3;n. En cambio, mantenemos el
comportamiento por defecto, y lo sobrescribimos para una transacci&#x00f3;n en particular, usando
@ -353,7 +375,12 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
Hibernate detectar&#x00e1; las clase persistentes que sobrescriban <literal>equals()</literal> o
<literal>hashCode()</literal>.
</para>
<para> UNTRANSLATED!!!
By choosing <literal>lazy="no-proxy"</literal> instead of the default
<literal>lazy="proxy"</literal>, we can avoid the problems associated with typecasting.
However, we will require buildtime bytecode instrumentation, and all operations
will result in immediate proxy initialization.
</para>
</sect2>
<sect2 id="performance-fetching-initialization">
@ -673,7 +700,8 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
<programlistingco>
<areaspec>
<area id="cache1" coords="2 70"/>
<area id="cache1" coords="2 70"/><area id="cache2" coords="3 70"/>
<area id="cache3" coords="4 70"/>
</areaspec>
<programlisting><![CDATA[<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
@ -687,7 +715,21 @@ Cat fritz = (Cat) iter.next();]]></programlisting>
<literal>nonstrict-read-write</literal> o
<literal>read-only</literal>
</para>
</callout>
</callout> <callout arearefs="cache2">
<para>
<literal>region</literal> (optional, defaults to the class or
collection role name) specifies the name of the second level cache
region
</para>
</callout>
<callout arearefs="cache3">
<para>
<literal>include</literal> (optional, defaults to <literal>all</literal>)
<literal>non-lazy</literal> specifies that properties of the entity mapped
with <literal>lazy="true"</literal> may not be cached when attribute-level
lazy fetching is enabled
</para>
</callout>
</calloutlist>
</programlistingco>

View File

@ -470,6 +470,71 @@ dynamicSession.close()
representaci&#x00f3;n XML en <xref linkend="xml"/>.
</para>
</sect1>
<sect1 id="persistent-classes-tuplizers" revision="1">
<title>UNTRANSLATED!!! 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>

View File

@ -182,6 +182,65 @@
<programlisting><![CDATA[from Document doc fetch all properties where lower(doc.name) like '%cats%']]></programlisting>
</sect1>
<sect1 id="queryhql-joins-forms">
<title>UNTRANSLATED!!! Forms of join syntax</title>
<para>
HQL supports two forms of association joining: <literal>implicit</literal> and <literal>explicit</literal>.
</para>
<para>
The queries shown in the previous section all use the <literal>explicit</literal> form where
the join keyword is explicitly used in the from clause. This is the recommended form.
</para>
<para>
The <literal>implicit</literal> form does not use the join keyword. Instead, the
associations are "dereferenced" using dot-notation. <literal>implicit</literal> joins
can appear in any of the HQL clauses. <literal>implicit</literal> join result
in inner joins in the resulting SQL statement.
</para>
<programlisting><![CDATA[from Cat as cat where cat.mate.name like '%s%']]></programlisting>
</sect1>
<sect1 id="queryhql-identifier-property">
<title>Refering to identifier property</title>
<para>
There are, generally speaking, 2 ways to refer to an entity's identifier property:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
The special property (lowercase) <literal>id</literal> may be used to reference the identifier
property of an entity <emphasis>provided that entity does not define a non-identifier property
named id</emphasis>.
</para>
</listitem>
<listitem>
<para>
If the entity defines a named identifier property, you may use that property name.
</para>
</listitem>
</itemizedlist>
<para>
References to composite identifier properties follow the same naming rules. If the
entity has a non-identifier property named id, the composite identifier property can only
be referenced by its defined named; otherwise, the special <literal>id</literal> property
can be used to rerference the identifier property.
</para>
<para>
Note: this has changed significantly starting in version 3.2.2. In previous versions,
<literal>id</literal> <emphasis>always</emphasis> referred to the identifier property no
matter what its actual name. A ramification of that decision was that non-identifier
properties named <literal>id</literal> could never be referenced in Hibernate queries.
</para>
</sect1>
<sect1 id="queryhql-select">
<title>La cl&#x00e1;usula select</title>
@ -843,56 +902,38 @@ order by count(kitten) asc, sum(kitten.weight) desc]]></programlisting>
SQL). Incluso se permiten subconsultas correlacionadas (subconsultas que hacen referencia a un alias en la
consulta exterior).
</para>
<programlisting><![CDATA[from Cat as fatcat
where fatcat.weight > (
select avg(cat.weight) from DomesticCat cat
)]]></programlisting>
<programlisting><![CDATA[from DomesticCat as cat
where cat.name = some (
select name.nickName from Name as name
)]]></programlisting>
<programlisting><![CDATA[from Cat as cat
where not exists (
from Cat as mate where mate.mate = cat
)]]></programlisting>
<programlisting><![CDATA[from DomesticCat as cat
where cat.name not in (
select name.nickName from Name as name
)]]></programlisting>
<para>
Para las subconsultas con m&#x00e1;s de una expresi&#x00f3;n en la lista de selecci&#x00f3;n, puedes usar un constructor
de tuplas:
</para>
<programlisting><![CDATA[from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)]]></programlisting>
<para>
Nota que en algunas bases de datos (pero no en Oracle o HSQL), puedes usar constructores de tuplar en
otros contextos, por ejemplo al consultar componentes o tipos de usuario compuestos:
</para>
<programlisting><![CDATA[from Person where name = ('Gavin', 'A', 'King')]]></programlisting>
<para>
Que es equivalente a la m&#x00e1;s verborr&#x00e1;gica:
</para>
<programlisting><![CDATA[from Person where name.first = 'Gavin' and name.initial = 'A' and name.last = 'King')]]></programlisting>
<para>
Existen dos buenas razones por las cuales podr&#x00ed;as no querer hacer este tipo de cosa: primero, no es
completamente portable entre plataformas de base de datos; segundo, la consulta ahora es dependiente
del orden de propiedades en el documento de mapeo.
</para>
<programlisting><![CDATA[from Cat as fatcat
where fatcat.weight > (
select avg(cat.weight) from DomesticCat cat
)]]></programlisting>
<programlisting><![CDATA[from DomesticCat as cat
where cat.name = some (
select name.nickName from Name as name
)]]></programlisting>
<programlisting><![CDATA[from Cat as cat
where not exists (
from Cat as mate where mate.mate = cat
)]]></programlisting>
<programlisting><![CDATA[from DomesticCat as cat
where cat.name not in (
select name.nickName from Name as name
)]]></programlisting>
<programlisting><![CDATA[select cat.id, (select max(kit.weight) from cat.kitten kit)
from Cat as cat]]></programlisting>
<para>
Note that HQL subqueries may occur only in the select or where clauses.
</para>
<para>
Note that subqueries can also utilize <literal>row value constructor</literal> syntax. See
<xref linkend="queryhql-tuple"/> for more details.
</para>
</sect1>
<sect1 id="queryhql-examples">

View File

@ -15,98 +15,388 @@
Hibernate3 te permite especificar SQL escrito a mano (incluyendo procedimientos almacenados) para todas
las operaciones de creaci&#x00f3;n, actualizaci&#x00f3;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&#x00e9;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&#x00e9;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&#x00ed;a devolver un valor escalar simple o una combinaci&#x00f3;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 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>
<para>
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.
</para>
</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-aliasreferences">
<title>Alias y referencias de propiedad</title>
<para>
La notaci&#x00f3;n <literal>{cat.*}</literal> usada arriba es un atajo para "todas las propiedades".
Alternativamente, puedes listar las columnas expl&#x00ed;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&#x00f3;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&#x00ed;amos incluso usar los alias de propiedad en la
cl&#x00e1;usula where si quisieramos.
</para>
<para>
La sint&#x00e1;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&#x00ed;citamente, &#x00a1;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>
@ -164,7 +454,38 @@ List loggedCats = sess.createSQLQuery(sql)
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>&lt;resultset&gt;</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},
address.STREET AS {address.street},
address.CITY AS {address.city},
address.STATE AS {address.state},
address.ZIP AS {address.zip}
FROM PERSON person
JOIN ADDRESS address
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>Usando return-property para especificar expl&#x00ed;citamente nombres de columna/alias</title>

View File

@ -101,7 +101,28 @@ Long generatedId = (Long) sess.save(fritz);]]></programlisting>
usar <literal>persist()</literal> en vez de <literal>save()</literal>, con la sem&#x00e1;ntica
definida en el temprano borrador de EJB3.
</para>
<itemizedlist spacing="compact">
<listitem>
<para> UNTRANSLATED!!!
<literal>persist()</literal> makes a transient instance persistent.
However, it doesn't guarantee that the identifier value will be assigned to
the persistent instance immediately, the assignment might happen at flush time.
<literal>persist()</literal> also guarantees that it will not execute an
<literal>INSERT</literal> statement if it is called outside of transaction
boundaries. This is useful in long-running conversations with an extended
Session/persistence context.
</para>
</listitem>
<listitem>
<para>
<literal>save()</literal> does guarantee to return an identifier. If an INSERT
has to be executed to get the identifier ( e.g. "identity" generator, not
"sequence"), this INSERT happens immediately, no matter if you are inside or
outside of a transaction. This is problematic in a long-running conversation
with an extended Session/persistence context.
</para>
</listitem>
</itemizedlist>
<para>
Alternativamente, puedes asignar el identificador usando una versi&#x00f3;n sobrecargada
de <literal>save()</literal>.
@ -312,8 +333,8 @@ while ( iter.hasNext() ) {
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}]]></programlisting>
@ -485,7 +506,13 @@ List cats = q.list();]]></programlisting>
usado; puedes adem&#x00e1;s definir consultas SQL nativas en metadatos, o migrar
consultas existentes a Hibernate coloc&#x00e1;ndolas en ficheros de mapeo.
</para>
<para> UNTRANSLATED!
Also note that a query declaration inside a <literal>&lt;hibernate-mapping&gt;</literal>
element requires a global unique name for the query, while a query declaration inside a
<literal>&lt;class&gt;</literal> element is made unique automatically by prepending the
fully qualified name of the class, for example
<literal>eg.Cat.ByNameAndMaximumWeight</literal>.
</para>
</sect3>
</sect2>
@ -567,16 +594,16 @@ List cats = crit.list();]]></programlisting>
Hibernate, debes encerrar los alias de SQL entre llaves:
</para>
<programlisting><![CDATA[List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list();]]></programlisting>
<programlisting><![CDATA[List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()]]></programlisting>
<programlisting><![CDATA[List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list();]]></programlisting>
<programlisting><![CDATA[List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()]]></programlisting>
<para>
Las consultas SQL pueden contener par&#x00e1;metros con nombre y posicionales, al igual que
@ -1187,7 +1214,14 @@ tx.commit(); // flush occurs]]></programlisting>
</para>
</listitem>
</itemizedlist>
<para> UNTRANSLATED!!!
Finally, note that cascading of operations can be applied to an object graph at
<emphasis>call time</emphasis> or at <emphasis>flush time</emphasis>. All operations,
if enabled, are cascaded to associated entities reachable when the operation is
executed. However, <literal>save-upate</literal> and <literal>delete-orphan</literal>
are transitive for all associated entities reachable during flush of the
<literal>Session</literal>.
</para>
</sect1>
<sect1 id="objectstate-metadata">

View File

@ -77,45 +77,68 @@
Con este atributo puedes establecer el tama&#x00f1;o de una columna. (O, para tipos de datos
num&#x00e9;ricos/decimales, la precisi&#x00f3;n.)
</para>
<programlisting><![CDATA[<property name="zip" length="5"/>]]></programlisting>
<programlisting><![CDATA[<property name="balance" precision="12" scale="2"/>]]></programlisting>
<para>
Algunas etiquetas tambi&#x00e9;n aceptan un atributo <literal>not-null</literal> (para generar una restricci&#x00f3;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>
<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>
<para>
Algunas etiquetas aceptan un atributo <literal>index</literal> para especificar el nombre de un &#x00ed;ndice
para esa columna. Se puede usar un atributo <literal>unique-key</literal> para agrupar columnas en una
restricci&#x00f3;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&#x00f3;n, s&#x00f3;lo para
agrupar las columnas en el fichero de mapeo.
</para>
<para>
Ejemplos:
</para>
<programlisting><![CDATA[<property name="lastName" index="CustName"/>
<property name="firstName" index="CustName"/>]]></programlisting>
<programlisting><![CDATA[<property name="foo" type="string" length="64" not-null="true"/>
<para>
A <literal>foreign-key</literal> attribute may be used to override the name
of any generated foreign key constraint.
</para>
<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>
<programlisting><![CDATA[<many-to-one name="bar" column="barId" foreign-key="FKFooBar"/>]]></programlisting>
<element column="serial_number" type="long" not-null="true" unique="true"/>]]></programlisting>
<para>
Many mapping elements also accept a child <literal>&lt;column&gt;</literal> element.
This is particularly useful for mapping multi-column types:
</para>
<para>
Alternativamente, estos elementos aceptan tamb&#x00ed;en un elemento hijo <literal>&lt;column&gt;</literal>.
Esto es particularmente &#x00fa;til para tipos multicolumnas:
</para>
<programlisting><![CDATA[<property name="foo" type="string">
<column name="foo" length="64" not-null="true" sql-type="text"/>
<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>
<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>
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>
El atributo <literal>sql-type</literal> permite al usuario sobrescribir el mapeo por defecto de
@ -155,7 +178,16 @@
<entry>number</entry>
<entry>largo de columna/precisi&#x00f3;n decimal</entry>
</row>
<row>
<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>
<entry><literal>not-null</literal></entry>
<entry><literal>true|false</literal></entry>
<entry>especifica que la columna debe ser no nulable</entry>
@ -180,8 +212,9 @@
<entry><literal>foreign_key_name</literal></entry>
<entry>
especifica el nombre de la restricci&#x00f3;n de clave for&#x00e1;nea generada por una
asociaci&#x00f3;n, &#x00fa;salo en los elementos de mapeo &lt;one-to-one&gt;, &lt;many-to-one&gt;,
&lt;key&gt;, y &lt;many-to-many&gt;. Nota que los lados
asociaci&#x00f3;n, &#x00fa;salo e <literal>&lt;one-to-one&gt;</literal>,
<literal>&lt;many-to-one&gt;</literal>, <literal>&lt;key&gt;</literal>,
or <literal>&lt;many-to-many&gt;</literal> . Nota que los lados
<literal>inverse="true"</literal> no ser&#x00e1;n considerados por
<literal>SchemaExport</literal>.
</entry>
@ -193,6 +226,12 @@
sobrescribe el tipo de columna por defecto (s&#x00f3;lo atributo del elemento
<literal>&lt;column&gt;</literal>)
</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>
@ -260,6 +299,9 @@
<row>
<entry><literal>--drop</literal></entry>
<entry>s&#x00f3;lo desechar las tablas</entry>
</row> <row>
<entry><literal>--create</literal></entry>
<entry>only create the tables</entry>
</row>
<row>
<entry><literal>--text</literal></entry>
@ -268,6 +310,9 @@
<row>
<entry><literal>--output=my_schema.ddl</literal></entry>
<entry>enviar la salida del gui&#x00f3;n ddl a un fichero</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>
@ -415,10 +460,20 @@ new SchemaExport(cfg).create(false, true);]]></programlisting>
<row>
<entry><literal>--quiet</literal></entry>
<entry>no enviar a salida est&#x00e1;ndar el gui&#x00f3;n</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>lee las propiedades de base de datos de un fichero</entry>
</row> <row>
<entry><literal>--config=hibernate.cfg.xml</literal></entry>
<entry>specify a <literal>.cfg.xml</literal> file</entry>
</row>
</tbody>
</tgroup>

View File

@ -14,7 +14,13 @@
B&#x00e1;sicamente, usa Hibernate como usar&#x00ed;as JDBC directo (o JTA/CMT) con tus recursos de base de
datos.
</para>
<para>
Hibernate does not lock objects in memory. Your application can expect the behavior as
defined by the isolation level of your database transactions. Note that thanks to the
<literal>Session</literal>, which is also a transaction-scoped cache, Hibernate
provides repeatable reads for lookup by identifier and entity queries (not
reporting queries that return scalar values).
</para>
<para>
Sin embargo, adem&#x00e1;s del versionado autom&#x00e1;tico, Hibernate ofrece una API (menor) para
bloqueo pesimista de filas, usando la sint&#x00e1;xis <literal>SELECT FOR UPDATE</literal>.
@ -90,22 +96,37 @@
aplicaciones.
</para>
<para>
El desaf&#x00ed;o yace en la implementaci&#x00f3;n: no s&#x00f3;lo tienen que comenzarse y terminarse correctamente
la <literal>Session</literal> y la transacci&#x00f3;n, sino que adem&#x00e1;s tienen que estar accesibles
para las operaciones de acceso a datos. La demarcaci&#x00f3;n de una unidad de trabajo se implementa
idealmente usando un interceptor que se ejecuta cuando una petici&#x00f3;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&#x00f3;n, usando una variable
<literal>ThreadLocal</literal>. Esto permite un f&#x00e1;cil acceso (como acceder a una variable static)
en t&#x00f3;do el c&#x00f3;digo que se ejecuta en esta hebra. Dependiendo del mecanismo de demarcaci&#x00f3;n de
transacciones de base de datos que elijas, podr&#x00ed;as mantener tambi&#x00e9;n el contexto de la transacci&#x00f3;n
en una variable <literal>ThreadLocal</literal>. Los patrones de implementaci&#x00f3;n para esto son
conocidos como <emphasis>Sesi&#x00f3;n Local de Hebra (ThreadLocal Session)</emphasis> y
<emphasis>Sesi&#x00f3;n Abierta en Vista (Open Session in View)</emphasis>. Puedes extender f&#x00e1;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>
The challenge lies in the implementation. Hibernate provides built-in management of
the "current session" to simplify this pattern. All you have to do is start a
transaction when a server request has to be processed, and end the transaction
before the response is send to the client. You can do this in any way you
like, common solutions are <literal>ServletFilter</literal>, AOP interceptor with a
pointcut on the service methods, or a proxy/interception container. An EJB container
is a standardized way to implement cross-cutting aspects such as transaction
demarcation on EJB session beans, declaratively with CMT. If you decide to
use programmatic transaction demarcation, prefer the Hibernate <literal>Transaction</literal>
API shown later in this chapter, for ease of use and code portability.
</para>
<para>
Your application code can access a "current session" to process the request
by simply calling <literal>sessionFactory.getCurrentSession()</literal> anywhere
and as often as needed. You will always get a <literal>Session</literal> scoped
to the current database transaction. This has to be configured for either
resource-local or JTA environments, see <xref linkend="architecture-current-session"/>.
</para>
<para>
Sometimes it is convenient to extend the scope of a <literal>Session</literal> and
database transaction until the "view has been rendered". This is especially useful
in servlet applications that utilize a separate rendering phase after the request
has been processed. Extending the database transaction until view rendering is
complete is easy to do if you implement your own interceptor. However, it is not
easily doable if you rely on EJBs with container-managed transactions, as a
transaction will be completed when an EJB method returns, before rendering of any
view can start. See the Hibernate website and forum for tips and examples around
this <emphasis>Open Session in View</emphasis> pattern.
</para>
</sect2>
@ -421,9 +442,7 @@ finally {
<para>
No tienes que limpiar con <literal>flush()</literal> la <literal>Session</literal> expl&#x00ed;citamente -
la llamada a <literal>commit()</literal> autom&#x00e1;ticamente dispara la sincronizaci&#x00f3;n.
</para>
<para>
Una llamada a <literal>close()</literal> marca el fin de una sesi&#x00f3;n. La principal implicaci&#x00f3;n
de <literal>close()</literal> es que la conexi&#x00f3;n JDBC ser&#x00e1; abandonada por la sesi&#x00f3;n.
</para>
@ -624,6 +643,50 @@ Session sess = factory.getCurrentSession();
</sect2>
<sect2 id="transactions-demarcation-timeout">
<title>Transaction timeout</title>
<para>
One extremely important feature provided by a managed environment like EJB
that is never provided for non-managed code is transaction timeout. Transaction
timeouts ensure that no misbehaving transaction can indefinitely tie up
resources while returning no response to the user. Outside a managed (JTA)
environment, Hibernate cannot fully provide this functionality. However,
Hibernate can at least control data access operations, ensuring that database
level deadlocks and queries with huge result sets are limited by a defined
timeout. In a managed environment, Hibernate can delegate transaction timeout
to JTA. This functioanlity is abstracted by the Hibernate
<literal>Transaction</literal> object.
</para>
<programlisting><![CDATA[
Session sess = factory.openSession();
try {
//set transaction timeout to 3 seconds
sess.getTransaction().setTimeout(3);
sess.getTransaction().begin();
// do some work
...
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e; // or display error message
}
finally {
sess.close();
}]]></programlisting>
<para>
Note that <literal>setTimeout()</literal> may not be called in a CMT bean,
where transaction timeouts must be defined declaratively.
</para>
</sect2>
</sect1>
@ -700,7 +763,7 @@ session.close();]]></programlisting>
La <literal>Session</literal> se desconecta de cualquier conexi&#x00f3;n JDBC subyacente
al esperar por una interacci&#x00f3;n del usuario. Este enfoque es el m&#x00e1;s eficiente en t&#x00e9;rminos
de acceso a base de datos. La aplicaci&#x00f3;n no necesita tratar por s&#x00ed; misma con el chequeo de
versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cada
versiones, ni re-uniendo instancias separadas, ni tiene que recargar instancias en cadatransactions-demarcation-timeout
transacci&#x00f3;n de base de datos.
</para>
@ -742,7 +805,12 @@ session.disconnect(); // Return JDBC connection ]]></programlisting>
<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>
<para> UNTRANSLATED!!!
The extended session pattern, or <emphasis>session-per-conversation</emphasis>, is
more difficult to implement with automatic current session context management.
You need to supply your own implementation of the <literal>CurrentSessionContext</literal>
for this, see the Hibernate Wiki for examples.
</para>
</sect2>
<sect2 id="transactions-optimistic-detached">

View File

@ -19,6 +19,10 @@
Michael Gloegl. Las bibliotecas de terceros que mencionamos son para JDK 1.4
y 5.0. Podr&#x00ed;as necesitar otras para JDK 1.3.
</para>
<para> UNTRANSLATED!!!
The source code for the tutorial is included in the distribution in the
<literal>doc/reference/tutorial/</literal> directory.
</para>
</sect1>
@ -785,18 +789,40 @@ else if (args[0].equals("list")) {
a la base de datosy poblar&#x00e1; los objetos <literal>Event</literal> con datos.
Puedes, por supuesto, crear consultas m&#x00e1;s complejas con HQL.
</para>
<para>
Si ahora llamas a Ant con <literal>-Daction=list</literal>, debes ver los eventos
que has almacenado hasta ahora. Puede sorprenderte que esto no funcione, al menos
si has seguido este tutorial paso por paso; el resultado siempre estar&#x00e1;
vac&#x00ed;o. La razon de esto es la opci&#x00f3;n <literal>hbm2ddl.auto</literal>
en la configuraci&#x00f3;n de Hibernate: Hibernate recrear&#x00e1; la base de datos
en cada ejecuci&#x00f3;n. Deshabil&#x00ed;tala quitando la opci&#x00f3;n, y ver&#x00e1;s
resultados en tu listado despu&#x00e9;s que llames a la acci&#x00f3;n <literal>store</literal>
unas cuantas veces. La generaci&#x00f3;n y exportaci&#x00f3;n de esquema es &#x00fa;til
mayormente en testeo unitario.
<para>
Now, to execute and test all of this, follow these steps:
</para>
<itemizedlist>
<listitem>
<para>
Run <literal>ant run -Daction=store</literal> to store something into the database
and, of course, to generate the database schema before through hbm2ddl.
</para>
</listitem>
<listitem>
<para>
Now disable hbm2ddl by commenting out the property in your <literal>hibernate.cfg.xml</literal>
file. Usually you only leave it turned on in continous unit testing, but another
run of hbm2ddl would <emphasis>drop</emphasis> everything you have stored - the
<literal>create</literal> configuration setting actually translates into "drop all
tables from the schema, then re-create all tables, when the SessionFactory is build".
</para>
</listitem>
</itemizedlist>
<para>
If you now call Ant with <literal>-Daction=list</literal>, you should see the events
you have stored so far. You can of course also call the <literal>store</literal> action a few
times more.
</para>
<para>
Note: Most new Hibernate users fail at this point and we see questions about
<emphasis>Table not found</emphasis> error messages regularly. However, if you follow the
steps outlined above you will not have this problem, as hbm2ddl creates the database
schema on the first run, and subsequent application restarts will use this schema. If
you change the mapping and/or database schema, you have to re-enable hbm2ddl once again.
</para>
</sect2>
@ -1119,11 +1145,25 @@ public void setEmailAddresses(Set emailAddresses) {
direcciones de email duplicadas por persona, que es exactamente la sem&#x00e1;ntica
que necesitamos para un conjunto en Java.
</para>
<programlisting><![CDATA[private void addEmailToPerson(Long personId, String emailAddress) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
// The getEmailAddresses() might trigger a lazy load of the collection
aPerson.getEmailAddresses().add(emailAddress);
session.getTransaction().commit();
}]]></programlisting>
<para>
Puedes ahora intentar y agregar elementos a esta colecci&#x00f3;n, al igual que
hicimos antes enlazando personas y eventos. Es el mismo c&#x00f3;digo en Java.
</para>
hicimos antes enlazando personas y eventos. Es el mismo c&#x00f3;digo en Java.</para><para>
This time we didnt' use a <emphasis>fetch</emphasis> query to initialize the collection.
Hence, the call to its getter method will trigger an additional select to initialize
it, so we can add an element to it. Monitor the SQL log and try to optimize this with
an eager fetch.</para>
</sect2>
@ -1246,7 +1286,279 @@ public void removeFromEvent(Event event) {
</para>
-->
</sect2>
</sect1>
<para>
Let's turn this into a small web application.
</para>
</sect1>
<sect1 id="tutorial-webapp">
<title>Part 3 - The EventManager web application</title>
<para>
A Hibernate web application uses <literal>Session</literal> and <literal>Transaction</literal>
almost like a standalone application. However, some common patterns are useful. We now write
an <literal>EventManagerServlet</literal>. This servlet can list all events stored in the
database, and it provides an HTML form to enter new events.
</para>
<sect2 id="tutorial-webapp-servlet" revision="2">
<title>Writing the basic servlet</title>
<para>
Create a new class in your source directory, in the <literal>events</literal>
package:
</para>
<programlisting><![CDATA[package events;
// Imports
public class EventManagerServlet extends HttpServlet {
// Servlet code
}]]></programlisting>
<para>
The servlet handles HTTP <literal>GET</literal> requests only, hence, the method
we implement is <literal>doGet()</literal>:
</para>
<programlisting><![CDATA[protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy");
try {
// Begin unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().beginTransaction();
// Process request and render page...
// End unit of work
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().commit();
} catch (Exception ex) {
HibernateUtil.getSessionFactory()
.getCurrentSession().getTransaction().rollback();
throw new ServletException(ex);
}
}]]></programlisting>
<para>
The pattern we are applying here is called <emphasis>session-per-request</emphasis>.
When a request hits the servlet, a new Hibernate <literal>Session</literal> is
opened through the first call to <literal>getCurrentSession()</literal> on the
<literal>SessionFactory</literal>. Then a database transaction is started&mdash;all
data access as to occur inside a transaction, no matter if data is read or written
(we don't use the auto-commit mode in applications).
</para>
<para>
Do <emphasis>not</emphasis> use a new Hibernate <literal>Session</literal> for
every database operation. Use one Hibernate <literal>Session</literal> that is
scoped to the whole request. Use <literal>getCurrentSession()</literal>, so that
it is automatically bound to the current Java thread.
</para>
<para>
Next, the possible actions of the request are processed and the response HTML
is rendered. We'll get to that part soon.
</para>
<para>
Finally, the unit of work ends when processing and rendering is complete. If any
problem occured during processing or rendering, an exception will be thrown
and the database transaction rolled back. This completes the
<literal>session-per-request</literal> pattern. Instead of the transaction
demarcation code in every servlet you could also write a servlet filter.
See the Hibernate website and Wiki for more information about this pattern,
called <emphasis>Open Session in View</emphasis>&mdash;you'll need it as soon
as you consider rendering your view in JSP, not in a servlet.
</para>
</sect2>
<sect2 id="tutorial-webapp-processing" revision="1">
<title>Processing and rendering</title>
<para>
Let's implement the processing of the request and rendering of the page.
</para>
<programlisting><![CDATA[// Write HTML header
PrintWriter out = response.getWriter();
out.println("<html><head><title>Event Manager</title></head><body>");
// Handle actions
if ( "store".equals(request.getParameter("action")) ) {
String eventTitle = request.getParameter("eventTitle");
String eventDate = request.getParameter("eventDate");
if ( "".equals(eventTitle) || "".equals(eventDate) ) {
out.println("<b><i>Please enter event title and date.</i></b>");
} else {
createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
out.println("<b><i>Added event.</i></b>");
}
}
// Print page
printEventForm(out);
listEvents(out, dateFormatter);
// Write HTML footer
out.println("</body></html>");
out.flush();
out.close();]]></programlisting>
<para>
Granted, this coding style with a mix of Java and HTML would not scale
in a more complex application&mdash;keep in mind that we are only illustrating
basic Hibernate concepts in this tutorial. The code prints an HTML
header and a footer. Inside this page, an HTML form for event entry and
a list of all events in the database are printed. The first method is
trivial and only outputs HTML:
</para>
<programlisting><![CDATA[private void printEventForm(PrintWriter out) {
out.println("<h2>Add new event:</h2>");
out.println("<form>");
out.println("Title: <input name='eventTitle' length='50'/><br/>");
out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
out.println("<input type='submit' name='action' value='store'/>");
out.println("</form>");
}]]></programlisting>
<para>
The <literal>listEvents()</literal> method uses the Hibernate
<literal>Session</literal> bound to the current thread to execute
a query:
</para>
<programlisting><![CDATA[private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {
List result = HibernateUtil.getSessionFactory()
.getCurrentSession().createCriteria(Event.class).list();
if (result.size() > 0) {
out.println("<h2>Events in database:</h2>");
out.println("<table border='1'>");
out.println("<tr>");
out.println("<th>Event title</th>");
out.println("<th>Event date</th>");
out.println("</tr>");
for (Iterator it = result.iterator(); it.hasNext();) {
Event event = (Event) it.next();
out.println("<tr>");
out.println("<td>" + event.getTitle() + "</td>");
out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
out.println("</tr>");
}
out.println("</table>");
}
}]]></programlisting>
<para>
Finally, the <literal>store</literal> action is dispatched to the
<literal>createAndStoreEvent()</literal> method, which also uses
the <literal>Session</literal> of the current thread:
</para>
<programlisting><![CDATA[protected void createAndStoreEvent(String title, Date theDate) {
Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);
HibernateUtil.getSessionFactory()
.getCurrentSession().save(theEvent);
}]]></programlisting>
<para>
That's it, the servlet is complete. A request to the servlet will be processed
in a single <literal>Session</literal> and <literal>Transaction</literal>. As
earlier in the standalone application, Hibernate can automatically bind these
ojects to the current thread of execution. This gives you the freedom to layer
your code and access the <literal>SessionFactory</literal> in any way you like.
Usually you'd use a more sophisticated design and move the data access code
into data access objects (the DAO pattern). See the Hibernate Wiki for more
examples.
</para>
</sect2>
<sect2 id="tutorial-webapp-deploy">
<title>Deploying and testing</title>
<para>
To deploy this application you have to create a web archive, a WAR. Add the
following Ant target to your <literal>build.xml</literal>:
</para>
<programlisting><![CDATA[<target name="war" depends="compile">
<war destfile="hibernate-tutorial.war" webxml="web.xml">
<lib dir="${librarydir}">
<exclude name="jsdk*.jar"/>
</lib>
<classes dir="${targetdir}"/>
</war>
</target>]]></programlisting>
<para>
This target creates a file called <literal>hibernate-tutorial.war</literal>
in your project directory. It packages all libraries and the <literal>web.xml</literal>
descriptor, which is expected in the base directory of your project:
</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>Event Manager</servlet-name>
<servlet-class>events.EventManagerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Event Manager</servlet-name>
<url-pattern>/eventmanager</url-pattern>
</servlet-mapping>
</web-app>]]></programlisting>
<para>
Before you compile and deploy the web application, note that an additional library
is required: <literal>jsdk.jar</literal>. This is the Java servlet development kit,
if you don't have this library already, get it from the Sun website and copy it to
your library directory. However, it will be only used for compliation and excluded
from the WAR package.
</para>
<para>
To build and deploy call <literal>ant war</literal> in your project directory
and copy the <literal>hibernate-tutorial.war</literal> file into your Tomcat
<literal>webapp</literal> directory. If you don't have Tomcat installed, download
it and follow the installation instructions. You don't have to change any Tomcat
configuration to deploy this application though.
</para>
<para>
Once deployed and Tomcat is running, access the application at
<literal>http://localhost:8080/hibernate-tutorial/eventmanager</literal>. Make
sure you watch the Tomcat log to see Hibernate initialize when the first
request hits your servlet (the static initializer in <literal>HibernateUtil</literal>
is called) and to get the detailed output if any exceptions occurs.
</para>
</sect2>
</sect1>
<sect1 id="tutorial-summary">
<title>Summary</title>