From 310599742c4cbabb12cd8a75b970eb2cd2ce76ac Mon Sep 17 00:00:00 2001 From: Martin Goik <goik@hdm-stuttgart.de> Date: Mon, 22 Apr 2013 21:26:13 +0200 Subject: [PATCH] oneToOne, cascading persistence --- Doc/course.xml | 154 +++++++++++++++++- .../main/java/entity/company1/Employee.java | 70 ++++++++ .../src/main/java/entity/company1/Laptop.java | 57 +++++++ .../main/java/entity/company1/Persist1.java | 30 ++++ .../main/java/entity/company1/Persist2.java | 39 +++++ .../java/entity/company1/hibernate.cfg.xml | 21 +++ .../main/java/entity/company2/Employee.java | 71 ++++++++ .../src/main/java/entity/company2/Laptop.java | 57 +++++++ .../entity/company2/LoadAndDeRegister.java | 26 +++ .../main/java/entity/company2/Persist.java | 33 ++++ .../java/entity/company2/hibernate.cfg.xml | 21 +++ 11 files changed, 577 insertions(+), 2 deletions(-) create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company1/Employee.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company1/Laptop.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company1/Persist1.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company1/Persist2.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company1/hibernate.cfg.xml create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company2/Employee.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company2/Laptop.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company2/LoadAndDeRegister.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company2/Persist.java create mode 100644 ws/eclipse/HibIntro/src/main/java/entity/company2/hibernate.cfg.xml diff --git a/Doc/course.xml b/Doc/course.xml index 46c16a757..6ff75c093 100644 --- a/Doc/course.xml +++ b/Doc/course.xml @@ -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> diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company1/Employee.java b/ws/eclipse/HibIntro/src/main/java/entity/company1/Employee.java new file mode 100644 index 000000000..3143f81fc --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company1/Employee.java @@ -0,0 +1,70 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company1/Laptop.java b/ws/eclipse/HibIntro/src/main/java/entity/company1/Laptop.java new file mode 100644 index 000000000..52dfb3ee1 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company1/Laptop.java @@ -0,0 +1,57 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist1.java b/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist1.java new file mode 100644 index 000000000..bd386e971 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist1.java @@ -0,0 +1,30 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist2.java b/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist2.java new file mode 100644 index 000000000..a078e3611 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company1/Persist2.java @@ -0,0 +1,39 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company1/hibernate.cfg.xml b/ws/eclipse/HibIntro/src/main/java/entity/company1/hibernate.cfg.xml new file mode 100644 index 000000000..261f65541 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company1/hibernate.cfg.xml @@ -0,0 +1,21 @@ +<?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> diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company2/Employee.java b/ws/eclipse/HibIntro/src/main/java/entity/company2/Employee.java new file mode 100644 index 000000000..ada3cda7a --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company2/Employee.java @@ -0,0 +1,71 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company2/Laptop.java b/ws/eclipse/HibIntro/src/main/java/entity/company2/Laptop.java new file mode 100644 index 000000000..462aa3b7f --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company2/Laptop.java @@ -0,0 +1,57 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company2/LoadAndDeRegister.java b/ws/eclipse/HibIntro/src/main/java/entity/company2/LoadAndDeRegister.java new file mode 100644 index 000000000..b4040b6d1 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company2/LoadAndDeRegister.java @@ -0,0 +1,26 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company2/Persist.java b/ws/eclipse/HibIntro/src/main/java/entity/company2/Persist.java new file mode 100644 index 000000000..d68352e66 --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company2/Persist.java @@ -0,0 +1,33 @@ +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 diff --git a/ws/eclipse/HibIntro/src/main/java/entity/company2/hibernate.cfg.xml b/ws/eclipse/HibIntro/src/main/java/entity/company2/hibernate.cfg.xml new file mode 100644 index 000000000..57b61c84a --- /dev/null +++ b/ws/eclipse/HibIntro/src/main/java/entity/company2/hibernate.cfg.xml @@ -0,0 +1,21 @@ +<?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> -- GitLab