Skip to content
Snippets Groups Projects
Commit 31059974 authored by Goik Martin's avatar Goik Martin
Browse files

oneToOne, cascading persistence

parent 80f735f4
No related branches found
No related tags found
No related merge requests found
Showing
with 577 additions and 2 deletions
......@@ -15686,7 +15686,9 @@ Namespace '<emphasis role="bold">http://www.w3.org/2000/svg</emphasis>' contains
<listitem>
<para>Counting frequencies and grouping by namespaces may be
achieved by using standard Java container implementations of
<classname>java.util.Map</classname>.</para>
<classname>java.util.Map</classname>. You may for example
define sets of related XML elements and group them by their
corresponding namespaces.</para>
</listitem>
 
<listitem>
......@@ -18067,6 +18069,9 @@ CREATE TABLE BankAccount (
</glossentry>
</glosslist>
 
<para>We recommend <xref linkend="Bloch08"/> regarding Java identity
and equality of objects.</para>
<section xml:id="objectEqualityByPrimaryKey">
<title>Defining object equality by primary key</title>
 
......@@ -18181,6 +18186,11 @@ public class User {
identical hash code value <code>a.hashCode() ==
b.hashCode()</code> in order to satisfy a collection's
contract.</para>
<para><xref linkend="Bloch08"/> covers the contract of
<methodname>Object.hashCode()</methodname> and its close relation
to <methodname>Object.equals(Object)</methodname> in
detail.</para>
</caution>
</section>
 
......@@ -18522,7 +18532,7 @@ public class PersistUser {
</qandaset>
 
<section xml:id="valueSets">
<title>Sets</title>
<title>Sets and lists</title>
 
<para>Users may have multiple email addresses:</para>
 
......@@ -18634,6 +18644,146 @@ public class PersistUser {
</qandaset>
</section>
</section>
<section xml:id="mappingEntities">
<title>Mapping entities</title>
<para>In contrast to mapping an entity having components we may also
persist relations between entities. We start with
<classname>entity.company1.Employee</classname>s possibly having
<classname>entity.company1.Laptop</classname>s assigned. So an
employee may or may not have one device at its disposal.</para>
<para>There may be other references to each laptop. So in contrast to
our email component examples
<classname>entity.company1.Laptop</classname> instances are
identifiable database objects and hence entities. Therefore we have to
add a <code>@OneToOne</code> annotation <coref
linkend="laptopOneToOne"/>:</para>
<programlisting>package entity.company1;
...
@Entity ...
public class Employee {
...
public int getStaffNumber() { return staffNumber;}
...
public String getCname() { return cname;}
...
/**
* @return Laptop currently "owned" by {@link Employee}.
*/
<emphasis role="bold">@OneToOne</emphasis> <co xml:id="laptopOneToOne"/>
public Laptop getLaptop() { return laptop;}
public void setLaptop(Laptop laptop) {this.laptop = laptop;}
private Laptop laptop;
...</programlisting>
<para>Populating the database with data seems to be quite
straightforward:</para>
<programlisting>package entity.company1;
...
public class Persist1 {
...
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
session.save(martin);
session.save(eve);
transaction.commit(); ...</programlisting>
<para>This actually fails:</para>
<programlisting> <classname>org.hibernate.TransientObjectException</classname>:
object references an unsaved transient instance -
save the transient instance before flushing: <classname>entity.company1.Laptop</classname></programlisting>
<para>There are at least two possible solutions:</para>
<orderedlist>
<listitem>
<para>We may save the dependent transient
<classname>entity.company1.Laptop</classname> instance along with
the two <classname>entity.company1.Employee</classname> instances.
There is no restriction regarding the order of these three save
operations but they must happen within the same
transaction:</para>
<programlisting>package entity.company1;
...
public class Persist2 {
...
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
session.save(martin);
session.save(eve);
session.save(superCoolGadget);
transaction.commit(); ...</programlisting>
</listitem>
<listitem>
<para>Hibernate supports the <quote>persistence by
reachability</quote> concept. Thus saving an
<classname>entity.company1.Employee</classname> instance should
save possibly referenced
<classname>entity.company1.Laptop</classname> instances as
well:</para>
<programlisting>package entity.company2;
...
public class Employee {
...
@OneToOne(<emphasis role="bold">cascade={CascadeType.ALL}</emphasis>)
public Laptop getLaptop() { return laptop;}
...</programlisting>
<para>No we do no longer have to care about referenced
<classname>entity.company1.Laptop</classname>s:</para>
<programlisting>package entity.company2;
...
public class Persist {
...
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
// Now only save the employee transients,
// superCoolGadget will be saved automatically due to
// CascadeType.ALL
session.save(martin);
session.save(eve);
transaction.commit();...</programlisting>
</listitem>
</orderedlist>
<para>After successfully inserting these three entities
<personname>Eve Blix</personname> may no longer be entitled to use her
laptop. We decouple these two entities:</para>
<programlisting>package entity.company2;
public class LoadAndDeRegister {
...
final Transaction transaction = session.beginTransaction();
final Employee eve = (Employee) session.load(Employee.class, 2L);
eve.setLaptop(null);
transaction.commit();</programlisting>
<para>All three entities are still present in our database but the
corresponding foreign key has been set to <code>NULL</code>.</para>
</section>
</chapter>
</part>
 
......
package entity.company1;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import entity.company12.Department;
/**
*
* {@link Employee} instances related to {@link Department}
*
*/
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"staffNumber"})})
public class Employee {
@Id
@GeneratedValue
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
private Long id;
public int getStaffNumber() { return staffNumber;}
public void setStaffNumber(int staffNumber) { this.staffNumber = staffNumber;}
int staffNumber = Integer.MIN_VALUE;
public String getCname() { return cname;}
public void setCname(String cname) { this.cname = cname;}
private String cname; // Common name
/**
* @return Laptop currently "owned" by {@link Employee}.
*/
@OneToOne
public Laptop getLaptop() { return laptop;}
public void setLaptop(Laptop laptop) {this.laptop = laptop;}
private Laptop laptop;// Workstation for employees.
protected Employee(){}
public Employee(final int staffNumber, final String cname) {
setStaffNumber(staffNumber);
setCname(cname);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (Integer.MIN_VALUE == getStaffNumber()) {
return false;
} else if (other instanceof Employee) {
final Employee that = (Employee) other;
return this.getStaffNumber() == that.getStaffNumber();
} else {
return false;
}
}
@Override
public int hashCode() {
if (Integer.MIN_VALUE == getStaffNumber()) {
return System.identityHashCode(this);
} else {
return getStaffNumber();
}
}
}
\ No newline at end of file
package entity.company1;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
*
* Laptops possibly being assigned to {@link Employee}
*
*/
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"idNumber"})})
public class Laptop {
@Id
@GeneratedValue
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
private Long id;
/**
* @return The device's unique part number
*/
public int getIdNumber() { return idNumber;}
public void setIdNumber(int idNumber) { this.idNumber = idNumber;}
int idNumber;
protected Laptop(){}
public Laptop(final int idNumber) {
setIdNumber(idNumber);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (Integer.MIN_VALUE == getIdNumber()) {
return false;
} else if (other instanceof Laptop) {
final Laptop that = (Laptop) other;
return this.getIdNumber() == that.getIdNumber();
} else {
return false;
}
}
@Override
public int hashCode() {
if (Integer.MIN_VALUE == getIdNumber()) {
return System.identityHashCode(this);
} else {
return getIdNumber();
}
}
}
\ No newline at end of file
package entity.company1;
import hibintro.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
/**
* Persisting {@link Employee} possibly along with
* personal assigned laptops.
*/
public class Persist1 {
/**
* @param args not used.
*/
public static void main(String[] args) {
final Session session = HibernateUtil.createSessionFactory("entity/company1/hibernate.cfg.xml").openSession();
{
final Transaction transaction = session.beginTransaction();
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
session.save(martin);
session.save(eve);
transaction.commit();
}
}
}
\ No newline at end of file
package entity.company1;
import hibintro.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
/**
* @author goik
*
* Persisting {@link Employee} possibly along with
* personally assigned laptops. Persisting dependent
* transient objects corrects bug in {@link Persist1}
*
*/
public class Persist2 {
/**
* @param args not used.
*/
public static void main(String[] args) {
final Session session = HibernateUtil.createSessionFactory("entity/company1/hibernate.cfg.xml").openSession();
{
final Transaction transaction = session.beginTransaction();
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
session.save(martin);
session.save(eve);
session.save(superCoolGadget);
transaction.commit();
}
}
}
\ No newline at end of file
<?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">false</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping class="entity.company1.Laptop"/>
<mapping class="entity.company1.Employee"/>
</session-factory>
</hibernate-configuration>
package entity.company2;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import entity.company12.Department;
/**
*
* {@link Employee} instances related to {@link Department}
*
*/
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"staffNumber"})})
public class Employee {
@Id
@GeneratedValue
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
private Long id;
public int getStaffNumber() { return staffNumber;}
public void setStaffNumber(int staffNumber) { this.staffNumber = staffNumber;}
int staffNumber = Integer.MIN_VALUE;
public String getCname() { return cname;}
public void setCname(String cname) { this.cname = cname;}
private String cname; // Common name
/**
* @return Laptop possibly "owned" by {@link Employee}.
*/
@OneToOne(cascade={CascadeType.ALL})
public Laptop getLaptop() { return laptop;}
public void setLaptop(Laptop laptop) {this.laptop = laptop;}
private Laptop laptop;// Workstation for employees.
protected Employee(){}
public Employee(final int staffNumber, final String cname) {
setStaffNumber(staffNumber);
setCname(cname);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (Integer.MIN_VALUE == getStaffNumber()) {
return false;
} else if (other instanceof Employee) {
final Employee that = (Employee) other;
return this.getStaffNumber() == that.getStaffNumber();
} else {
return false;
}
}
@Override
public int hashCode() {
if (Integer.MIN_VALUE == getStaffNumber()) {
return System.identityHashCode(this);
} else {
return getStaffNumber();
}
}
}
\ No newline at end of file
package entity.company2;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
*
* Laptops possibly being assigned to {@link Employee}
*
*/
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"idNumber"})})
public class Laptop {
@Id
@GeneratedValue
public Long getId() {return id;}
protected void setId(Long id) {this.id = id;}
private Long id;
/**
* @return The device's unique part number
*/
public int getIdNumber() { return idNumber;}
public void setIdNumber(int idNumber) { this.idNumber = idNumber;}
int idNumber;
protected Laptop(){}
public Laptop(final int idNumber) {
setIdNumber(idNumber);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (Integer.MIN_VALUE == getIdNumber()) {
return false;
} else if (other instanceof Laptop) {
final Laptop that = (Laptop) other;
return this.getIdNumber() == that.getIdNumber();
} else {
return false;
}
}
@Override
public int hashCode() {
if (Integer.MIN_VALUE == getIdNumber()) {
return System.identityHashCode(this);
} else {
return getIdNumber();
}
}
}
\ No newline at end of file
package entity.company2;
import hibintro.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
/**
* Removing {@link Laptop} from {@link Employee}
*/
public class LoadAndDeRegister {
/**
* @param args not used.
*/
public static void main(String[] args) {
final Session session = HibernateUtil.createSessionFactory("entity/company2/hibernate.cfg.xml").openSession();
{
final Transaction transaction = session.beginTransaction();
final Employee eve = (Employee) session.load(Employee.class, 2L);
eve.setLaptop(null);
transaction.commit();
}
}
}
\ No newline at end of file
package entity.company2;
import hibintro.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
/**
* Persisting {@link Employee} possibly along with
* personal assigned laptops.
*/
public class Persist {
/**
* @param args not used.
*/
public static void main(String[] args) {
final Session session = HibernateUtil.createSessionFactory("entity/company2/hibernate.cfg.xml").openSession();
{
final Transaction transaction = session.beginTransaction();
final Employee martin = new Employee(123, "Martin Goik"),
eve = new Employee(140, "Eve Blix");
final Laptop superCoolGadget = new Laptop(2213);
eve.setLaptop(superCoolGadget);
// Now only save the employee transients,
// superCoolGadget will be saved automatically due to
// CascadeType.ALL
session.save(martin);
session.save(eve);
transaction.commit();
}
}
}
\ No newline at end of file
<?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">false</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping class="entity.company2.Laptop"/>
<mapping class="entity.company2.Employee"/>
</session-factory>
</hibernate-configuration>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment