Pārlūkot izejas kodu

Domain model persisted using Hibernate annotations.

Repository test against an actual database.

Hibernate needs package-protected no-args constructors, which blocks the use of final in many places.

"Long id" properties are excluded from equals and hashcode builders, to enforce the use of natural business keys that are separate from persistence details.
peter_backlund 18 gadus atpakaļ
vecāks
revīzija
21ffa2b051

+ 29
- 3
dddsample/src/main/java/se/citerus/dddsample/domain/Cargo.java Parādīt failu

@@ -3,17 +3,35 @@ package se.citerus.dddsample.domain;
3 3
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
4 4
 import org.apache.commons.lang.builder.ToStringStyle;
5 5
 
6
+import javax.persistence.*;
7
+
6 8
 
7 9
 /**
8 10
  * A Cargo an entity identifed by TrackingId and is capable of getting its DeliveryHistory plus a number
9 11
  * of convenience operation for finding current destination etc.
10 12
  */
13
+@Entity
14
+@Table(name = "cargo")
11 15
 public class Cargo {
12
-  private final TrackingId trackingId;
13
-  private final Location origin;
14
-  private final Location finalDestination;
16
+
17
+  @EmbeddedId
18
+  private TrackingId trackingId;
19
+
20
+  @ManyToOne(fetch = FetchType.EAGER)
21
+  @JoinColumn(name = "origin_location_fk")
22
+  private Location origin;
23
+
24
+  @ManyToOne(fetch = FetchType.EAGER)
25
+  @JoinColumn(name = "final_destination_location_fk")
26
+  private Location finalDestination;
27
+
28
+  @OneToOne(fetch = FetchType.LAZY)
29
+  @JoinColumn(name = "delivery_history_fk")
15 30
   private DeliveryHistory history;
16 31
 
32
+  // Needed by Hibernate
33
+  Cargo() {}
34
+
17 35
   public Cargo(TrackingId trackingId, Location origin, Location finalDestination) {
18 36
     this.trackingId = trackingId;
19 37
     this.origin = origin;
@@ -58,6 +76,14 @@ public class Cargo {
58 76
     return trackingId;
59 77
   }
60 78
 
79
+  public Location origin() {
80
+    return origin;
81
+  }
82
+
83
+  public Location finalDestination() {
84
+    return finalDestination;
85
+  }
86
+
61 87
   @Override
62 88
   public String toString() {
63 89
     return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);

+ 13
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/CarrierMovement.java Parādīt failu

@@ -1,7 +1,20 @@
1 1
 package se.citerus.dddsample.domain;
2 2
 
3
+import javax.persistence.*;
4
+
5
+@Entity
6
+@Table(name = "carrier_movement")
3 7
 public class CarrierMovement {
8
+
9
+  @Id
10
+  private Long id;
11
+
12
+  @ManyToOne(fetch = FetchType.EAGER)
13
+  @JoinColumn(name = "from_location_fk")
4 14
   private final Location from;
15
+
16
+  @ManyToOne(fetch = FetchType.EAGER)
17
+  @JoinColumn(name = "to_location_fk")
5 18
   private final Location to;
6 19
 
7 20
   public CarrierMovement(Location from, Location to) {

+ 14
- 3
dddsample/src/main/java/se/citerus/dddsample/domain/DeliveryHistory.java Parādīt failu

@@ -1,17 +1,28 @@
1 1
 package se.citerus.dddsample.domain;
2 2
 
3
-import java.util.SortedSet;
4
-import java.util.TreeSet;
5
-
6 3
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
7 4
 import org.apache.commons.lang.builder.ToStringStyle;
5
+import org.hibernate.annotations.Sort;
6
+import org.hibernate.annotations.SortType;
7
+
8
+import javax.persistence.*;
9
+import java.util.SortedSet;
10
+import java.util.TreeSet;
8 11
 
9 12
 /**
10 13
  * A wrapper class that holds a sorted set of HandlingEvents. The set can not contain events with the same timestamp.
11 14
  * 
12 15
  */
16
+@Entity
17
+@Table(name = "delivery_history")
13 18
 public class DeliveryHistory {
14 19
 
20
+  @Id
21
+  private Long id;
22
+
23
+  @OneToMany(fetch = FetchType.EAGER)
24
+  @JoinColumn(name = "delivery_history_fk")
25
+  @Sort(type = SortType.NATURAL)
15 26
   private final SortedSet<HandlingEvent> events = new TreeSet<HandlingEvent>();
16 27
 
17 28
   public SortedSet<HandlingEvent> getEvents() {

+ 24
- 5
dddsample/src/main/java/se/citerus/dddsample/domain/HandlingEvent.java Parādīt failu

@@ -3,6 +3,7 @@ package se.citerus.dddsample.domain;
3 3
 import org.apache.commons.lang.builder.EqualsBuilder;
4 4
 import org.apache.commons.lang.builder.HashCodeBuilder;
5 5
 
6
+import javax.persistence.*;
6 7
 import java.util.Date;
7 8
 
8 9
 /**
@@ -13,16 +14,34 @@ import java.util.Date;
13 14
  * in correct order.
14 15
  * 
15 16
  */
17
+@Entity
18
+@Table(name = "handling_events")
16 19
 public class HandlingEvent implements Comparable<HandlingEvent> {
17 20
 
18
-  private final Type type;
19
-  private final CarrierMovement carrierMovement;
20
-  private final Date time;
21
+  @Id
22
+  private Long id;
23
+
24
+  @Enumerated(value = EnumType.STRING)
25
+  @Column(name = "type")
26
+  private Type type;
27
+
28
+  @ManyToOne(fetch = FetchType.EAGER)
29
+  @JoinColumn(name = "carrier_movement_fk")
30
+  private CarrierMovement carrierMovement;
31
+  
32
+  @Column(name = "time")
33
+  private Date time;
21 34
 
22 35
   public enum Type {
23 36
     LOAD, UNLOAD, RECEIVE, CLAIM
24 37
   }
25 38
 
39
+  // Exclude the id field from equals() and hashcode()
40
+  private static final String[] excludedFields = {"id"};
41
+
42
+  // Needed by Hibernate
43
+  HandlingEvent() {}
44
+
26 45
   public HandlingEvent(Date time, Type type, CarrierMovement carrierMovement) {
27 46
     this.time = time;
28 47
     this.type = type;
@@ -77,12 +96,12 @@ public class HandlingEvent implements Comparable<HandlingEvent> {
77 96
   
78 97
   @Override
79 98
   public boolean equals(Object obj) {
80
-    return EqualsBuilder.reflectionEquals(this, obj);
99
+    return EqualsBuilder.reflectionEquals(this, obj, excludedFields);
81 100
   }
82 101
 
83 102
   @Override
84 103
   public int hashCode() {
85
-    return HashCodeBuilder.reflectionHashCode(this);
104
+    return HashCodeBuilder.reflectionHashCode(this, excludedFields);
86 105
   }
87 106
 
88 107
   public int compareTo(HandlingEvent o) {

+ 25
- 5
dddsample/src/main/java/se/citerus/dddsample/domain/Location.java Parādīt failu

@@ -3,13 +3,30 @@ package se.citerus.dddsample.domain;
3 3
 import org.apache.commons.lang.builder.EqualsBuilder;
4 4
 import org.apache.commons.lang.builder.HashCodeBuilder;
5 5
 
6
+import javax.persistence.Column;
7
+import javax.persistence.Entity;
8
+import javax.persistence.Id;
9
+import javax.persistence.Table;
10
+
11
+@Entity
12
+@Table(name = "locations")
6 13
 public class Location {
7 14
   /**
8 15
    * The NULL Location object
9 16
    */
10
-  public static final Location NULL = new Location("");
11
-  
12
-  private final String unlocode;
17
+  public static final Location NULL = new Location(Location.class.getName() + ".NULL");
18
+
19
+  @Id
20
+  private Long id;
21
+
22
+  @Column(name = "unlocode")
23
+  private String unlocode;
24
+
25
+  // Exclude the id field from equals() and hashcode()
26
+  private static final String[] excludedFields = {"id"};
27
+
28
+  // Needed by Hibernate
29
+  Location() {}
13 30
 
14 31
   public Location(String unlocode) {
15 32
     this.unlocode = unlocode;
@@ -21,12 +38,15 @@ public class Location {
21 38
 
22 39
   @Override
23 40
   public boolean equals(Object obj) {
24
-    return EqualsBuilder.reflectionEquals(this, obj);
41
+    if (this == NULL || obj == NULL) {
42
+      return this == obj;
43
+    }
44
+    return EqualsBuilder.reflectionEquals(this, obj, excludedFields);
25 45
   }
26 46
 
27 47
   @Override
28 48
   public int hashCode() {
29
-    return HashCodeBuilder.reflectionHashCode(this);
49
+    return HashCodeBuilder.reflectionHashCode(this, excludedFields);
30 50
   }
31 51
 
32 52
   @Override

+ 11
- 4
dddsample/src/main/java/se/citerus/dddsample/domain/TrackingId.java Parādīt failu

@@ -1,19 +1,26 @@
1 1
 package se.citerus.dddsample.domain;
2 2
 
3
-import java.io.Serializable;
4
-
5 3
 import org.apache.commons.lang.builder.EqualsBuilder;
6 4
 import org.apache.commons.lang.builder.HashCodeBuilder;
7 5
 
6
+import javax.persistence.Column;
7
+import javax.persistence.Embeddable;
8
+import java.io.Serializable;
9
+
8 10
 /**
9 11
  * TrackingId is a simple ID wrapper which implements Serializable for easier
10
- * integration with persistens frameworks.
12
+ * integration with persistence frameworks.
11 13
  * 
12 14
  */
15
+@Embeddable
13 16
 public class TrackingId implements Serializable {
14 17
 
15 18
   private static final long serialVersionUID = 6273117599327914522L;
16
-  private final String id;
19
+
20
+  @Column(name = "tracking_id")
21
+  private String id;
22
+
23
+  TrackingId() {}
17 24
 
18 25
   public TrackingId(String id) {
19 26
     this.id = id;

+ 2
- 0
dddsample/src/main/java/se/citerus/dddsample/repository/CargoRepositoryHibernate.java Parādīt failu

@@ -1,9 +1,11 @@
1 1
 package se.citerus.dddsample.repository;
2 2
 
3 3
 import org.hibernate.SessionFactory;
4
+import org.springframework.stereotype.Repository;
4 5
 import se.citerus.dddsample.domain.Cargo;
5 6
 import se.citerus.dddsample.domain.TrackingId;
6 7
 
8
+@Repository
7 9
 public class CargoRepositoryHibernate implements CargoRepository {
8 10
 
9 11
   private SessionFactory sessionFactory;

+ 12
- 2
dddsample/src/main/resources/context-persistence.xml Parādīt failu

@@ -15,8 +15,17 @@
15 15
     <property name="password" value="${jdbc.password}"/>
16 16
   </bean>
17 17
 
18
-  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
18
+  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
19 19
     <property name="dataSource" ref="dataSource"/>
20
+    <property name="annotatedClasses">
21
+      <list>
22
+        <value>se.citerus.dddsample.domain.Cargo</value>
23
+        <value>se.citerus.dddsample.domain.Location</value>
24
+        <value>se.citerus.dddsample.domain.DeliveryHistory</value>
25
+        <value>se.citerus.dddsample.domain.HandlingEvent</value>
26
+        <value>se.citerus.dddsample.domain.CarrierMovement</value>
27
+      </list>
28
+    </property>
20 29
     <property name="hibernateProperties">
21 30
       <props>
22 31
         <!-- These are properties that are common for test and production -->
@@ -30,7 +39,8 @@
30 39
     <property name="sessionFactory" ref="sessionFactory"/>
31 40
   </bean>
32 41
 
33
-  <bean id="cargoRepository" class="se.citerus.dddsample.repository.CargoRepositoryInMem">
42
+  <bean id="cargoRepository" class="se.citerus.dddsample.repository.CargoRepositoryHibernate">
43
+    <property name="sessionFactory" ref="sessionFactory"/>
34 44
   </bean>
35 45
 
36 46
 </beans>

+ 16
- 1
dddsample/src/test/java/se/citerus/dddsample/domain/CargoRepositoryTest.java Parādīt failu

@@ -12,14 +12,29 @@ public class CargoRepositoryTest extends AbstractTransactionalDataSourceSpringCo
12 12
   }
13 13
 
14 14
   protected String[] getConfigLocations() {
15
-    return new String[] {"context-persistence.xml"};
15
+    return new String[]{"context-persistence.xml"};
16
+  }
17
+
18
+  protected void onSetUpInTransaction() throws Exception {
19
+    String[] testData = {
20
+            "INSERT INTO locations (id, unlocode) VALUES (1, 'SESTO')",
21
+            "INSERT INTO locations (id, unlocode) VALUES (2, 'CNHKG')",
22
+
23
+            "INSERT INTO cargo (tracking_id, origin_location_fk, final_destination_location_fk) " +
24
+                    "VALUES ('XYZ', 1, 2)"
25
+    };
26
+    jdbcTemplate.batchUpdate(testData);
16 27
   }
17 28
 
18 29
   public void testFindByCargoId() {
19 30
     final TrackingId trackingId = new TrackingId("XYZ");
31
+    final Location origin = new Location("SESTO");
32
+    final Location finalDestination = new Location("CNHKG");
20 33
     Cargo cargo = cargoRepository.find(trackingId);
21 34
 
22 35
     assertEquals(trackingId, cargo.trackingId());
36
+    assertEquals(origin, cargo.origin());
37
+    assertEquals(finalDestination, cargo.finalDestination());
23 38
   }
24 39
 
25 40
 }

+ 31
- 0
dddsample/src/test/java/se/citerus/dddsample/domain/LocationTest.java Parādīt failu

@@ -0,0 +1,31 @@
1
+package se.citerus.dddsample.domain;
2
+
3
+import junit.framework.TestCase;
4
+
5
+public class LocationTest extends TestCase {
6
+
7
+  public void testEquals() {
8
+    // Same location string - equal
9
+    assertTrue(new Location("TEST").equals(new Location("TEST")));
10
+
11
+    // Different location strings - not equal
12
+    assertFalse(new Location("TEST").equals(new Location("ANOTHER_TEST")));
13
+
14
+    // Always equal to itself
15
+    Location location = new Location("TEST");
16
+    assertTrue(location.equals(location));
17
+
18
+    // Never equal to null
19
+    assertFalse(location.equals(null));
20
+
21
+    // Special NULL location is equal to itself
22
+    assertTrue(Location.NULL.equals(Location.NULL));
23
+
24
+    // No other location should be equal to the NULL location
25
+    assertFalse(new Location(null).equals(Location.NULL));
26
+    assertFalse(new Location("").equals(Location.NULL));
27
+    assertFalse(new Location("   ").equals(Location.NULL));
28
+    assertFalse(new Location("   FOO  BAR ").equals(Location.NULL));
29
+  }
30
+
31
+}