Repository: iluwatar/java-design-patterns
Issue: Repository vs DAO
Labels: type: bug
info: help wanted
type: enhancement

I think that Repository pattern is not implemented in the correct way and it looks like DAO. Here is a good article about this http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/


Issue comments:
Hi @stephen-lazarionok and thanks for pointing out the interesting article. We would like to improve the example so that it would better distinguish Repository from DAO. I noticed that the class naming could be improved and maybe add a query method that takes criteria as a parameter like they have in the article. Can you pinpoint more improvements we should apply? PRs are welcome too, if you would rather work that way.

I can work on Repository pattern and improve it. The only thing that I do not have enough time as I took some patterns to implement and in addition to that I have real projects I've been working on. So it may take some weeks to implement all the stuff on me :) If it is okay then I will go ahead.

I did not review all the code I just took a look at the repository pattern implementation to find out what is a default DB is used and saw that issue :) That is why I raised it. Once I get some spare time I will check out the rest of the codebase.

I can work upon the udpates required for correcting the Repository pattern, as desired by @stephen-lazarionok. Let me know if that would be okay.

@shaileshkhose Sure go ahead. Your contribution is welcome.

The Repository Pattern is so awesome compare to the Dao Pattern, and this issue has nothing progress since Aug. 
I want to work on this issue, let me know if that would be okay.

Great @hoswey go ahead!


Pull request review comments:

Patch that resolved the issue:
diff --git a/pom.xml b/pom.xml
index bcdf9affe8f1..e6603b06c58e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<hibernate.version>5.0.1.Final</hibernate.version>
+		<spring.version>4.1.7.RELEASE</spring.version>
 		<spring-data.version>1.9.0.RELEASE</spring-data.version>
 		<h2.version>1.4.188</h2.version>
 		<junit.version>4.12</junit.version>
@@ -20,6 +21,7 @@
 		<commons-dbcp.version>1.4</commons-dbcp.version>
 		<camel.version>2.15.3</camel.version>
 		<log4j.version>1.2.17</log4j.version>
+ 		<guava.version>18.0</guava.version>
 	</properties>
 	<modules>
 		<module>abstract-factory</module>
@@ -101,6 +103,11 @@
 				<version>${hibernate.version}</version>
 			</dependency>
 			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-test</artifactId>
+				<version>${spring.version}</version>
+			</dependency>
+            		<dependency>
 				<groupId>org.springframework.data</groupId>
 				<artifactId>spring-data-jpa</artifactId>
 				<version>${spring-data.version}</version>
@@ -142,6 +149,11 @@
 				<artifactId>log4j</artifactId>
 				<version>${log4j.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>com.google.guava</groupId>
+				<artifactId>guava</artifactId>
+				<version>${guava.version}</version>
+            		</dependency>
 		</dependencies>
 	</dependencyManagement>
 
diff --git a/repository/etc/repository.ucls b/repository/etc/repository.ucls
index 6c42019e1bd5..894e9434cccc 100644
--- a/repository/etc/repository.ucls
+++ b/repository/etc/repository.ucls
@@ -19,10 +19,48 @@
       <operations public="true" package="true" protected="true" private="true" static="true"/>    
     </display>  
   </interface>  
-  <dependency id="3">    
+  <class id="3" language="java" name="com.iluwatar.repository.PersonSpecifications.AgeBetweenSpec" project="repository" 
+    file="/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java" binary="false" 
+    corner="BOTTOM_RIGHT">    
+    <position height="-1" width="-1" x="412" y="246"/>    
+    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
+      sort-features="false" accessors="true" visibility="true">      
+      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
+      <operations public="true" package="true" protected="true" private="true" static="true"/>    
+    </display>  
+  </class>  
+  <class id="4" language="java" name="com.iluwatar.repository.PersonSpecifications" project="repository" 
+    file="/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java" binary="false" 
+    corner="BOTTOM_RIGHT">    
+    <position height="-1" width="-1" x="627" y="68"/>    
+    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
+      sort-features="false" accessors="true" visibility="true">      
+      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
+      <operations public="true" package="true" protected="true" private="true" static="true"/>    
+    </display>  
+  </class>  
+  <class id="5" language="java" name="com.iluwatar.repository.PersonSpecifications.NameEqualSpec" project="repository" 
+    file="/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java" binary="false" 
+    corner="BOTTOM_RIGHT">    
+    <position height="-1" width="-1" x="834" y="238"/>    
+    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
+      sort-features="false" accessors="true" visibility="true">      
+      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
+      <operations public="true" package="true" protected="true" private="true" static="true"/>    
+    </display>  
+  </class>  
+  <nesting id="6">    
+    <end type="SOURCE" refId="4"/>    
+    <end type="TARGET" refId="3"/>  
+  </nesting>  
+  <dependency id="7">    
     <end type="SOURCE" refId="2"/>    
     <end type="TARGET" refId="1"/>  
   </dependency>  
+  <nesting id="8">    
+    <end type="SOURCE" refId="4"/>    
+    <end type="TARGET" refId="5"/>  
+  </nesting>  
   <classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
     sort-features="false" accessors="true" visibility="true">    
     <attributes public="true" package="true" protected="true" private="true" static="true"/>    
diff --git a/repository/index.md b/repository/index.md
index a65044d29637..8ecd16528cea 100644
--- a/repository/index.md
+++ b/repository/index.md
@@ -25,3 +25,9 @@ querying is utilized.
 **Real world examples:** 
 
 * [Spring Data](http://projects.spring.io/spring-data/)
+
+**Credits:**
+
+* [Don’t use DAO, use Repository](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/)
+* [Advanced Spring Data JPA - Specifications and Querydsl](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/)
+
diff --git a/repository/pom.xml b/repository/pom.xml
index 60b51568e08a..c3adc7a922c2 100644
--- a/repository/pom.xml
+++ b/repository/pom.xml
@@ -1,35 +1,42 @@
 <?xml version="1.0"?>
-<project
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
-	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>com.iluwatar</groupId>
-		<artifactId>java-design-patterns</artifactId>
-		<version>1.8.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>repository</artifactId>
-	<dependencies>
-		<dependency>
-			<groupId>org.springframework.data</groupId>
-			<artifactId>spring-data-jpa</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.hibernate</groupId>
-			<artifactId>hibernate-entitymanager</artifactId>
-		</dependency>
-		<dependency>
-	  		<groupId>commons-dbcp</groupId>
-	  		<artifactId>commons-dbcp</artifactId>
-	  	</dependency>		
-		<dependency>
-			<groupId>com.h2database</groupId>
-			<artifactId>h2</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<scope>test</scope>
-		</dependency>
-	</dependencies>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+    xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.iluwatar</groupId>
+        <artifactId>java-design-patterns</artifactId>
+        <version>1.8.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>repository</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-entitymanager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-dbcp</groupId>
+            <artifactId>commons-dbcp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java
index fb9680cb6207..2442c854cd48 100644
--- a/repository/src/main/java/com/iluwatar/repository/App.java
+++ b/repository/src/main/java/com/iluwatar/repository/App.java
@@ -5,7 +5,6 @@
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
 /**
- * 
  * Repository pattern mediates between the domain and data mapping layers using a collection-like
  * interface for accessing domain objects. A system with complex domain model often benefits from a
  * layer that isolates domain objects from the details of the database access code and in such
@@ -16,9 +15,9 @@
  * <p>
  * In this example we utilize Spring Data to automatically generate a repository for us from the
  * {@link Person} domain object. Using the {@link PersonRepository} we perform CRUD operations on
- * the entity. Underneath we have configured in-memory H2 database for which schema is created and
- * dropped on each run.
- *
+ * the entity, moreover, the query by {@link org.springframework.data.jpa.domain.Specification} are
+ * also performed. Underneath we have configured in-memory H2 database for which schema is created
+ * and dropped on each run.
  */
 public class App {
 
@@ -28,16 +27,21 @@ public class App {
    * @param args command line args
    */
   public static void main(String[] args) {
+
     ClassPathXmlApplicationContext context =
         new ClassPathXmlApplicationContext("applicationContext.xml");
     PersonRepository repository = context.getBean(PersonRepository.class);
 
-    Person peter = new Person("Peter", "Sagan");
-    Person nasta = new Person("Nasta", "Kuzminova");
+    Person peter = new Person("Peter", "Sagan", 17);
+    Person nasta = new Person("Nasta", "Kuzminova", 25);
+    Person john = new Person("John", "lawrence", 35);
+    Person terry = new Person("Terry", "Law", 36);
 
     // Add new Person records
     repository.save(peter);
     repository.save(nasta);
+    repository.save(john);
+    repository.save(terry);
 
     // Count Person records
     System.out.println("Count Person records: " + repository.count());
@@ -48,9 +52,6 @@ public static void main(String[] args) {
       System.out.println(person);
     }
 
-    // Find Person by surname
-    System.out.println("Find by surname 'Sagan': " + repository.findBySurname("Sagan"));
-
     // Update Person
     nasta.setName("Barbora");
     nasta.setSurname("Spotakova");
@@ -61,9 +62,22 @@ public static void main(String[] args) {
     // Remove record from Person
     repository.delete(2L);
 
-    // And finally count records
+    // count records
     System.out.println("Count Person records: " + repository.count());
 
+    // find by name
+    Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John"));
+    System.out.println("Find by John is " + p);
+
+    // find by age
+    persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40));
+
+    System.out.println("Find Person with age between 20,40: ");
+    for (Person person : persons) {
+      System.out.println(person);
+    }
+
     context.close();
+
   }
 }
diff --git a/repository/src/main/java/com/iluwatar/repository/Person.java b/repository/src/main/java/com/iluwatar/repository/Person.java
index 97d5e7120229..57439b8c0516 100644
--- a/repository/src/main/java/com/iluwatar/repository/Person.java
+++ b/repository/src/main/java/com/iluwatar/repository/Person.java
@@ -18,11 +18,14 @@ public class Person {
   private String name;
   private String surname;
 
+  private int age;
+
   public Person() {}
 
-  public Person(String name, String surname) {
+  public Person(String name, String surname, int age) {
     this.name = name;
     this.surname = surname;
+    this.age = age;
   }
 
   public Long getId() {
@@ -49,8 +52,59 @@ public void setSurname(String surname) {
     this.surname = surname;
   }
 
+
+  public int getAge() {
+    return age;
+  }
+
+  public void setAge(int age) {
+    this.age = age;
+  }
+
   @Override
   public String toString() {
-    return "Person [id=" + id + ", name=" + name + ", surname=" + surname + "]";
+    return "Person [id=" + id + ", name=" + name + ", surname=" + surname + ", age=" + age + "]";
+  }
+
+  @Override
+  public int hashCode() {
+
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + age;
+    result = prime * result + ((id == null) ? 0 : id.hashCode());
+    result = prime * result + ((name == null) ? 0 : name.hashCode());
+    result = prime * result + ((surname == null) ? 0 : surname.hashCode());
+    return result;
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    Person other = (Person) obj;
+    if (age != other.age)
+      return false;
+    if (id == null) {
+      if (other.id != null)
+        return false;
+    } else if (!id.equals(other.id))
+      return false;
+    if (name == null) {
+      if (other.name != null)
+        return false;
+    } else if (!name.equals(other.name))
+      return false;
+    if (surname == null) {
+      if (other.surname != null)
+        return false;
+    } else if (!surname.equals(other.surname))
+      return false;
+    return true;
+  }
+
 }
diff --git a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java
index 167b40d19545..98bb7abce02a 100644
--- a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java
+++ b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java
@@ -1,7 +1,6 @@
 package com.iluwatar.repository;
 
-import java.util.List;
-
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.stereotype.Repository;
 
@@ -11,7 +10,8 @@
  *
  */
 @Repository
-public interface PersonRepository extends CrudRepository<Person, Long> {
+public interface PersonRepository
+    extends CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> {
 
-  public List<Person> findBySurname(String surname);
+  public Person findByName(String name);
 }
diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java
new file mode 100644
index 000000000000..dadaae36b577
--- /dev/null
+++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java
@@ -0,0 +1,50 @@
+package com.iluwatar.repository;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * Helper class, includes vary Specification as the abstraction of sql query criteria
+ */
+public class PersonSpecifications {
+
+  public static class AgeBetweenSpec implements Specification<Person> {
+
+    private int from;
+
+    private int to;
+
+    public AgeBetweenSpec(int from, int to) {
+      this.from = from;
+      this.to = to;
+    }
+
+    @Override
+    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
+
+      return cb.between(root.get("age"), from, to);
+
+    }
+
+  }
+  public static class NameEqualSpec implements Specification<Person> {
+
+    public String name;
+
+    public NameEqualSpec(String name) {
+      this.name = name;
+    }
+
+    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
+
+      return cb.equal(root.get("name"), this.name);
+
+    }
+  }
+
+}
+
diff --git a/repository/src/main/resources/applicationContext.xml b/repository/src/main/resources/applicationContext.xml
index 3fe15b2f6269..9322c9f64c63 100644
--- a/repository/src/main/resources/applicationContext.xml
+++ b/repository/src/main/resources/applicationContext.xml
@@ -6,10 +6,10 @@
 	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
 	xsi:schemaLocation="
 				http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
-				http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">
-
-	<jpa:repositories base-package="com.iluwatar" />
+				http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
 
+	<jpa:repositories base-package="com.iluwatar"  base-class="org.springframework.data.jpa.repository.support.SimpleJpaRepository" />
+ 
 	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
 		<property name="entityManagerFactory" ref="entityManagerFactory" />
 	</bean>


Test patch:
diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/repository/src/test/java/com/iluwatar/repository/AppTest.java
deleted file mode 100644
index 929f619414e4..000000000000
--- a/repository/src/test/java/com/iluwatar/repository/AppTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.iluwatar.repository;
-
-import org.junit.Test;
-
-import com.iluwatar.repository.App;
-
-/**
- * 
- * Application test
- *
- */
-public class AppTest {
-
-  @Test
-  public void test() {
-    String[] args = {};
-    App.main(args);
-  }
-}
diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java
new file mode 100644
index 000000000000..26689321a395
--- /dev/null
+++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java
@@ -0,0 +1,109 @@
+package com.iluwatar.repository;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Test case to test the functions of {@link PersonRepository}, beside the CRUD functions, the query
+ * by {@link org.springframework.data.jpa.domain.Specification} are also test.
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
+public class RepositoryTest {
+
+  @Resource
+  private PersonRepository repository;
+
+  Person peter = new Person("Peter", "Sagan", 17);
+  Person nasta = new Person("Nasta", "Kuzminova", 25);
+  Person john = new Person("John", "lawrence", 35);
+  Person terry = new Person("Terry", "Law", 36);
+
+  List<Person> persons = Arrays.asList(peter, nasta, john, terry);
+
+  /**
+   * Prepare data for test
+   */
+  @Before
+  public void setup() {
+
+    repository.save(persons);
+  }
+
+  @Test
+  public void testFindAll() {
+
+    List<Person> actuals = Lists.newArrayList(repository.findAll());
+    assertTrue(actuals.containsAll(persons) && persons.containsAll(actuals));
+  }
+
+  @Test
+  public void testSave() {
+
+    Person terry = repository.findByName("Terry");
+    terry.setSurname("Lee");
+    terry.setAge(47);
+    repository.save(terry);
+
+    terry = repository.findByName("Terry");
+    assertEquals(terry.getSurname(), "Lee");
+    assertEquals(47, terry.getAge());
+  }
+
+  @Test
+  public void testDelete() {
+
+    Person terry = repository.findByName("Terry");
+    repository.delete(terry);
+
+    assertEquals(3, repository.count());
+    assertNull(repository.findByName("Terry"));
+  }
+
+  @Test
+  public void testCount() {
+
+    assertEquals(4, repository.count());
+  }
+
+  @Test
+  public void testFindAllByAgeBetweenSpec() {
+
+    List<Person> persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40));
+
+    assertEquals(3, persons.size());
+    assertTrue(persons.stream().allMatch((item) -> {
+      return item.getAge() > 20 && item.getAge() < 40;
+    }));
+  }
+
+  @Test
+  public void testFindOneByNameEqualSpec() {
+
+    Person actual = repository.findOne(new PersonSpecifications.NameEqualSpec("Terry"));
+    assertEquals(terry, actual);
+  }
+
+  @After
+  public void cleanup() {
+
+    repository.deleteAll();
+  }
+
+}
+