diff --git a/Doc/course.xml b/Doc/course.xml index f0544501483da2a049d286c04006bdf90809c5a6..7f464f7057787af90466e018ff68358b34978e30 100644 --- a/Doc/course.xml +++ b/Doc/course.xml @@ -16992,220 +16992,225 @@ public class User { </section> </section> - <section xml:id="inheritance"> - <title>Inheritance</title> + <section xml:id="sect_hibernateValidation"> + <title>Hibernate validation</title> + + <para/> + </section> + </chapter> + + <chapter xml:id="inheritance"> + <title>Inheritance</title> + + <para>Mapping inheritance hierarchies to relational databases means + bridging the gap between object <link + xlink:href="http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch">oriented + and relational models</link>. We start with a slightly modified example + from <xref linkend="Bauer05"/>:</para> + + <figure xml:id="fig_BillingDetails"> + <title>Modelling payment.</title> + + <mediaobject> + <imageobject> + <imagedata fileref="Ref/Fig/billing.fig"/> + </imageobject> + + <caption> + <para>Simplified Billing details example derived from <xref + linkend="Bauer05"/>. Notice + <classname>inherit.v1.BillingDetails</classname> being an abstract + parent class of two concrete classes + <classname>inherit.v1.CreditCard</classname> and + <classname>inherit.v1.BankAccount</classname>. The attribute + <code>number</code> applies both to bank account and credit card + payments.</para> + </caption> + </mediaobject> + </figure> + + <para>Since the relational model lacks inheritance completely we have to + implement a database schema ourselves. We subsequently explore three + main approaches each of which having its own advantages and + disadvantages.</para> - <para>Mapping inheritance hierarchies to relational databases means - bridging the gap between object <link - xlink:href="http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch">oriented - and relational models</link>. We start with a slightly modified - example from <xref linkend="Bauer05"/>:</para> + <section xml:id="sect_InheritTablePerClassHierarchie"> + <title>Single table per class hierarchy</title> - <figure xml:id="fig_BillingDetails"> - <title>Modelling payment.</title> + <para>This approach may be considered the most simple: We just create + one database table for storing instances of arbitrary classes + belonging to the inheritance hierarchy in question:</para> + + <figure xml:id="fig_TablePerClassHierarchyData"> + <title>A single relation mapping.</title> <mediaobject> <imageobject> - <imagedata fileref="Ref/Fig/billing.fig"/> + <imagedata fileref="Ref/Fig/billingData.fig"/> </imageobject> <caption> - <para>Simplified Billing details example derived from <xref - linkend="Bauer05"/>. Notice - <classname>inherit.v1.BillingDetails</classname> being an - abstract parent class of two concrete classes - <classname>inherit.v1.CreditCard</classname> and - <classname>inherit.v1.BankAccount</classname>. The attribute - <code>number</code> applies both to bank account and credit card - payments.</para> + <para>Fitting both <classname>inherit.v1.CreditCard</classname> + and <classname>inherit.v1.BankAccount</classname> instances into + a single relation.</para> </caption> </mediaobject> </figure> - <para>Since the relational model lacks inheritance completely we have - to implement a database schema ourselves. We subsequently explore - three main approaches each of which having its own advantages and - disadvantages.</para> - - <section xml:id="sect_InheritTablePerClassHierarchie"> - <title>Single table per class hierarchy</title> - - <para>This approach may be considered the most simple: We just - create one database table for storing instances of arbitrary classes - belonging to the inheritance hierarchy in question:</para> - - <figure xml:id="fig_TablePerClassHierarchyData"> - <title>A single relation mapping.</title> + <para>The relation may be created by the following <abbrev + xlink:href="http://en.wikipedia.org/wiki/Data_definition_language">DDL</abbrev>:</para> - <mediaobject> - <imageobject> - <imagedata fileref="Ref/Fig/billingData.fig"/> - </imageobject> + <figure xml:id="fig_TablePerClassHierarchyMapping"> + <title>Mapping the inheritance hierarchy.</title> - <caption> - <para>Fitting both - <classname>inherit.v1.CreditCard</classname> and - <classname>inherit.v1.BankAccount</classname> instances into a - single relation.</para> - </caption> - </mediaobject> - </figure> - - <para>The relation may be created by the following <abbrev - xlink:href="http://en.wikipedia.org/wiki/Data_definition_language">DDL</abbrev>:</para> - - <figure xml:id="fig_TablePerClassHierarchyMapping"> - <title>Mapping the inheritance hierarchy.</title> - - <mediaobject> - <imageobject> - <imagedata fileref="Ref/Fig/billingSql.fig"/> - </imageobject> - </mediaobject> - </figure> + <mediaobject> + <imageobject> + <imagedata fileref="Ref/Fig/billingSql.fig"/> + </imageobject> + </mediaobject> + </figure> - <para>We take a closer look at the generated relation. Since</para> + <para>We take a closer look at the generated relation. Since</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.v1; + <td valign="top"><programlisting>package inherit.v1; ... @Entity @Inheritance(strategy=InheritanceType.<emphasis role="bold">SINGLE_TABLE</emphasis>) <co - linkends="billingMapSingleTableCallout" - xml:id="billingMapSingleTable"/> + linkends="billingMapSingleTableCallout" + xml:id="billingMapSingleTable"/> @DiscriminatorColumn(name="dataType", discriminatorType=DiscriminatorType.STRING) <co - linkends="billingMapSingleTableDiscriminatorCallout" - xml:id="billingMapSingleTableDiscriminator"/> + linkends="billingMapSingleTableDiscriminatorCallout" + xml:id="billingMapSingleTableDiscriminator"/> abstract class BillingDetails { @Id @GeneratedValue <co linkends="billingMapSingleTableIdGeneratedCallout" - xml:id="billingMapSingleTableIdGenerated"/> public Long getId() ... + xml:id="billingMapSingleTableIdGenerated"/> public Long getId() ... @Column(nullable = false, length = 32)public final String getNumber() ... @Temporal(TemporalType.TIMESTAMP) @Column(nullable = false) public Date getCreated() ...</programlisting><programlisting>package inherit.v1; ... @Entity @DiscriminatorValue(value = "Credit card" <co - xml:id="billingMapSingleTableDiscriminatorCredit"/>) + xml:id="billingMapSingleTableDiscriminatorCredit"/>) public class CreditCard extends BillingDetails { ... //Nothing JPA related happens here</programlisting><programlisting>package inherit.v1; ... @Entity @DiscriminatorValue(value = "Bank account" <co - xml:id="billingMapSingleTableDiscriminatorBank"/>) + xml:id="billingMapSingleTableDiscriminatorBank"/>) public class BankAccount extends BillingDetails { ... //Nothing JPA related happens here</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">CREATE TABLE BillingDetails <co - linkends="billingMapSingleTableCallout" - xml:id="BillingDetailsGeneratedRelationName"/> ( + <td><programlisting continuation="continues">CREATE TABLE BillingDetails <co + linkends="billingMapSingleTableCallout" + xml:id="BillingDetailsGeneratedRelationName"/> ( dataType varchar(31) NOT NULL, id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, number varchar(255) NOT NULL, <co - linkends="billingMapSingleTableBaseNotNull" - xml:id="billingMapSingleTableCalloutNumberNotNull"/> + linkends="billingMapSingleTableBaseNotNull" + xml:id="billingMapSingleTableCalloutNumberNotNull"/> created datetime NOT NULL, <co linkends="billingMapSingleTableBaseNotNull" - xml:id="billingMapSingleTableCalloutCreatedNotNull"/> + xml:id="billingMapSingleTableCalloutCreatedNotNull"/> cardType int(11) DEFAULT NULL, <co - linkends="billingMapSingleTableDerivedNull" - xml:id="billingMapSingleTableCardTypeNull"/> + linkends="billingMapSingleTableDerivedNull" + xml:id="billingMapSingleTableCardTypeNull"/> expiration datetime DEFAULT NULL, <co - linkends="billingMapSingleTableDerivedNull" - xml:id="billingMapSingleTableExpirationNull"/> + linkends="billingMapSingleTableDerivedNull" + xml:id="billingMapSingleTableExpirationNull"/> bankName varchar(255) DEFAULT NULL, <co - linkends="billingMapSingleTableDerivedNull" - xml:id="billingMapSingleTableBankNameNull"/> + linkends="billingMapSingleTableDerivedNull" + xml:id="billingMapSingleTableBankNameNull"/> swiftcode varchar(255) DEFAULT NULL <co - linkends="billingMapSingleTableDerivedNull" - xml:id="billingMapSingleTableSwiftCodeNull"/> + linkends="billingMapSingleTableDerivedNull" + xml:id="billingMapSingleTableSwiftCodeNull"/> )</programlisting></td> - </tr> - </informaltable> + </tr> + </informaltable> - <calloutlist> - <callout arearefs="billingMapSingleTable" - xml:id="billingMapSingleTableCallout"> - <para>All classes of the inheritance hierarchy will be mapped to - a single table. Unless stated otherwise the <link - linkend="gloss_JPA"><abbrev>JPA</abbrev></link> provider will - choose the root class' name (<code>BillingDetails</code>) as - default value for the generated relation's name <coref - linkend="BillingDetailsGeneratedRelationName"/>.</para> - </callout> + <calloutlist> + <callout arearefs="billingMapSingleTable" + xml:id="billingMapSingleTableCallout"> + <para>All classes of the inheritance hierarchy will be mapped to a + single table. Unless stated otherwise the <link + linkend="gloss_JPA"><abbrev>JPA</abbrev></link> provider will + choose the root class' name (<code>BillingDetails</code>) as + default value for the generated relation's name <coref + linkend="BillingDetailsGeneratedRelationName"/>.</para> + </callout> - <callout arearefs="billingMapSingleTableDiscriminator" - xml:id="billingMapSingleTableDiscriminatorCallout"> - <para>The <link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> - provider needs a column to distinguish the different types of - database objects. We've chosen the discriminator attribute - <code>dataType</code> values to be simple strings. Due to the - definitions in <coref - linkend="billingMapSingleTableDiscriminatorCredit"/> and <coref - linkend="billingMapSingleTableDiscriminatorBank"/> database - object types are being identified by either of the two - values:</para> + <callout arearefs="billingMapSingleTableDiscriminator" + xml:id="billingMapSingleTableDiscriminatorCallout"> + <para>The <link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> + provider needs a column to distinguish the different types of + database objects. We've chosen the discriminator attribute + <code>dataType</code> values to be simple strings. Due to the + definitions in <coref + linkend="billingMapSingleTableDiscriminatorCredit"/> and <coref + linkend="billingMapSingleTableDiscriminatorBank"/> database object + types are being identified by either of the two values:</para> - <itemizedlist> - <listitem> - <para><code>Credit card</code>: object will be mapped to - <classname>inherit.v1.CreditCard</classname>.</para> - </listitem> + <itemizedlist> + <listitem> + <para><code>Credit card</code>: object will be mapped to + <classname>inherit.v1.CreditCard</classname>.</para> + </listitem> - <listitem> - <para><code>Bank account</code>: object will be mapped to - <classname>inherit.v1.BankAccount</classname>.</para> - </listitem> - </itemizedlist> + <listitem> + <para><code>Bank account</code>: object will be mapped to + <classname>inherit.v1.BankAccount</classname>.</para> + </listitem> + </itemizedlist> - <para>In a productive system the - <classname>javax.persistence.DiscriminatorType</classname> - setting will typically favour - <classname>javax.persistence.DiscriminatorType</classname><code>.INTEGER</code> - over - <classname>javax.persistence.DiscriminatorType</classname><code>.STRING</code> - unless the application in question has to deal with a legacy - database schema.</para> - </callout> + <para>In a productive system the + <classname>javax.persistence.DiscriminatorType</classname> setting + will typically favour + <classname>javax.persistence.DiscriminatorType</classname><code>.INTEGER</code> + over + <classname>javax.persistence.DiscriminatorType</classname><code>.STRING</code> + unless the application in question has to deal with a legacy + database schema.</para> + </callout> - <callout arearefs="billingMapSingleTableIdGenerated" - xml:id="billingMapSingleTableIdGeneratedCallout"> - <para>This one is unrelated to inheritance: Our primary key - values will be auto generated by the database server e.g. by - <code>SEQUENCE</code> or <code>IDENTITY</code> mechanisms if - available.</para> - </callout> + <callout arearefs="billingMapSingleTableIdGenerated" + xml:id="billingMapSingleTableIdGeneratedCallout"> + <para>This one is unrelated to inheritance: Our primary key values + will be auto generated by the database server e.g. by + <code>SEQUENCE</code> or <code>IDENTITY</code> mechanisms if + available.</para> + </callout> - <callout arearefs="billingMapSingleTableCalloutNumberNotNull billingMapSingleTableCalloutCreatedNotNull" - xml:id="billingMapSingleTableBaseNotNull"> - <para>Only the base class' attributes may exclude - <code>NULL</code> values.</para> - </callout> + <callout arearefs="billingMapSingleTableCalloutNumberNotNull billingMapSingleTableCalloutCreatedNotNull" + xml:id="billingMapSingleTableBaseNotNull"> + <para>Only the base class' attributes may exclude + <code>NULL</code> values.</para> + </callout> - <callout arearefs="billingMapSingleTableCardTypeNull billingMapSingleTableExpirationNull billingMapSingleTableBankNameNull billingMapSingleTableSwiftCodeNull" - xml:id="billingMapSingleTableDerivedNull"> - <para>All derived classes' attributes must allow - <code>NULL</code> values.</para> - </callout> - </calloutlist> + <callout arearefs="billingMapSingleTableCardTypeNull billingMapSingleTableExpirationNull billingMapSingleTableBankNameNull billingMapSingleTableSwiftCodeNull" + xml:id="billingMapSingleTableDerivedNull"> + <para>All derived classes' attributes must allow <code>NULL</code> + values.</para> + </callout> + </calloutlist> - <para>We may now insert instances of - <classname>inherit.v1.BankAccount</classname> or - <classname>inherit.v1.CreditCard</classname>:</para> + <para>We may now insert instances of + <classname>inherit.v1.BankAccount</classname> or + <classname>inherit.v1.CreditCard</classname>:</para> - <figure xml:id="insertCreditBank"> - <title>Inserting payment information</title> + <figure xml:id="insertCreditBank"> + <title>Inserting payment information</title> - <programlisting>package inherit.v1; + <programlisting>package inherit.v1; ... public class Persist { public static void main(String[] args) throws ParseException { @@ -17218,25 +17223,25 @@ public class Persist { session.save(bankAccount); } transaction.commit(); ...</programlisting> - </figure> + </figure> - <section xml:id="sect_InheritTablePerClassHierarchieLoad"> - <title>Database object retrieval</title> + <section xml:id="sect_InheritTablePerClassHierarchieLoad"> + <title>Database object retrieval</title> - <para>As in <xref linkend="retrieveAllUserByHql"/> objects being - stored by <xref linkend="insertCreditBank"/> may be queried using - <abbrev - xlink:href="http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch16.html">HQL</abbrev>.</para> + <para>As in <xref linkend="retrieveAllUserByHql"/> objects being + stored by <xref linkend="insertCreditBank"/> may be queried using + <abbrev + xlink:href="http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch16.html">HQL</abbrev>.</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.v1; + <td valign="top"><programlisting>package inherit.v1; ... public class RetrieveCredit { public static void main(String[] args) { @@ -17244,18 +17249,18 @@ public class RetrieveCredit { final Transaction transaction = session.beginTransaction(); final Query searchCreditPayments = session.createQuery("<emphasis - role="bold">from inherit.v1.CreditCard</emphasis>"); <co - xml:id="hqlQueryCreditCard"/> + role="bold">from inherit.v1.CreditCard</emphasis>"); <co + xml:id="hqlQueryCreditCard"/> final List<CreditCard> creditCardList = (List<CreditCard>) searchCreditPayments.list(); for (final CreditCard c: creditCardList) { System.out.println(c); } ...</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">INFO: HHH000232: Schema update complete + <td><programlisting continuation="continues">INFO: HHH000232: Schema update complete Hibernate: select creditcard0_.id as id0_, @@ -17267,57 +17272,56 @@ Hibernate: BillingDetails creditcard0_ where creditcard0_.<emphasis role="bold">dataType</emphasis> <co - xml:id="hqlQueryCreditCard_dataType"/>='<emphasis - role="bold">Credit card</emphasis>' + xml:id="hqlQueryCreditCard_dataType"/>='<emphasis + role="bold">Credit card</emphasis>' <emphasis role="bold">CreditCard: number=4412 8334 4512 9416, created 2013-02-19 13:09:22.0, cardType=1, expiration=2015-05-18 00:00:00.</emphasis> <co - xml:id="hqlQueryCreditCardResultSet"/></programlisting></td> - </tr> - </informaltable> - - <para>Some Remarks: Our query asks for instances of - <classname>inherit.v2.CreditCard</classname> <coref - linkend="hqlQueryCreditCard"/>. This gets implemented as an - <acronym - xlink:href="http://en.wikipedia.org/wiki/Sql">SQL</acronym> - <code>SELECT</code> choosing datasets whose discriminator - attribute <code>value of dataType</code> <coref - linkend="hqlQueryCreditCard_dataType"/> equals <quote><code>Credit - card</code></quote>. The current result set contains just one - element <coref linkend="hqlQueryCreditCardResultSet"/> in - accordance with <xref linkend="insertCreditBank"/>.</para> + xml:id="hqlQueryCreditCardResultSet"/></programlisting></td> + </tr> + </informaltable> - <para>Retrieving both <classname>inherit.v1.CreditCard</classname> - and <classname>inherit.v1.BankAccount</classname> instances is - accomplished by querying for the common base class - <classname>inherit.v1.BillingDetails</classname>:</para> + <para>Some Remarks: Our query asks for instances of + <classname>inherit.v2.CreditCard</classname> <coref + linkend="hqlQueryCreditCard"/>. This gets implemented as an <acronym + xlink:href="http://en.wikipedia.org/wiki/Sql">SQL</acronym> + <code>SELECT</code> choosing datasets whose discriminator attribute + <code>value of dataType</code> <coref + linkend="hqlQueryCreditCard_dataType"/> equals <quote><code>Credit + card</code></quote>. The current result set contains just one + element <coref linkend="hqlQueryCreditCardResultSet"/> in accordance + with <xref linkend="insertCreditBank"/>.</para> + + <para>Retrieving both <classname>inherit.v1.CreditCard</classname> + and <classname>inherit.v1.BankAccount</classname> instances is + accomplished by querying for the common base class + <classname>inherit.v1.BillingDetails</classname>:</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.v1; + <td valign="top"><programlisting>package inherit.v1; ... public class RetrieveAll { ... final Query searchBilling = session.createQuery("from <emphasis - role="bold">inherit.v1.BillingDetails</emphasis>"); + role="bold">inherit.v1.BillingDetails</emphasis>"); @SuppressWarnings("unchecked") final List<BillingDetails> billingDetailsList = (List<BillingDetails>) searchBilling.list(); for (final BillingDetails c: billingDetailsList) { System.out.println(c); } ...</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">INFO: HHH000232: Schema update complete + <td><programlisting continuation="continues">INFO: HHH000232: Schema update complete Hibernate: select billingdet0_.id as id0_, @@ -17327,71 +17331,71 @@ Hibernate: BillingDetails billingdet0_ CreditCard: number=4412 8334 4512 9416, created 2013-02-19 13:09:22.0, <co - xml:id="resultSetHeterogeneous"/> + xml:id="resultSetHeterogeneous"/> cardType=1, expiration=2015-05-18 00:00:00.0 BankAccount: number=1107 2 31, created 2013-02-19 13:09:22.0, bankName=Lehman Brothers, swiftcode=BARCGB22</programlisting></td> - </tr> - </informaltable> + </tr> + </informaltable> - <para>This is the first example of a polymorphic query yielding a - heterogeneous result set<coref - linkend="resultSetHeterogeneous"/>.</para> - </section> + <para>This is the first example of a polymorphic query yielding a + heterogeneous result set<coref + linkend="resultSetHeterogeneous"/>.</para> + </section> - <section xml:id="sect_InheritTablePerClassHierarchieNullProblem"> - <title>Null values</title> + <section xml:id="sect_InheritTablePerClassHierarchieNullProblem"> + <title>Null values</title> - <para>Our current mapping strategy limits our means to specify - data integrity constraints. It is no longer possible to disallow - <code>null</code> values for properties belonging to derived - classes. We might want to disallow <code>null</code> values in the - <code>bankName</code> property. Hibernate will generate a - corresponding database attribute <coref - linkend="require_bankNameNotNullDb"/>:</para> + <para>Our current mapping strategy limits our means to specify data + integrity constraints. It is no longer possible to disallow + <code>null</code> values for properties belonging to derived + classes. We might want to disallow <code>null</code> values in the + <code>bankName</code> property. Hibernate will generate a + corresponding database attribute <coref + linkend="require_bankNameNotNullDb"/>:</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.v2; + <td valign="top"><programlisting>package inherit.v2; ... @Entity @DiscriminatorValue(value = "Bank account") public class BankAccount extends BillingDetails { String bankName; @Column(<emphasis role="bold">nullable=false</emphasis>) <co - xml:id="require_bankNameNotNull"/> + xml:id="require_bankNameNotNull"/> public String getBankName() {return bankName;} ...</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">CREATE TABLE BillingDetails ( + <td><programlisting continuation="continues">CREATE TABLE BillingDetails ( id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, bankName varchar(255) <emphasis role="bold">NOT NULL</emphasis>, <co - xml:id="require_bankNameNotNullDb"/> + xml:id="require_bankNameNotNullDb"/> ...</programlisting></td> - </tr> - </informaltable> + </tr> + </informaltable> - <para>Looks good? Unfortunately the attempt to save a bank account - <coref linkend="saveBankAccount"/> yields a runtime exception - <coref linkend="saveBankAccountException"/>:</para> + <para>Looks good? Unfortunately the attempt to save a bank account + <coref linkend="saveBankAccount"/> yields a runtime exception <coref + linkend="saveBankAccountException"/>:</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.v2; + <td valign="top"><programlisting>package inherit.v2; ... public class Persist { ... @@ -17400,12 +17404,12 @@ public class Persist { final BankAccount bankAccount = new BankAccount("1107 2 31", "Lehman Brothers", "BARCGB22"); session.save(bankAccount) <co xml:id="saveBankAccount"/>; ...</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">... + <td><programlisting continuation="continues">... Feb 19, 2013 10:28:00 AM org.hibernate.tool.hbm2ddl.SchemaUpdate execute INFO: HHH000232: Schema update complete Hibernate: @@ -17421,102 +17425,102 @@ Feb 19, 2013 10:28:00 AM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExc <emphasis role="bold">ERROR: Field 'bankName' doesn't have a default value Exception in thread "main" org.hibernate.exception.GenericJDBCException: Field 'bankName' doesn't have a default value</emphasis> <co - xml:id="saveBankAccountException"/> + xml:id="saveBankAccountException"/> ... at inherit.v2.Persist.main(Persist.java:28) Caused by: java.sql.SQLException: Field 'bankName' doesn't have a default value</programlisting></td> - </tr> - </informaltable> + </tr> + </informaltable> - <para>Conclusion: A table per class hierarchy mapping does not - allow to specify not null constraints for properties of derived - classes.</para> + <para>Conclusion: A table per class hierarchy mapping does not allow + to specify not null constraints for properties of derived + classes.</para> - <qandaset role="exercise"> - <title>Mapping figures</title> + <qandaset role="exercise"> + <title>Mapping figures</title> - <qandadiv> - <qandaentry> - <question> - <para>Map the following model to a database:</para> + <qandadiv> + <qandaentry> + <question> + <para>Map the following model to a database:</para> - <figure xml:id="modelFigureInheritance"> - <title>Figure subclasses</title> + <figure xml:id="modelFigureInheritance"> + <title>Figure subclasses</title> - <mediaobject> - <imageobject> - <imagedata fileref="Ref/Fig/figureInherit.fig"/> - </imageobject> - </mediaobject> - </figure> + <mediaobject> + <imageobject> + <imagedata fileref="Ref/Fig/figureInherit.fig"/> + </imageobject> + </mediaobject> + </figure> - <para>The two properties <code>xCenter</code> and - <code>yCenter</code> in the abstract base class - <code>Figure</code> represent the coordinates of the - concrete figure's center of gravity. In a drawing - application this would be considered the placement of the - respective object.</para> - - <para>The abstract method <code>getArea()</code> is meant - to be implemented without interfering with your database - mapping. Choose an integer discriminator. Test your - application by storing and loading objects.</para> - </question> + <para>The two properties <code>xCenter</code> and + <code>yCenter</code> in the abstract base class + <code>Figure</code> represent the coordinates of the + concrete figure's center of gravity. In a drawing + application this would be considered the placement of the + respective object.</para> + + <para>The abstract method <code>getArea()</code> is meant to + be implemented without interfering with your database + mapping. Choose an integer discriminator. Test your + application by storing and loading objects.</para> + </question> - <answer> - <para>The main difference to the current - <classname>inherit.v1.BillingDetails</classname> example - is the <classname>javax.persistence.Transient</classname> - annotation of the <code>area</code> property in - <classname>inherit.v3.Figure</classname>, - <classname>inherit.v3.Circle</classname> and - <classname>inherit.v3.Rectangle</classname>. The storage - ant retrieval applications are - <classname>inherit.v3.Persist</classname>, - <classname>inherit.v3.RetrieveRectangles</classname> and - <classname>inherit.v3.RetrieveAll</classname> are - straightforward.</para> - </answer> - </qandaentry> - </qandadiv> - </qandaset> - </section> + <answer> + <para>The main difference to the current + <classname>inherit.v1.BillingDetails</classname> example is + the <classname>javax.persistence.Transient</classname> + annotation of the <code>area</code> property in + <classname>inherit.v3.Figure</classname>, + <classname>inherit.v3.Circle</classname> and + <classname>inherit.v3.Rectangle</classname>. The storage ant + retrieval applications are + <classname>inherit.v3.Persist</classname>, + <classname>inherit.v3.RetrieveRectangles</classname> and + <classname>inherit.v3.RetrieveAll</classname> are + straightforward.</para> + </answer> + </qandaentry> + </qandadiv> + </qandaset> </section> + </section> - <section xml:id="joinedSubclass"> - <title>Joined subclasses</title> + <section xml:id="joinedSubclass"> + <title>Joined subclasses</title> - <para>The basic idea is to generate a normalized schema implementing - inheritance relationships by foreign keys:</para> + <para>The basic idea is to generate a normalized schema implementing + inheritance relationships by foreign keys:</para> - <figure xml:id="joindSubclassMapping"> - <title>Joined subclass mapping.</title> + <figure xml:id="joindSubclassMapping"> + <title>Joined subclass mapping.</title> - <mediaobject> - <imageobject> - <imagedata fileref="Ref/Fig/billingMapJoined.fig"/> - </imageobject> - </mediaobject> - </figure> + <mediaobject> + <imageobject> + <imagedata fileref="Ref/Fig/billingMapJoined.fig"/> + </imageobject> + </mediaobject> + </figure> - <para>The inheritance strategy of joined subclasses <coref - linkend="strategyJoinedSubclass"/> is being defined in the abstract - base class - <classname>inherit.joined.v1.BillingDetails</classname>:</para> + <para>The inheritance strategy of joined subclasses <coref + linkend="strategyJoinedSubclass"/> is being defined in the abstract + base class + <classname>inherit.joined.v1.BillingDetails</classname>:</para> - <programlisting>package inherit.joined.v1; + <programlisting>package inherit.joined.v1; ... @Entity @Inheritance(strategy=InheritanceType.JOINED) <co - xml:id="strategyJoinedSubclass"/> + xml:id="strategyJoinedSubclass"/> public abstract class BillingDetails { ... }</programlisting> - <para>The derived classes need to provide an implementation hint in - order to identify the required foreign key <coref - linkend="referenceParentClass"/> to the parent class - <classname>inherit.joined.v1.BillingDetails</classname>:</para> + <para>The derived classes need to provide an implementation hint in + order to identify the required foreign key <coref + linkend="referenceParentClass"/> to the parent class + <classname>inherit.joined.v1.BillingDetails</classname>:</para> - <programlisting>package inherit.joined.v1; + <programlisting>package inherit.joined.v1; ... @Entity @PrimaryKeyJoinColumn(name="parent" <co xml:id="referenceParentClass"/>, referencedColumnName="id") @@ -17534,44 +17538,43 @@ public class CreditCard extends BillingDetails { ... }</programlisting> - <para>Notice the ability to exclude null values in <coref - linkend="tpcNotNullCardType"/> and <coref - linkend="tpcNotNullexpiration"/>.</para> + <para>Notice the ability to exclude null values in <coref + linkend="tpcNotNullCardType"/> and <coref + linkend="tpcNotNullexpiration"/>.</para> - <section xml:id="joinedSubclassRetrieve"> - <title>Retrieving Objects</title> + <section xml:id="joinedSubclassRetrieve"> + <title>Retrieving Objects</title> - <para>On the database server side object retrieval results in a - more expensive operation: A query for root class instances - of<classname>inherit.joined.v1.BillingDetails</classname> <coref - linkend="joinedQueryBillingDetails"/> of our inheritance hierarchy - results in joining all three tables <code>BillingDetails</code> - <coref linkend="joinFromBillingDetails"/>, - <code>BankAccount</code> <coref linkend="joinFromBankAccount"/> - and <code>CreditCard</code> <coref - linkend="joinFromCreditCard"/>:</para> + <para>On the database server side object retrieval results in a more + expensive operation: A query for root class instances + of<classname>inherit.joined.v1.BillingDetails</classname> <coref + linkend="joinedQueryBillingDetails"/> of our inheritance hierarchy + results in joining all three tables <code>BillingDetails</code> + <coref linkend="joinFromBillingDetails"/>, <code>BankAccount</code> + <coref linkend="joinFromBankAccount"/> and <code>CreditCard</code> + <coref linkend="joinFromCreditCard"/>:</para> - <informaltable border="1"> - <colgroup width="6%"/> + <informaltable border="1"> + <colgroup width="6%"/> - <colgroup width="94%"/> + <colgroup width="94%"/> - <tr> - <td valign="top"><emphasis role="bold">Java</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Java</emphasis></td> - <td valign="top"><programlisting>package inherit.joined.v1; + <td valign="top"><programlisting>package inherit.joined.v1; ... public class RetrieveAll { ... final Query searchBilling = session.createQuery("<emphasis role="bold">from inherit.tpc.v1.BillingDetails</emphasis>" <co - xml:id="joinedQueryBillingDetails"/>); + xml:id="joinedQueryBillingDetails"/>); ...</programlisting></td> - </tr> + </tr> - <tr> - <td valign="top"><emphasis role="bold">Sql</emphasis></td> + <tr> + <td valign="top"><emphasis role="bold">Sql</emphasis></td> - <td><programlisting continuation="continues">Hibernate: + <td><programlisting continuation="continues">Hibernate: select billingdet0_.id as id0_, billingdet0_.created as created0_, @@ -17587,224 +17590,220 @@ public class RetrieveAll { end as clazz_ from <emphasis role="bold">BillingDetails</emphasis> billingdet0_ <co - xml:id="joinFromBillingDetails"/> + xml:id="joinFromBillingDetails"/> left outer join <emphasis role="bold">BankAccount</emphasis> billingdet0_1_ <co - xml:id="joinFromBankAccount"/> + xml:id="joinFromBankAccount"/> on billingdet0_.id=billingdet0_1_.id left outer join <emphasis role="bold">CreditCard</emphasis> billingdet0_2_ <co - xml:id="joinFromCreditCard"/> + xml:id="joinFromCreditCard"/> on billingdet0_.id=billingdet0_2_.id </programlisting></td> - </tr> - </informaltable> + </tr> + </informaltable> - <qandaset role="exercise"> - <title><link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> - constraints and database integrity.</title> + <qandaset role="exercise"> + <title><link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> + constraints and database integrity.</title> - <qandadiv> - <qandaentry> - <question> - <para>Explain all integrity constraints of the Hibernate - generated schema. Is it able to implement the correct - constraints on database level corresponding to the - inheritance related <link - linkend="gloss_Java"><trademark>Java</trademark></link> - objects? On contrary: Are there possible database states - which do not correspond to the domain model's object - constraints?</para> - </question> + <qandadiv> + <qandaentry> + <question> + <para>Explain all integrity constraints of the Hibernate + generated schema. Is it able to implement the correct + constraints on database level corresponding to the + inheritance related <link + linkend="gloss_Java"><trademark>Java</trademark></link> + objects? On contrary: Are there possible database states + which do not correspond to the domain model's object + constraints?</para> + </question> - <answer> - <para>We take a look to the database schema:</para> + <answer> + <para>We take a look to the database schema:</para> - <programlisting>CREATE TABLE BillingDetails ( + <programlisting>CREATE TABLE BillingDetails ( id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY <co - linkends="inheritJoinSqlJava-1" - xml:id="inheritJoinSqlJava-1-co"/>, + linkends="inheritJoinSqlJava-1" + xml:id="inheritJoinSqlJava-1-co"/>, created datetime NOT NULL, number varchar(32) NOT NULL ); CREATE TABLE CreditCard ( id bigint(20) NOT NULL PRIMARY KEY <co linkends="inheritJoinSqlJava-2" - xml:id="inheritJoinSqlJava-2-co"/> REFERENCES <co - linkends="inheritJoinSqlJava-3" - xml:id="inheritJoinSqlJava-3-co"/> BillingDetails, + xml:id="inheritJoinSqlJava-2-co"/> REFERENCES <co + linkends="inheritJoinSqlJava-3" + xml:id="inheritJoinSqlJava-3-co"/> BillingDetails, cardType int(11) NOT NULL, expiration datetime NOT NULL ); CREATE TABLE BankAccount ( id bigint(20) NOT NULL PRIMARY KEY <co linkends="inheritJoinSqlJava-4" - xml:id="inheritJoinSqlJava-4-co"/> REFERENCES <co - linkends="inheritJoinSqlJava-4" - xml:id="inheritJoinSqlJava-5-co"/> BillingDetails, + xml:id="inheritJoinSqlJava-4-co"/> REFERENCES <co + linkends="inheritJoinSqlJava-4" + xml:id="inheritJoinSqlJava-5-co"/> BillingDetails, bankName varchar(255) NOT NULL, swiftcode varchar(255) NOT NULL );</programlisting> - <calloutlist> - <callout arearefs="inheritJoinSqlJava-1-co" - xml:id="inheritJoinSqlJava-1"> - <para>The table implementing the root class - <classname>inherit.joined.v1.BillingDetails</classname> - of the inheritance hierarchy will be referenced both - by <code>CreditCard</code> and - <code>BankAccount</code> datasets and thus requires a - key to become addressable. Moreover the corresponding - <classname>inherit.joined.v1.BillingDetails</classname> - class requires this attribute to be the primary key - anyway.</para> - </callout> + <calloutlist> + <callout arearefs="inheritJoinSqlJava-1-co" + xml:id="inheritJoinSqlJava-1"> + <para>The table implementing the root class + <classname>inherit.joined.v1.BillingDetails</classname> + of the inheritance hierarchy will be referenced both by + <code>CreditCard</code> and <code>BankAccount</code> + datasets and thus requires a key to become addressable. + Moreover the corresponding + <classname>inherit.joined.v1.BillingDetails</classname> + class requires this attribute to be the primary key + anyway.</para> + </callout> - <callout arearefs="inheritJoinSqlJava-2-co" - xml:id="inheritJoinSqlJava-2"> - <para>Each <code>CreditCard</code> specific set of - attributes belongs to exactly one - <code>BillingDetails</code> instance and hence the id - within our table <code>CreditCard</code> must be - unique.</para> - </callout> + <callout arearefs="inheritJoinSqlJava-2-co" + xml:id="inheritJoinSqlJava-2"> + <para>Each <code>CreditCard</code> specific set of + attributes belongs to exactly one + <code>BillingDetails</code> instance and hence the id + within our table <code>CreditCard</code> must be + unique.</para> + </callout> - <callout arearefs="inheritJoinSqlJava-3-co" - xml:id="inheritJoinSqlJava-3"> - <para>As stated in <coref - linkend="inheritJoinSqlJava-2-co"/> each - <code>CreditCard</code> dataset must refer to its - parent <code>BillingDetails</code> instance.</para> - </callout> + <callout arearefs="inheritJoinSqlJava-3-co" + xml:id="inheritJoinSqlJava-3"> + <para>As stated in <coref + linkend="inheritJoinSqlJava-2-co"/> each + <code>CreditCard</code> dataset must refer to its parent + <code>BillingDetails</code> instance.</para> + </callout> - <callout arearefs="inheritJoinSqlJava-4-co inheritJoinSqlJava-5-co" - xml:id="inheritJoinSqlJava-4"> - <para>These constraints likewise describe <coref - linkend="inheritJoinSqlJava-2-co"/> and <coref - linkend="inheritJoinSqlJava-3-co"/> for - <code>BankAccount</code> datasets.</para> - </callout> - </calloutlist> + <callout arearefs="inheritJoinSqlJava-4-co inheritJoinSqlJava-5-co" + xml:id="inheritJoinSqlJava-4"> + <para>These constraints likewise describe <coref + linkend="inheritJoinSqlJava-2-co"/> and <coref + linkend="inheritJoinSqlJava-3-co"/> for + <code>BankAccount</code> datasets.</para> + </callout> + </calloutlist> - <para>The NOT NULL constraints implement their counterpart - properties in the corresponding <link - linkend="gloss_Java"><trademark>Java</trademark></link> - objects.</para> - - <para>The mapping does not cover one important integrity - constraint of our domain model: The base class - <classname>inherit.joined.v1.BillingDetails</classname> is - abstract. Thus each entry in the database must refer - either to a - <classname>inherit.joined.v1.CreditCard</classname> or a - <classname>inherit.joined.v1.BankAccount</classname> - instance. But the above database schema allows for - datasets to appear in the <code>BillingDetails</code> - table not being referenced by either - <code>BankAccount</code> or <code>CreditCard</code> - datasets.</para> - - <para>So the current database schema actually refers to a - domain model having a <emphasis - role="bold">concrete</emphasis> base class - <code>BillingDetails</code>.</para> - </answer> - </qandaentry> - </qandadiv> - </qandaset> + <para>The NOT NULL constraints implement their counterpart + properties in the corresponding <link + linkend="gloss_Java"><trademark>Java</trademark></link> + objects.</para> + + <para>The mapping does not cover one important integrity + constraint of our domain model: The base class + <classname>inherit.joined.v1.BillingDetails</classname> is + abstract. Thus each entry in the database must refer either + to a <classname>inherit.joined.v1.CreditCard</classname> or + a <classname>inherit.joined.v1.BankAccount</classname> + instance. But the above database schema allows for datasets + to appear in the <code>BillingDetails</code> table not being + referenced by either <code>BankAccount</code> or + <code>CreditCard</code> datasets.</para> + + <para>So the current database schema actually refers to a + domain model having a <emphasis + role="bold">concrete</emphasis> base class + <code>BillingDetails</code>.</para> + </answer> + </qandaentry> + </qandadiv> + </qandaset> - <qandaset role="exercise"> - <title>Implementing figures by joined subclasses</title> + <qandaset role="exercise"> + <title>Implementing figures by joined subclasses</title> - <qandadiv> - <qandaentry> - <question> - <para>Implement the model being given in <xref - linkend="modelFigureInheritance"/> by joined - subclasses.</para> - </question> + <qandadiv> + <qandaentry> + <question> + <para>Implement the model being given in <xref + linkend="modelFigureInheritance"/> by joined + subclasses.</para> + </question> - <answer> - <para>See - <classname>inherit.joined.v2.Figure</classname>.</para> - </answer> - </qandaentry> - </qandadiv> - </qandaset> - </section> + <answer> + <para>See + <classname>inherit.joined.v2.Figure</classname>.</para> + </answer> + </qandaentry> + </qandadiv> + </qandaset> </section> + </section> - <section xml:id="inheritTablePerConcrete"> - <title>Table per concrete class</title> + <section xml:id="inheritTablePerConcrete"> + <title>Table per concrete class</title> - <para>Not covered here.</para> - </section> + <para>Not covered here.</para> </section> + </chapter> - <section xml:id="mappingRelatedEntities"> - <title>Mapping related entities</title> + <chapter xml:id="mappingRelatedEntities"> + <title>Mapping related entities</title> - <section xml:id="primaryKeyRevisit"> - <title>Primary keys revisited</title> + <section xml:id="primaryKeyRevisit"> + <title>Primary keys revisited</title> - <para>Following <xref linkend="Bauer05"/> (p.88) we list important - properties of primary keys with respect to <quote>best - practices</quote> on top of their relational counterparts:</para> + <para>Following <xref linkend="Bauer05"/> (p.88) we list important + properties of primary keys with respect to <quote>best + practices</quote> on top of their relational counterparts:</para> - <itemizedlist> - <listitem> - <para>A primary key's values never change</para> - </listitem> + <itemizedlist> + <listitem> + <para>A primary key's values never change</para> + </listitem> - <listitem> - <para>Primary key values should not have a business - meaning</para> - </listitem> + <listitem> + <para>Primary key values should not have a business meaning</para> + </listitem> - <listitem> - <para>Primary keys should be chosen to have proper indexing - support with respect to the database product in question.</para> - </listitem> - </itemizedlist> + <listitem> + <para>Primary keys should be chosen to have proper indexing + support with respect to the database product in question.</para> + </listitem> + </itemizedlist> - <para>Regarding persistence we have three different concepts related - to object identity:</para> + <para>Regarding persistence we have three different concepts related + to object identity:</para> - <glosslist> - <glossentry> - <glossterm>Java Object identity</glossterm> + <glosslist> + <glossentry> + <glossterm>Java Object identity</glossterm> - <glossdef> - <para>The operator == checks whether two identifiers point to - the same memory address.</para> - </glossdef> - </glossentry> + <glossdef> + <para>The operator == checks whether two identifiers point to + the same memory address.</para> + </glossdef> + </glossentry> - <glossentry> - <glossterm>Java Object equality</glossterm> + <glossentry> + <glossterm>Java Object equality</glossterm> - <glossdef> - <para>The - <methodname>Object.equals(Object)</methodname>.</para> - </glossdef> - </glossentry> + <glossdef> + <para>The <methodname>Object.equals(Object)</methodname>.</para> + </glossdef> + </glossentry> - <glossentry> - <glossterm>Database identity</glossterm> + <glossentry> + <glossterm>Database identity</glossterm> - <glossdef> - <para>Two distinct datasets (tuples) are identical if all - primary key attributes have the same value.</para> + <glossdef> + <para>Two distinct datasets (tuples) are identical if all + primary key attributes have the same value.</para> - <para>In other words: Two distinct database objects differ at - least in one of their primary key attribute values.</para> - </glossdef> - </glossentry> - </glosslist> + <para>In other words: Two distinct database objects differ at + least in one of their primary key attribute values.</para> + </glossdef> + </glossentry> + </glosslist> - <section xml:id="objectEqualityByPrimaryKey"> - <title>Defining object equality by primary key</title> + <section xml:id="objectEqualityByPrimaryKey"> + <title>Defining object equality by primary key</title> - <para>Consider the following code:</para> + <para>Consider the following code:</para> - <programlisting>package session1; + <programlisting>package session1; ... public class LoadUser { ... @@ -17816,7 +17815,7 @@ public class LoadUser { final User user1 = (User) session.load(User.class, 1L); System.out.println("first transaction: user.equals(user1):" + user.equals(user1)); <co - xml:id="rereadInstance_1"/> + xml:id="rereadInstance_1"/> transaction.commit(); session.close(); @@ -17827,36 +17826,35 @@ public class LoadUser { final User user2 = (User) session.load(User.class, 1L); System.out.println("second transaction: user.equals(user2):" + user.equals(user2)); <co - xml:id="rereadInstance_2"/> + xml:id="rereadInstance_2"/> transaction.commit(); session.close(); } ...</programlisting> - <para>At first we compare two <classname>session1.User</classname> - instances being read by the same session manager <coref - linkend="rereadInstance_1"/>. Subsequently we compare a detached - <classname>session1.User</classname> instance with a second - instance being read by a different session <coref - linkend="rereadInstance_2"/>. This yields the following - result:</para> + <para>At first we compare two <classname>session1.User</classname> + instances being read by the same session manager <coref + linkend="rereadInstance_1"/>. Subsequently we compare a detached + <classname>session1.User</classname> instance with a second instance + being read by a different session <coref + linkend="rereadInstance_2"/>. This yields the following + result:</para> - <programlisting> first transaction: user.equals(user1):true <coref - linkend="rereadInstance_1"/> + <programlisting> first transaction: user.equals(user1):true <coref + linkend="rereadInstance_1"/> second transaction: user.equals(user2):false <coref linkend="rereadInstance_2"/></programlisting> - <para>The two instances in question definitely represent the same - database entity. The two entity managers referring to different - sessions create two distinct instances within a given <link - linkend="gloss_Java"><acronym>Java</acronym></link> - runtime.</para> + <para>The two instances in question definitely represent the same + database entity. The two entity managers referring to different + sessions create two distinct instances within a given <link + linkend="gloss_Java"><acronym>Java</acronym></link> runtime.</para> - <para>Since <link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> - entities require an <code>@javax.persistence.Id</code> attribute - we may generally define object equality solely based on this at - tribute's value:</para> + <para>Since <link linkend="gloss_JPA"><abbrev>JPA</abbrev></link> + entities require an <code>@javax.persistence.Id</code> attribute we + may generally define object equality solely based on this at + tribute's value:</para> - <programlisting>package session2; + <programlisting>package session2; ... public class User { @@ -17865,20 +17863,20 @@ public class User { ... @Override public boolean equals(Object other) { if (this == other) <co linkends="equalByPrimaryKey-1" - xml:id="equalByPrimaryKey-1-co"/>{ + xml:id="equalByPrimaryKey-1-co"/>{ return true; } else if (id == null) { return false; } else if (other instanceof User) { final User that = (User) other; return this.id.equals(that.getId()) <co linkends="equalByPrimaryKey-2" - xml:id="equalByPrimaryKey-2-co"/>; + xml:id="equalByPrimaryKey-2-co"/>; } else { return false; } } @Override public int hashCode() { <co linkends="equalByPrimaryKey-3" - xml:id="equalByPrimaryKey-3-co"/> + xml:id="equalByPrimaryKey-3-co"/> if (null == id) { return System.identityHashCode(this); } else { @@ -17887,59 +17885,59 @@ public class User { } }</programlisting> - <para>This way of defining - <methodname>session2.User.equals(java.lang.Object)</methodname> - implies that either or both of the following two conditions must - be met:</para> + <para>This way of defining + <methodname>session2.User.equals(java.lang.Object)</methodname> + implies that either or both of the following two conditions must be + met:</para> - <calloutlist> - <callout arearefs="equalByPrimaryKey-1-co" - xml:id="equalByPrimaryKey-1"> - <para>Both instances must be identical.</para> - </callout> + <calloutlist> + <callout arearefs="equalByPrimaryKey-1-co" + xml:id="equalByPrimaryKey-1"> + <para>Both instances must be identical.</para> + </callout> - <callout arearefs="equalByPrimaryKey-2-co" - xml:id="equalByPrimaryKey-2"> - <para>Both instances must have the same - <methodname>session2.User.getId()</methodname> value .</para> - </callout> - </calloutlist> + <callout arearefs="equalByPrimaryKey-2-co" + xml:id="equalByPrimaryKey-2"> + <para>Both instances must have the same + <methodname>session2.User.getId()</methodname> value .</para> + </callout> + </calloutlist> - <caution> - <para>Do not forget to implement - <methodname>Object.hashCode()</methodname> <coref - linkend="equalByPrimaryKey-3-co" xml:id="equalByPrimaryKey-3"/> - accordingly: Two instances <code>a</code> and <code>b</code> - returning <code>a.equals(b) == true</code> equal must return an - identical hash code value <code>a.hashCode() == - b.hashCode()</code> in order to satisfy a collection's - contract.</para> - </caution> - </section> + <caution> + <para>Do not forget to implement + <methodname>Object.hashCode()</methodname> <coref + linkend="equalByPrimaryKey-3-co" xml:id="equalByPrimaryKey-3"/> + accordingly: Two instances <code>a</code> and <code>b</code> + returning <code>a.equals(b) == true</code> equal must return an + identical hash code value <code>a.hashCode() == + b.hashCode()</code> in order to satisfy a collection's + contract.</para> + </caution> + </section> - <section xml:id="objectEqualityByNaturalKey"> - <title>Object equality by natural key</title> + <section xml:id="objectEqualityByNaturalKey"> + <title>Object equality by natural key</title> - <para>Defining entity equality based on database identity suffers - a severe deficiency: Newly created instances invariably differ - from any foreign non-identical instance regardless whether it does - have a database identity or not. We consider an example:</para> + <para>Defining entity equality based on database identity suffers a + severe deficiency: Newly created instances invariably differ from + any foreign non-identical instance regardless whether it does have a + database identity or not. We consider an example:</para> - <programlisting>package session2; + <programlisting>package session2; ... public class CompareNewlyCreated { ... // Create two transient instances final User a = new User(123, "goik", "Martin Goik"), <co - linkends="compareTransientById-1_2" - xml:id="compareTransientById-1-co"/> + linkends="compareTransientById-1_2" + xml:id="compareTransientById-1-co"/> b = new User(123, "goik", "Martin Goik"); <co - linkends="compareTransientById-1_2" - xml:id="compareTransientById-2-co"/> + linkends="compareTransientById-1_2" + xml:id="compareTransientById-2-co"/> System.out.println("a.equals(b):" + a.equals(b)); <co - linkends="compareTransientById-3" - xml:id="compareTransientById-3-co"/> + linkends="compareTransientById-3" + xml:id="compareTransientById-3-co"/> { final Session session = HibernateUtil.createSessionFactory("session2/hibernate.cfg.xml").openSession(); @@ -17947,72 +17945,69 @@ public class CompareNewlyCreated { ... // previously saved as new User(123, "goik", "Martin Goik"); final User user = (User) session.load(User.class, 1L); <co - linkends="compareTransientById-4" - xml:id="compareTransientById-4-co"/> + linkends="compareTransientById-4" + xml:id="compareTransientById-4-co"/> System.out.println("a.equals(user)):" + a.equals(user)); <co - linkends="compareTransientById-5" - xml:id="compareTransientById-5-co"/> + linkends="compareTransientById-5" + xml:id="compareTransientById-5-co"/> transaction.commit(); session.close(); } ...</programlisting> - <calloutlist> - <callout arearefs="compareTransientById-1-co compareTransientById-2-co" - xml:id="compareTransientById-1_2"> - <para><!--1 + 2-->Create two transient instances being - identical by value.</para> - </callout> + <calloutlist> + <callout arearefs="compareTransientById-1-co compareTransientById-2-co" + xml:id="compareTransientById-1_2"> + <para><!--1 + 2-->Create two transient instances being identical + by value.</para> + </callout> - <callout arearefs="compareTransientById-3-co" - xml:id="compareTransientById-3"> - <para><!--3-->Both instances are defined to differ by - value.</para> - </callout> + <callout arearefs="compareTransientById-3-co" + xml:id="compareTransientById-3"> + <para><!--3-->Both instances are defined to differ by + value.</para> + </callout> - <callout arearefs="compareTransientById-4-co" - xml:id="compareTransientById-4"> - <para><!--4-->Load a persistent entity from the - database.</para> - </callout> + <callout arearefs="compareTransientById-4-co" + xml:id="compareTransientById-4"> + <para><!--4-->Load a persistent entity from the database.</para> + </callout> - <callout arearefs="compareTransientById-5-co" - xml:id="compareTransientById-5"> - <para><!--5-->Transient and persistent instances are defined - to differ by value.</para> - </callout> - </calloutlist> + <callout arearefs="compareTransientById-5-co" + xml:id="compareTransientById-5"> + <para><!--5-->Transient and persistent instances are defined to + differ by value.</para> + </callout> + </calloutlist> - <para>Apparently this is definitely wrong: We do have unique - database index definitions. All objects in question do have common - values 123 and <code>"goik"</code> on these respective - keys.</para> - </section> + <para>Apparently this is definitely wrong: We do have unique + database index definitions. All objects in question do have common + values 123 and <code>"goik"</code> on these respective keys.</para> + </section> - <section xml:id="equalityByNaturalKey"> - <title>Implementing <methodname>Object.equals(Object)</methodname> - by natural keys</title> + <section xml:id="equalityByNaturalKey"> + <title>Implementing <methodname>Object.equals(Object)</methodname> + by natural keys</title> - <para>The last section's result actually provides a hint to - implement <methodname>Object.equals(Object)</methodname> in a more - meaningful way. The problem comparing transient instances occurs - since a surrogate key's value is being provided by the database - server when an entity is being persisted. If at least one natural - key (sometimes referred to as <quote>business key</quote>) is - being defined this one may be used instead:</para> + <para>The last section's result actually provides a hint to + implement <methodname>Object.equals(Object)</methodname> in a more + meaningful way. The problem comparing transient instances occurs + since a surrogate key's value is being provided by the database + server when an entity is being persisted. If at least one natural + key (sometimes referred to as <quote>business key</quote>) is being + defined this one may be used instead:</para> - <figure xml:id="implementEqualsByNaturalKey"> - <title>Implementing - <methodname>Object.equals(Object)</methodname> by natural - keys</title> + <figure xml:id="implementEqualsByNaturalKey"> + <title>Implementing <methodname>Object.equals(Object)</methodname> + by natural keys</title> - <programlisting>package session3; + <programlisting>package session3; @Entity @Table(uniqueConstraints={@<emphasis role="bold">UniqueConstraint(columnNames={"uid"}</emphasis>)<co - linkends="implementEqualsByNaturalKey-1" - xml:id="implementEqualsByNaturalKey-1-co"/> , + linkends="implementEqualsByNaturalKey-1" + xml:id="implementEqualsByNaturalKey-1-co"/> , @UniqueConstraint(columnNames={"uidNumber"})}) public class User { ... @@ -18031,8 +18026,8 @@ public class User { } else if (other instanceof User) { final User that = (User) other; return <emphasis role="bold">this.getUid().equals( that.getUid() )</emphasis>; <co - linkends="implementEqualsByNaturalKey-2" - xml:id="implementEqualsByNaturalKey-2-co"/> + linkends="implementEqualsByNaturalKey-2" + xml:id="implementEqualsByNaturalKey-2-co"/> } else { return false; } @@ -18043,146 +18038,143 @@ public class User { return System.identityHashCode(this); } else { return <emphasis role="bold">getUid().hashCode()</emphasis>; <co - linkends="implementEqualsByNaturalKey-3" - xml:id="implementEqualsByNaturalKey-3-co"/> + linkends="implementEqualsByNaturalKey-3" + xml:id="implementEqualsByNaturalKey-3-co"/> } } }</programlisting> - <calloutlist> - <callout arearefs="implementEqualsByNaturalKey-1-co" - xml:id="implementEqualsByNaturalKey-1"> - <para>Definition of property - <methodname>session3.User.getUid()</methodname> to become a - natural key.</para> - </callout> + <calloutlist> + <callout arearefs="implementEqualsByNaturalKey-1-co" + xml:id="implementEqualsByNaturalKey-1"> + <para>Definition of property + <methodname>session3.User.getUid()</methodname> to become a + natural key.</para> + </callout> - <callout arearefs="implementEqualsByNaturalKey-2-co" - xml:id="implementEqualsByNaturalKey-2"> - <para>Two <classname>session3.User</classname> instances - having identical - <methodname>session3.User.getUid()</methodname> values will - be considered equal.</para> - </callout> + <callout arearefs="implementEqualsByNaturalKey-2-co" + xml:id="implementEqualsByNaturalKey-2"> + <para>Two <classname>session3.User</classname> instances + having identical + <methodname>session3.User.getUid()</methodname> values will be + considered equal.</para> + </callout> - <callout arearefs="implementEqualsByNaturalKey-3-co" - xml:id="implementEqualsByNaturalKey-3"> - <para>The <methodname>session3.User.hashCode()</methodname> - implementation has to be changed accordingly.</para> - </callout> - </calloutlist> - </figure> + <callout arearefs="implementEqualsByNaturalKey-3-co" + xml:id="implementEqualsByNaturalKey-3"> + <para>The <methodname>session3.User.hashCode()</methodname> + implementation has to be changed accordingly.</para> + </callout> + </calloutlist> + </figure> - <qandaset role="exercise"> - <qandadiv> - <qandaentry> - <question> - <para>Consider <xref - linkend="implementEqualsByNaturalKey"/>. You may get a - different runtime behaviour when using <emphasis - role="bold"><code>this.uid().equals( that.uid() - )</code></emphasis> at <coref - linkend="implementEqualsByNaturalKey-2-co"/>. Execute the - corresponding <emphasis - role="bold">session3.CompareNewlyCreated</emphasis> and - <emphasis role="bold">session3.LoadUser</emphasis> - applications and explain the result.</para> - </question> + <qandaset role="exercise"> + <qandadiv> + <qandaentry> + <question> + <para>Consider <xref + linkend="implementEqualsByNaturalKey"/>. You may get a + different runtime behaviour when using <emphasis + role="bold"><code>this.uid().equals( that.uid() + )</code></emphasis> at <coref + linkend="implementEqualsByNaturalKey-2-co"/>. Execute the + corresponding <emphasis + role="bold">session3.CompareNewlyCreated</emphasis> and + <emphasis role="bold">session3.LoadUser</emphasis> + applications and explain the result.</para> + </question> - <answer> - <para><link linkend="gloss_JPA">JPA</link> allows lazy - fetch mode typically enabled by default. So the - <code>uid</code> attribute's initialization will be - deferred until - <methodname>session3.User.getUid()</methodname> is being - called for the first time.</para> - </answer> - </qandaentry> - </qandadiv> - </qandaset> - </section> + <answer> + <para><link linkend="gloss_JPA">JPA</link> allows lazy fetch + mode typically enabled by default. So the <code>uid</code> + attribute's initialization will be deferred until + <methodname>session3.User.getUid()</methodname> is being + called for the first time.</para> + </answer> + </qandaentry> + </qandadiv> + </qandaset> </section> + </section> - <section xml:id="entityValueTypes"> - <title>Entity and value types</title> + <section xml:id="entityValueTypes"> + <title>Entity and value types</title> - <para>From the viewpoint of <link linkend="gloss_ORM">ORM</link> we - distinguish two distinct types of database objects:</para> + <para>From the viewpoint of <link linkend="gloss_ORM">ORM</link> we + distinguish two distinct types of database objects:</para> - <glosslist> - <glossentry> - <glossterm>Value type</glossterm> + <glosslist> + <glossentry> + <glossterm>Value type</glossterm> - <glossdef> - <para>An object of value type has no database identity. It - will appear in a database as a composite of a parent entity - type. Its life cycle is completely dependent on its - parent.</para> - </glossdef> - </glossentry> + <glossdef> + <para>An object of value type has no database identity. It will + appear in a database as a composite of a parent entity type. Its + life cycle is completely dependent on its parent.</para> + </glossdef> + </glossentry> - <glossentry> - <glossterm>Entity type</glossterm> + <glossentry> + <glossterm>Entity type</glossterm> - <glossdef> - <para>Objects of this type do have their own database identity - and may exist independently of other (database) - entities.</para> - </glossdef> - </glossentry> - </glosslist> - </section> + <glossdef> + <para>Objects of this type do have their own database identity + and may exist independently of other (database) entities.</para> + </glossdef> + </glossentry> + </glosslist> + </section> - <section xml:id="sect_MappingComponents"> - <title>Mapping components</title> + <section xml:id="sect_MappingComponents"> + <title>Mapping components</title> - <para>We consider a simple example. We may add an email property to - <classname>session3.User</classname>:</para> + <para>We consider a simple example. We may add an email property to + <classname>session3.User</classname>:</para> - <programlisting>... + <programlisting>... public class User { ... private Email address; ...</programlisting> - <para>Why do we use a separate class Email rather than a simple - <emphasis role="bold"><code>private String email</code></emphasis> - declaration? The answer is quite simple: We want Email instances to - be extensible and allow for method definitions like - <code>sendEmail(...)</code>:</para> + <para>Why do we use a separate class Email rather than a simple + <emphasis role="bold"><code>private String email</code></emphasis> + declaration? The answer is quite simple: We want Email instances to be + extensible and allow for method definitions like + <code>sendEmail(...)</code>:</para> - <programlisting>public class Email { + <programlisting>public class Email { private String emailAddress; ... void sendEmail(final String subject, final String content) {} }</programlisting> - <para>Our <code>Email</code> class may of course have more than just - one property. We don't want to email addresses to be database - entities themselves. Instead they are meant to be components of User - instances. This is achieved by:</para> + <para>Our <code>Email</code> class may of course have more than just + one property. We don't want to email addresses to be database entities + themselves. Instead they are meant to be components of User instances. + This is achieved by:</para> - <glosslist> - <glossentry> - <glossterm>Annotate class Email to be embeddable:</glossterm> + <glosslist> + <glossentry> + <glossterm>Annotate class Email to be embeddable:</glossterm> - <glossdef> - <programlisting>package component.email; + <glossdef> + <programlisting>package component.email; <emphasis role="bold"> @Embeddable</emphasis> public class Email { private String emailAddress; ... }</programlisting> - </glossdef> - </glossentry> + </glossdef> + </glossentry> - <glossentry> - <glossterm>Annotate <code>emailAddress</code> to become an - embedded property:</glossterm> + <glossentry> + <glossterm>Annotate <code>emailAddress</code> to become an + embedded property:</glossterm> - <glossdef> - <programlisting>package component.email; + <glossdef> + <programlisting>package component.email; ... public class User { @@ -18190,14 +18182,14 @@ private Email address; <emphasis role="bold">@Embedded</emphasis> public Email getEmailAddress() { return address;} ...</programlisting> - </glossdef> - </glossentry> - </glosslist> + </glossdef> + </glossentry> + </glosslist> - <para>We may now persist <classname>component.email.User</classname> - instances:</para> + <para>We may now persist <classname>component.email.User</classname> + instances:</para> - <programlisting>package component.email; + <programlisting>package component.email; ... public class PersistUser { ... @@ -18209,14 +18201,14 @@ public class PersistUser { transaction.commit(); } ...</programlisting> - <qandaset role="exercise"> - <qandadiv> - <qandaentry> - <question> - <para>Consider the following sketch of an address - class:</para> + <qandaset role="exercise"> + <qandadiv> + <qandaentry> + <question> + <para>Consider the following sketch of an address + class:</para> - <programlisting>public class Address { + <programlisting>public class Address { private String street; private String city; @@ -18224,20 +18216,129 @@ public class PersistUser { ... }</programlisting> - <para>Extend class <classname>session3.User</classname> to - allow for two properties <code>homeAddress</code> and - <code>workAddress</code> of type Address.</para> + <para>Extend class <classname>session3.User</classname> to + allow for two properties <code>homeAddress</code> and + <code>workAddress</code> of type Address. You will encounter a + problem concerning conflicting database attribute names. The + hibernate forum does provide a <link + xlink:href="https://forum.hibernate.org/viewtopic.php?p=2432511">hint</link> + to resolve this issue in favour of multiple + <code>@javax.persistence.AttributeOverrides</code> + declarations.</para> + </question> + + <answer> + <para>See <classname>component.address.Address</classname> and + <classname>component.address.User</classname>.</para> + </answer> + </qandaentry> + + <qandaentry> + <question> + <para>Load a User instance from your database and demonstrate + that changes to an existing persistent object component's + value get persisted.</para> + </question> + + <answer> + <para>See + <classname>component.address.ModifyWorkAddress</classname>.</para> + </answer> + </qandaentry> + </qandadiv> + </qandaset> + + <section xml:id="valueSets"> + <title>Sets</title> + + <para>Users may have multiple email addresses:</para> + + <programlisting>public class User { + + Set<Email> email; + ...</programlisting> + + <para>Using embedded values we need an + <code>@ElementCollection</code> declaration to achieve a proper + mapping:</para> + + <programlisting>package component.emails; +... +public class User { +... + private Set<Email> emails = new HashSet<Email>(); + <emphasis role="bold">@ElementCollection </emphasis> + public Set<Email> getEmails() { return emails; } + ...</programlisting> + + <para>This will map <classname>component.emails.Email</classname> + entries in a separate table:</para> + + <programlisting>package component.emails; +... +public class PersistUser { + public static void main(String[] args) { + final Session session = HibernateUtil.createSessionFactory("component/emails/hibernate.cfg.xml").openSession(); + { + final Transaction transaction = session.beginTransaction(); + final User u = new User(123, "goik", "Martin Goik"); + u.getEmails().add(new Email("goik@hdm-stuttgart.de")); + u.getEmails().add(new Email("goma@someserver.org")); + session.save(u); + transaction.commit(); + } ...</programlisting> + + <qandaset role="exercise"> + <qandadiv> + <qandaentry> + <question> + <para>Create a <link linkend="gloss_Hql">HQL</link> query + searching for:</para> + + <itemizedlist> + <listitem> + <para>All Users having an email address + <email>goma@someserver.org</email>.</para> + </listitem> + + <listitem> + <para>All users having at least two email + addresses.</para> + </listitem> + </itemizedlist> + + <para>You may want to use a Hibernate Console to develop the + appropriate queries.</para> </question> </qandaentry> </qandadiv> </qandaset> - </section> - </section> - <section xml:id="sect_hibernateValidation"> - <title>Hibernate validation</title> + <qandaset role="exercise"> + <qandadiv> + <qandaentry> + <question> + <para>Construct a corresponding example allowing + <classname>session3.User</classname> instances to have a set + of embedded <classname>component.address.Address</classname> + elements.</para> + </question> + </qandaentry> + </qandadiv> + </qandaset> - <para/> + <qandaset role="exercise"> + <qandadiv> + <qandaentry> + <question> + <para>What happens, if an owning User instance gets deleted? + Are composed Email or Address values are implicitly being + deleted as well?</para> + </question> + </qandaentry> + </qandadiv> + </qandaset> + </section> </section> </chapter> </part> @@ -18248,6 +18349,16 @@ public class PersistUser { <para/> <glossary> + <glossentry xml:id="gloss_Hql"> + <glossterm><abbrev>HQL</abbrev></glossterm> + + <glossdef> + <para>The <link + xlink:href="http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html">Hibernate + Query Language</link>.</para> + </glossdef> + </glossentry> + <glossentry xml:id="gloss_Java"> <glossterm><trademark xlink:href="http://www.oracle.com/us/legal/third-party-trademarks/index.html">Java</trademark></glossterm> diff --git a/ws/eclipse/HibIntro/src/main/java/component/emails/DeleteUser.java b/ws/eclipse/HibIntro/src/main/java/component/emails/DeleteUser.java new file mode 100644 index 0000000000000000000000000000000000000000..ef715765cdaa9108e24a6766c61d999897f0f72e --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/component/emails/DeleteUser.java @@ -0,0 +1,29 @@ +package component.emails; + +import hibintro.util.HibernateUtil; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +/** + * @author goik + * + * Persisting {@link User} instances having component E-Mail Addresses + * + */ +public class DeleteUser { + + /** + * @param args not used. + */ + public static void main(String[] args) { + final Session session = HibernateUtil.createSessionFactory("component/emails/hibernate.cfg.xml").openSession(); + + { + final Transaction transaction = session.beginTransaction(); + final User user = (User) session.load(User.class, 1L); + session.delete(user); + transaction.commit(); + } + } +} \ No newline at end of file diff --git a/ws/eclipse/HibIntro/src/main/java/component/emails/Email.java b/ws/eclipse/HibIntro/src/main/java/component/emails/Email.java new file mode 100644 index 0000000000000000000000000000000000000000..9c0bb14876edd25e30be7b2afb906f06281415c9 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/component/emails/Email.java @@ -0,0 +1,19 @@ +package component.emails; + +import javax.persistence.Embeddable; + +@Embeddable +public class Email { + + private String emailAddress; + public String getEmailAddress() { return emailAddress;} + public void setEmailAddress(final String emailAddress) { this.emailAddress = emailAddress;} + + protected Email() {} + public Email(final String emailAddress) { + setEmailAddress(emailAddress); + } + void sendEmail(final String subject, final String content) { + //Not yet implemented + } +} \ No newline at end of file diff --git a/ws/eclipse/HibIntro/src/main/java/component/emails/PersistUser.java b/ws/eclipse/HibIntro/src/main/java/component/emails/PersistUser.java new file mode 100644 index 0000000000000000000000000000000000000000..8c5cf5c693f5531349c1df9ad1823c238c174e0f --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/component/emails/PersistUser.java @@ -0,0 +1,31 @@ +package component.emails; + +import hibintro.util.HibernateUtil; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +/** + * @author goik + * + * Persisting {@link User} instances having component E-Mail Addresses + * + */ +public class PersistUser { + + /** + * @param args not used. + */ + public static void main(String[] args) { + final Session session = HibernateUtil.createSessionFactory("component/emails/hibernate.cfg.xml").openSession(); + + { + final Transaction transaction = session.beginTransaction(); + final User u = new User(123, "goik", "Martin Goik"); + u.getEmails().add(new Email("goik@hdm-stuttgart.de")); + u.getEmails().add(new Email("goma@someserver.org")); + session.save(u); + transaction.commit(); + } + } +} \ No newline at end of file diff --git a/ws/eclipse/HibIntro/src/main/java/component/emails/User.java b/ws/eclipse/HibIntro/src/main/java/component/emails/User.java new file mode 100644 index 0000000000000000000000000000000000000000..743d610db7fa8f7713ed03e03f2db855bc4d8988 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/component/emails/User.java @@ -0,0 +1,91 @@ +package component.emails; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * @author goik + * + * {@link User} instances with addresses + * + */ +@Entity +@Table(uniqueConstraints={@UniqueConstraint(columnNames={"uid"}), + @UniqueConstraint(columnNames={"uidNumber"})}) +public class User { + + private Long id; + @Id + @GeneratedValue + public Long getId() {return id;} + protected void setId(Long id) {this.id = id;} + + int uidNumber; + public int getUidNumber() {return uidNumber;} + public void setUidNumber(int uidNumber) {this.uidNumber = uidNumber;} + + String uid; + + /** + * + * @return The user's unique login name e.g. "goik" + */ + @Column(nullable=false) + public String getUid() {return uid;} + public void setUid(String uid) {this.uid = uid;} + + String cname; + /** + * @return The user's common name e.g. "Martin Goik" + */ + @Column(nullable = false) + public String getCname() {return cname;} + public void setCname(String cname) {this.cname = cname;} + + private Set<Email> emails = new HashSet<Email>(); + @ElementCollection + public Set<Email> getEmails() { return emails; } + public void setEmails(Set<Email> emails) { this.emails = emails;} + + protected User() {} + + /** + * @param uidNumber See {@link #getUidNumber()}. + * @param uid See {@link #getUid()}. This parameter must not be null + * @param cname See {@link #getCname()}. + */ + public User(final int uidNumber, final String uid, final String cname) { + setUidNumber(uidNumber); + setUid(uid); + setCname(cname); + } + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (getUid() == null) { + return false; + } else if (other instanceof User) { + final User that = (User) other; + return this.getUid().equals( that.getUid() ); + } else { + return false; + } + } + @Override + public int hashCode() { + if (null == getUid()) { + return System.identityHashCode(this); + } else { + return getUid().hashCode(); + } + } +} \ No newline at end of file diff --git a/ws/eclipse/HibIntro/src/main/java/component/emails/hibernate.cfg.xml b/ws/eclipse/HibIntro/src/main/java/component/emails/hibernate.cfg.xml new file mode 100644 index 0000000000000000000000000000000000000000..61f03a0fb9456a23d79a4cc2d228e3f2bdc134c6 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/component/emails/hibernate.cfg.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE hibernate-configuration PUBLIC + "-//Hibernate/Hibernate Configuration DTD 3.0//EN" + "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> + +<hibernate-configuration> + <session-factory > + <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> + <property name="hibernate.connection.password">XYZ</property> + <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hdm</property> + <property name="hibernate.connection.username">hdmuser</property> + <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> + <property name="hibernate.show_sql">true</property> + <property name="hibernate.format_sql">true</property> + <property name="hibernate.hbm2ddl.auto">update</property> + + <mapping class="component.emails.User"/> + </session-factory> +</hibernate-configuration>