Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
G
GoikLectures
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Locked files
Deploy
Releases
Container Registry
Model registry
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Goik Martin
GoikLectures
Commits
96a2db5a
Commit
96a2db5a
authored
12 years ago
by
Goik Martin
Browse files
Options
Downloads
Patches
Plain Diff
Natural keys, equals and lazy fetchmode
parent
6e741b11
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
Doc/course.xml
+249
-5
249 additions, 5 deletions
Doc/course.xml
with
249 additions
and
5 deletions
Doc/course.xml
+
249
−
5
View file @
96a2db5a
...
...
@@ -17800,7 +17800,7 @@ CREATE TABLE BankAccount (
</glosslist>
<section xml:id="objectEqualityByPrimaryKey">
<title>Defining object equality</title>
<title>Defining object equality
by primary key
</title>
<para>Consider the following code:</para>
...
...
@@ -17847,15 +17847,259 @@ second transaction: user.equals(user2):false <coref linkend="rereadInstance_2"/>
<para>The two instances in question definitely represent the same
database entity. The two entity managers referring to different
sessions create two disting instances within the java
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>
we may generally define object equality solely based on this
at
tribute's value:</para>
<programlisting/>
<programlisting>package session2;
...
public class User {
@Id @GeneratedValue
private Long id;
...
@Override public boolean equals(Object other) {
if (this == other) <co linkends="equalByPrimaryKey-1"
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"/>;
} else {
return false;
}
}
@Override public int hashCode() { <co linkends="equalByPrimaryKey-3"
xml:id="equalByPrimaryKey-3-co"/>
if (null == id) {
return System.identityHashCode(this);
} else {
return id.hashCode();
}
}
}</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>
<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>
<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>
<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;
...
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"/>
b = new User(123, "goik", "Martin Goik"); <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"/>
{
final Session session = HibernateUtil.createSessionFactory("session2/hibernate.cfg.xml").openSession();
final Transaction transaction = session.beginTransaction();
// 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"/>
System.out.println("a.equals(user)):" + a.equals(user)); <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>
<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-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>
<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>
<figure xml:id="implementEqualsByNaturalKey">
<title>Implementing
<methodname>Object.equals(Object)</methodname> by natural
keys</title>
<programlisting>package session3;
@Entity
@Table(uniqueConstraints={@<emphasis role="bold">UniqueConstraint(columnNames={"uid"}</emphasis>)<co
linkends="implementEqualsByNaturalKey-1"
xml:id="implementEqualsByNaturalKey-1-co"/> ,
@UniqueConstraint(columnNames={"uidNumber"})})
public class User {
...
String uid;
@Column(nullable=false)
public String getUid() {return uid;}
public void setUid(String uid) {this.uid = uid;}
...
@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 <emphasis role="bold">this.getUid().equals( that.getUid() )</emphasis>; <co
linkends="implementEqualsByNaturalKey-2"
xml:id="implementEqualsByNaturalKey-2-co"/>
} else {
return false;
}
}
@Override
public int hashCode() {
if (null == getUid()) {
return System.identityHashCode(this);
} else {
return <emphasis role="bold">getUid().hashCode()</emphasis>; <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>
<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>
<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>
</section>
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment