diff --git a/pom.xml b/pom.xml index bcdf9aff..e6603b06 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ UTF-8 5.0.1.Final + 4.1.7.RELEASE 1.9.0.RELEASE 1.4.188 4.12 @@ -20,6 +21,7 @@ 1.4 2.15.3 1.2.17 + 18.0 abstract-factory @@ -101,6 +103,11 @@ ${hibernate.version} + org.springframework + spring-test + ${spring.version} + + org.springframework.data spring-data-jpa ${spring-data.version} @@ -142,6 +149,11 @@ log4j ${log4j.version} + + com.google.guava + guava + ${guava.version} + diff --git a/repository/etc/repository.png b/repository/etc/repository.png index 1e031f3a..08d5d571 100644 Binary files a/repository/etc/repository.png and b/repository/etc/repository.png differ diff --git a/repository/etc/repository.ucls b/repository/etc/repository.ucls index 6c42019e..894e9434 100644 --- a/repository/etc/repository.ucls +++ b/repository/etc/repository.ucls @@ -19,10 +19,48 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repository/index.md b/repository/index.md index a65044d2..8ecd1652 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 60b51568..c3adc7a9 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -1,35 +1,42 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.8.0-SNAPSHOT - - repository - - - org.springframework.data - spring-data-jpa - - - org.hibernate - hibernate-entitymanager - - - commons-dbcp - commons-dbcp - - - com.h2database - h2 - - - junit - junit - test - - + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.8.0-SNAPSHOT + + repository + + + org.springframework + spring-test + + + org.springframework.data + spring-data-jpa + + + org.hibernate + hibernate-entitymanager + + + commons-dbcp + commons-dbcp + + + com.h2database + h2 + + + junit + junit + test + + + com.google.guava + guava + + diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java index fb9680cb..2442c854 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 @@ *

* 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 97d5e712..57439b8c 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 167b40d1..98bb7abc 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 { +public interface PersonRepository + extends CrudRepository, JpaSpecificationExecutor { - public List 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 00000000..dadaae36 --- /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 { + + private int from; + + private int to; + + public AgeBetweenSpec(int from, int to) { + this.from = from; + this.to = to; + } + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + + return cb.between(root.get("age"), from, to); + + } + + } + public static class NameEqualSpec implements Specification { + + public String name; + + public NameEqualSpec(String name) { + this.name = name; + } + + public Predicate toPredicate(Root 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 3fe15b2f..9322c9f6 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"> - - + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> + + 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 929f6194..00000000 --- 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 00000000..26689321 --- /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 persons = Arrays.asList(peter, nasta, john, terry); + + /** + * Prepare data for test + */ + @Before + public void setup() { + + repository.save(persons); + } + + @Test + public void testFindAll() { + + List 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 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(); + } + +} +