├── .gitignore
├── src
├── main
│ ├── resources
│ │ ├── application.properties
│ │ └── logback.xml
│ └── java
│ │ └── de
│ │ └── olivergierke
│ │ └── deepdive
│ │ ├── ProductRepositoryCustom.java
│ │ ├── ApplicationConfig.java
│ │ ├── ProductRepositoryImpl.java
│ │ ├── ProductRepository.java
│ │ ├── CustomerRepository.java
│ │ ├── AbstractEntity.java
│ │ ├── Address.java
│ │ ├── ReadOnlyRepository.java
│ │ ├── EmailAddress.java
│ │ ├── Customer.java
│ │ └── Product.java
└── test
│ ├── resources
│ └── data.sql
│ └── java
│ └── de
│ └── olivergierke
│ └── deepdive
│ ├── DatabasePopulationIntegrationTest.java
│ ├── ApplicationTest.java
│ ├── AbstractIntegrationTest.java
│ ├── CustomerRepositoryTransactionReconfigurationIntegrationTest.java
│ ├── ProductRepositoryIntegrationTest.java
│ └── CustomerRepositoryIntegrationTest.java
├── test.sh
├── pom.xml
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .settings/
3 | .project
4 | .classpath
5 | .springBeans
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.jpa.hibernate.ddl_auto=update
2 | spring.datasource.continueOnError=true
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d %5p %50.50c:%4L - %m%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | COMMITS=$(git log --oneline --reverse | tail -n+2 | cut -d " " -f 1)
3 | CODE=0
4 |
5 | git reset --hard
6 |
7 | for COMMIT in $COMMITS
8 | do
9 | git checkout $COMMIT
10 |
11 | # run-tests
12 | mvn clean test
13 |
14 | if [ $? -eq 0 ]
15 | then
16 | echo $COMMIT - passed
17 | else
18 | echo $COMMIT - failed
19 | exit
20 | fi
21 |
22 | git reset --hard
23 | done
24 |
25 | git checkout master
--------------------------------------------------------------------------------
/src/test/resources/data.sql:
--------------------------------------------------------------------------------
1 | insert into Customer (id, email, firstname, lastname) values (1, 'dave@dmband.com', 'Dave', 'Matthews');
2 | insert into Customer (id, email, firstname, lastname) values (2, 'carter@dmband.com', 'Carter', 'Beauford');
3 | insert into Customer (id, email, firstname, lastname) values (3, 'boyd@dmband.com', 'Boyd', 'Tinsley');
4 |
5 | insert into Address (id, street, city, country, customer_id) values (1, '27 Broadway', 'New York', 'United States', 1);
6 | insert into Address (id, street, city, country, customer_id) values (2, '27 Broadway', 'New York', 'United States', 1);
7 |
8 | insert into Product (id, name, description, price) values (1, 'iPad', 'Apple tablet device', 499.0);
9 | insert into Product (id, name, description, price) values (2, 'MacBook Pro', 'Apple notebook', 1299.0);
10 | insert into Product (id, name, description, price) values (3, 'Dock', 'Dock for iPhone/iPad', 49.0);
11 |
12 | insert into Product_Attributes (attributes_key, product_id, attributes) values ('connector', 1, 'socket');
13 | insert into Product_Attributes (attributes_key, product_id, attributes) values ('connector', 3, 'plug');
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/DatabasePopulationIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import org.junit.Test;
19 |
20 | /**
21 | * Dummy test class to make sure the setup method of the superclass executes correctly.
22 | *
23 | * @author Oliver Gierke
24 | * @since Step 1
25 | */
26 | public class DatabasePopulationIntegrationTest extends AbstractIntegrationTest {
27 |
28 | @Test
29 | public void populatesDatabaseCorrectly() {
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/ProductRepositoryCustom.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.math.BigDecimal;
19 |
20 | /**
21 | * Interface for data access code to be implemented manually.
22 | *
23 | * @author Oliver Gierke
24 | */
25 | interface ProductRepositoryCustom {
26 |
27 | /**
28 | * Removes all {@link Product}s with a price greater than the given one.
29 | *
30 | * @param price
31 | */
32 | void removeProductsMoreExpensiveThan(BigDecimal price);
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/ApplicationConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2013 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
19 | import org.springframework.context.annotation.ComponentScan;
20 | import org.springframework.context.annotation.Configuration;
21 |
22 | /**
23 | * Application configuration.
24 | *
25 | * @author Oliver Gierke
26 | * @since Step 1
27 | */
28 | @Configuration
29 | @EnableAutoConfiguration
30 | @ComponentScan
31 | class ApplicationConfig {
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import org.junit.Test;
19 | import org.springframework.context.annotation.AnnotationConfigApplicationContext;
20 |
21 | /**
22 | * Sample test case bootstrapping the application.
23 | *
24 | * @author Oliver Gierke
25 | * @since Step 1
26 | */
27 | public class ApplicationTest {
28 |
29 | @Test
30 | public void bootstrapsApplication() {
31 | new AnnotationConfigApplicationContext(ApplicationConfig.class).close();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/AbstractIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import org.junit.runner.RunWith;
19 | import org.springframework.boot.test.SpringApplicationConfiguration;
20 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
21 | import org.springframework.transaction.annotation.Transactional;
22 |
23 | /**
24 | * Abstract integration test to populate the database with dummy data.
25 | *
26 | * @author Oliver Gierke
27 | * @since Step 1
28 | */
29 | @RunWith(SpringJUnit4ClassRunner.class)
30 | @Transactional
31 | @SpringApplicationConfiguration(classes = ApplicationConfig.class)
32 | public abstract class AbstractIntegrationTest {
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/ProductRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.math.BigDecimal;
19 |
20 | import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport;
21 |
22 | /**
23 | * Custom implementation class to implement {@link ProductRepositoryCustom}. Using the Querydsl repository base class.
24 | *
25 | * @author Oliver Gierke
26 | */
27 | public class ProductRepositoryImpl extends QueryDslRepositorySupport implements ProductRepositoryCustom {
28 |
29 | private static final QProduct product = QProduct.product;
30 |
31 | /**
32 | * Creates a new instance of {@link ProductRepositoryImpl}.
33 | */
34 | public ProductRepositoryImpl() {
35 | super(Product.class);
36 | }
37 |
38 | /*
39 | * (non-Javadoc)
40 | * @see de.olivergierke.deepdive.ProductRepositoryCustom#removeProductsMoreExpensiveThan(java.math.BigDecimal)
41 | */
42 | @Override
43 | public void removeProductsMoreExpensiveThan(BigDecimal price) {
44 | delete(product).where(product.price.gt(price)).execute();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/ProductRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.util.List;
19 |
20 | import org.springframework.data.domain.Page;
21 | import org.springframework.data.domain.Pageable;
22 | import org.springframework.data.jpa.repository.Query;
23 |
24 | /**
25 | * Repository interface to access {@link Product}s.
26 | *
27 | * @author Oliver Gierke
28 | */
29 | public interface ProductRepository extends ReadOnlyRepository, ProductRepositoryCustom {
30 |
31 | /**
32 | * Returns a {@link Page} of {@link Product}s having a description which contains the given snippet.
33 | *
34 | * @param description
35 | * @param pageable
36 | * @return
37 | */
38 | Page findByDescriptionContaining(String description, Pageable pageable);
39 |
40 | /**
41 | * Returns all {@link Product}s having the given attribute.
42 | *
43 | * @param attribute
44 | * @return
45 | */
46 | @Query("select p from Product p where p.attributes[?1] = ?2")
47 | List findByAttributeAndValue(String attribute, String value);
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.util.List;
19 |
20 | import org.springframework.data.querydsl.QueryDslPredicateExecutor;
21 | import org.springframework.data.repository.PagingAndSortingRepository;
22 | import org.springframework.data.repository.Repository;
23 | import org.springframework.transaction.annotation.Transactional;
24 |
25 | /**
26 | * {@link Repository} to access {@link Customer} instances.
27 | *
28 | * @author Oliver Gierke
29 | * @since Step 2
30 | */
31 | public interface CustomerRepository extends PagingAndSortingRepository,
32 | QueryDslPredicateExecutor {
33 |
34 | /*
35 | * (non-Javadoc)
36 | * @see org.springframework.data.repository.CrudRepository#findAll()
37 | */
38 | List findAll();
39 |
40 | /*
41 | * (non-Javadoc)
42 | * @see org.springframework.data.repository.CrudRepository#save(S)
43 | */
44 | @Transactional(timeout = 10)
45 | S save(S entity);
46 |
47 | /**
48 | * Returns the customer with the given {@link EmailAddress}.
49 | *
50 | * @param emailAddress the {@link EmailAddress} to search for.
51 | * @since Step 2
52 | * @return
53 | */
54 | Customer findByEmailAddress(EmailAddress emailAddress);
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/AbstractEntity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import javax.persistence.GeneratedValue;
19 | import javax.persistence.GenerationType;
20 | import javax.persistence.Id;
21 | import javax.persistence.MappedSuperclass;
22 |
23 | /**
24 | * Base class to derive entity classes from.
25 | *
26 | * @author Oliver Gierke
27 | */
28 | @MappedSuperclass
29 | public class AbstractEntity {
30 |
31 | @Id
32 | @GeneratedValue(strategy = GenerationType.AUTO)
33 | private Long id;
34 |
35 | /**
36 | * Returns the identifier of the entity.
37 | *
38 | * @return the id
39 | */
40 | public Long getId() {
41 | return id;
42 | }
43 |
44 | /*
45 | * (non-Javadoc)
46 | * @see java.lang.Object#equals(java.lang.Object)
47 | */
48 | @Override
49 | public boolean equals(Object obj) {
50 |
51 | if (this == obj) {
52 | return true;
53 | }
54 |
55 | if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
56 | return false;
57 | }
58 |
59 | AbstractEntity that = (AbstractEntity) obj;
60 |
61 | return this.id.equals(that.getId());
62 | }
63 |
64 | /*
65 | * (non-Javadoc)
66 | * @see java.lang.Object#hashCode()
67 | */
68 | @Override
69 | public int hashCode() {
70 | return id == null ? 0 : id.hashCode();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/CustomerRepositoryTransactionReconfigurationIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import static org.hamcrest.Matchers.*;
19 | import static org.junit.Assert.*;
20 |
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 | import org.springframework.beans.factory.annotation.Autowired;
24 | import org.springframework.boot.test.SpringApplicationConfiguration;
25 | import org.springframework.test.annotation.DirtiesContext;
26 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
27 |
28 | /**
29 | * Integration test to show customized transaction configuration in {@link CustomerRepository}.
30 | *
31 | * @since Step 5.2
32 | * @author Oliver Gierke
33 | */
34 | @RunWith(SpringJUnit4ClassRunner.class)
35 | @SpringApplicationConfiguration(classes = ApplicationConfig.class)
36 | @DirtiesContext
37 | public class CustomerRepositoryTransactionReconfigurationIntegrationTest {
38 |
39 | @Autowired CustomerRepository repository;
40 |
41 | /**
42 | * @since Step 5.2
43 | */
44 | @Test
45 | public void executesRedeclaredMethodWithCustomTransactionConfiguration() {
46 |
47 | Customer customer = new Customer("Dave", "Matthews");
48 | Customer result = repository.save(customer);
49 |
50 | assertThat(result, is(notNullValue()));
51 | assertThat(result.getId(), is(notNullValue()));
52 | assertThat(result.getFirstname(), is("Dave"));
53 | assertThat(result.getLastname(), is("Matthews"));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/Address.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import javax.persistence.Entity;
19 |
20 | import org.springframework.util.Assert;
21 |
22 | /**
23 | * An address.
24 | *
25 | * @author Oliver Gierke
26 | */
27 | @Entity
28 | public class Address extends AbstractEntity {
29 |
30 | private String street, city, country;
31 |
32 | /**
33 | * Creates a new {@link Address} from the given street, city and country.
34 | *
35 | * @param street must not be {@literal null} or empty.
36 | * @param city must not be {@literal null} or empty.
37 | * @param country must not be {@literal null} or empty.
38 | */
39 | public Address(String street, String city, String country) {
40 |
41 | Assert.hasText(street, "Street must not be null or empty!");
42 | Assert.hasText(city, "City must not be null or empty!");
43 | Assert.hasText(country, "Country must not be null or empty!");
44 |
45 | this.street = street;
46 | this.city = city;
47 | this.country = country;
48 | }
49 |
50 | protected Address() {
51 |
52 | }
53 |
54 | /**
55 | * Returns a copy of the current {@link Address} instance which is a new entity in terms of persistence.
56 | *
57 | * @return
58 | */
59 | public Address getCopy() {
60 | return new Address(this.street, this.city, this.country);
61 | }
62 |
63 | /**
64 | * Returns the street.
65 | *
66 | * @return
67 | */
68 | public String getStreet() {
69 | return street;
70 | }
71 |
72 | /**
73 | * Returns the city.
74 | *
75 | * @return
76 | */
77 | public String getCity() {
78 | return city;
79 | }
80 |
81 | /**
82 | * Returns the country.
83 | *
84 | * @return
85 | */
86 | public String getCountry() {
87 | return country;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | de.olivergierke.deepdive
6 | repositories-deepdive
7 | 1.0.0.BUILD-SNAPSHOT
8 | Spring Data JPA repositories deep dive
9 |
10 |
11 | org.springframework.boot
12 | spring-boot-starter-parent
13 | 1.1.5.RELEASE
14 |
15 |
16 |
17 | 3.4.2
18 |
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-data-jpa
25 |
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-test
30 |
31 |
32 |
33 | org.hsqldb
34 | hsqldb
35 | runtime
36 |
37 |
38 |
39 | com.mysema.querydsl
40 | querydsl-jpa
41 | ${querydsl.version}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-compiler-plugin
51 |
52 | 1.8
53 | 1.8
54 |
55 |
56 |
57 | com.mysema.maven
58 | apt-maven-plugin
59 | 1.1.2
60 |
61 | com.mysema.query.apt.jpa.JPAAnnotationProcessor
62 |
63 |
64 |
65 | com.mysema.querydsl
66 | querydsl-apt
67 | ${querydsl.version}
68 |
69 |
70 |
71 |
72 | generate-sources
73 |
74 | process
75 |
76 |
77 | target/generated-sources/annotations
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/ReadOnlyRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.util.List;
19 | import java.util.Optional;
20 |
21 | import org.springframework.data.domain.Page;
22 | import org.springframework.data.domain.Pageable;
23 | import org.springframework.data.domain.Sort;
24 | import org.springframework.data.repository.NoRepositoryBean;
25 | import org.springframework.data.repository.Repository;
26 |
27 | /**
28 | * Base interface to use for Spring Data repositories that only expose read-only methods.
29 | *
30 | * @author Oliver Gierke
31 | */
32 | @NoRepositoryBean
33 | public interface ReadOnlyRepository extends Repository {
34 |
35 | /**
36 | * Retrives an entity by its id.
37 | *
38 | * @param id must not be {@literal null}.
39 | * @return the entity with the given id or {@literal null} if none found
40 | * @throws IllegalArgumentException if {@code id} is {@literal null}
41 | */
42 | Optional findOne(Long id);
43 |
44 | /**
45 | * Returns whether an entity with the given id exists.
46 | *
47 | * @param id must not be {@literal null}.
48 | * @return true if an entity with the given id exists, alse otherwise
49 | * @throws IllegalArgumentException if {@code id} is {@literal null}
50 | */
51 | boolean exists(Long id);
52 |
53 | /**
54 | * Returns all instances of the type.
55 | *
56 | * @return all entities
57 | */
58 | List findAll();
59 |
60 | /**
61 | * Returns all instances of the type with the given IDs.
62 | *
63 | * @param ids
64 | * @return
65 | */
66 | List findAll(Iterable ids);
67 |
68 | /**
69 | * Returns the number of entities available.
70 | *
71 | * @return the number of entities
72 | */
73 | long count();
74 |
75 | /**
76 | * Returns all entities sorted by the given options.
77 | *
78 | * @param sort
79 | * @return all entities sorted by the given options
80 | */
81 | List findAll(Sort sort);
82 |
83 | /**
84 | * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
85 | *
86 | * @param pageable
87 | * @return a page of entities
88 | */
89 | Page findAll(Pageable pageable);
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/EmailAddress.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.util.regex.Pattern;
19 |
20 | import javax.persistence.Column;
21 | import javax.persistence.Embeddable;
22 |
23 | import org.springframework.util.Assert;
24 |
25 | /**
26 | * A value object abstraction of an email address.
27 | *
28 | * @author Oliver Gierke
29 | */
30 | @Embeddable
31 | public class EmailAddress {
32 |
33 | private static final String EMAIL_REGEX = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
34 | private static final Pattern PATTERN = Pattern.compile(EMAIL_REGEX);
35 |
36 | @Column(name = "email")
37 | private String value;
38 |
39 | /**
40 | * Creates a new {@link EmailAddress} from the given string source.
41 | *
42 | * @param emailAddress must not be {@literal null} or empty.
43 | */
44 | public EmailAddress(String emailAddress) {
45 | Assert.isTrue(isValid(emailAddress), "Invalid email address!");
46 | this.value = emailAddress;
47 | }
48 |
49 | protected EmailAddress() {
50 |
51 | }
52 |
53 | /**
54 | * Returns whether the given {@link String} is a valid {@link EmailAddress} which means you can safely instantiate the
55 | * class.
56 | *
57 | * @param candidate
58 | * @return
59 | */
60 | public static boolean isValid(String candidate) {
61 | return candidate == null ? false : PATTERN.matcher(candidate).matches();
62 | }
63 |
64 | /*
65 | * (non-Javadoc)
66 | * @see java.lang.Object#toString()
67 | */
68 | @Override
69 | public String toString() {
70 | return value;
71 | }
72 |
73 | /*
74 | * (non-Javadoc)
75 | * @see java.lang.Object#equals(java.lang.Object)
76 | */
77 | @Override
78 | public boolean equals(Object obj) {
79 |
80 | if (this == obj) {
81 | return true;
82 | }
83 |
84 | if (!(obj instanceof EmailAddress)) {
85 | return false;
86 | }
87 |
88 | EmailAddress that = (EmailAddress) obj;
89 | return this.value.equals(that.value);
90 | }
91 |
92 | /*
93 | * (non-Javadoc)
94 | * @see java.lang.Object#hashCode()
95 | */
96 | @Override
97 | public int hashCode() {
98 | return value.hashCode();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/ProductRepositoryIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import static org.hamcrest.Matchers.*;
19 | import static org.junit.Assert.*;
20 |
21 | import java.math.BigDecimal;
22 | import java.util.List;
23 | import java.util.Optional;
24 |
25 | import org.hamcrest.Matchers;
26 | import org.junit.Test;
27 | import org.springframework.beans.factory.annotation.Autowired;
28 | import org.springframework.data.domain.Page;
29 | import org.springframework.data.domain.PageRequest;
30 |
31 | /**
32 | * Integration tests for {@link ProductRepository}.
33 | *
34 | * @author Oliver Gierke
35 | * @since Step 6
36 | */
37 | public class ProductRepositoryIntegrationTest extends AbstractIntegrationTest {
38 |
39 | @Autowired ProductRepository repository;
40 |
41 | /**
42 | * @since Step 6.1
43 | */
44 | @Test
45 | public void findsAllProducts() {
46 |
47 | List products = repository.findAll();
48 | assertThat(products, hasSize(3));
49 | }
50 |
51 | /**
52 | * @since Step 6.2
53 | */
54 | @Test
55 | public void findsAllAppleProductPaged() {
56 |
57 | Page products = repository.findByDescriptionContaining("Apple", new PageRequest(0, 1));
58 |
59 | assertThat(products.isFirst(), is(true));
60 | assertThat(products.hasNext(), is(true));
61 | assertThat(products, Matchers. hasItem(hasProperty("name", is("iPad"))));
62 | assertThat(products, not(Matchers. hasItem(hasProperty("name", is("Dock")))));
63 | }
64 |
65 | /**
66 | * @since Step 6.3
67 | */
68 | @Test
69 | public void returnsOptionalEmptyForNonExistingProduct() {
70 |
71 | Optional result = repository.findOne(4711L);
72 | assertThat(result, is(Optional.empty()));
73 | }
74 |
75 | /**
76 | * @since Step 7
77 | */
78 | @Test
79 | public void executesManuallyDeclaredQuery() {
80 |
81 | List products = repository.findByAttributeAndValue("connector", "plug");
82 |
83 | assertThat(products, Matchers. hasItem(hasProperty("name", is("Dock"))));
84 | }
85 |
86 | /**
87 | * @since Step 9
88 | */
89 | @Test
90 | public void executesCustomlyImplementedMethod() {
91 |
92 | repository.removeProductsMoreExpensiveThan(new BigDecimal(500));
93 |
94 | List result = repository.findAll();
95 |
96 | assertThat(result, hasSize(2));
97 | assertThat(result, not(Matchers. hasItem(hasProperty("name", is("Mac Book Pro")))));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/Customer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.util.Collections;
19 | import java.util.HashSet;
20 | import java.util.Set;
21 |
22 | import javax.persistence.CascadeType;
23 | import javax.persistence.Column;
24 | import javax.persistence.Entity;
25 | import javax.persistence.JoinColumn;
26 | import javax.persistence.OneToMany;
27 |
28 | import org.springframework.util.Assert;
29 |
30 | /**
31 | * A customer.
32 | *
33 | * @author Oliver Gierke
34 | */
35 | @Entity
36 | public class Customer extends AbstractEntity {
37 |
38 | private String firstname, lastname;
39 |
40 | @Column(unique = true)
41 | private EmailAddress emailAddress;
42 |
43 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
44 | @JoinColumn(name = "customer_id")
45 | private Set addresses = new HashSet();
46 |
47 | /**
48 | * Creates a new {@link Customer} from the given firstname and lastname.
49 | *
50 | * @param firstname must not be {@literal null} or empty.
51 | * @param lastname must not be {@literal null} or empty.
52 | */
53 | public Customer(String firstname, String lastname) {
54 |
55 | Assert.hasText(firstname);
56 | Assert.hasText(lastname);
57 |
58 | this.firstname = firstname;
59 | this.lastname = lastname;
60 | }
61 |
62 | protected Customer() {
63 |
64 | }
65 |
66 | /**
67 | * Adds the given {@link Address} to the {@link Customer}.
68 | *
69 | * @param address must not be {@literal null}.
70 | */
71 | public void add(Address address) {
72 |
73 | Assert.notNull(address);
74 | this.addresses.add(address);
75 | }
76 |
77 | /**
78 | * Returns the firstname of the {@link Customer}.
79 | *
80 | * @return
81 | */
82 | public String getFirstname() {
83 | return firstname;
84 | }
85 |
86 | /**
87 | * Returns the lastname of the {@link Customer}.
88 | *
89 | * @return
90 | */
91 | public String getLastname() {
92 | return lastname;
93 | }
94 |
95 | /**
96 | * Sets the lastname of the {@link Customer}.
97 | *
98 | * @param lastname
99 | */
100 | public void setLastname(String lastname) {
101 | this.lastname = lastname;
102 | }
103 |
104 | /**
105 | * Returns the {@link EmailAddress} of the {@link Customer}.
106 | *
107 | * @return
108 | */
109 | public EmailAddress getEmailAddress() {
110 | return emailAddress;
111 | }
112 |
113 | /**
114 | * Sets the {@link Customer}'s {@link EmailAddress}.
115 | *
116 | * @param emailAddress must not be {@literal null}.
117 | */
118 | public void setEmailAddress(EmailAddress emailAddress) {
119 | this.emailAddress = emailAddress;
120 | }
121 |
122 | /**
123 | * Return the {@link Customer}'s addresses.
124 | *
125 | * @return
126 | */
127 | public Set getAddresses() {
128 | return Collections.unmodifiableSet(addresses);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/de/olivergierke/deepdive/Product.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import java.math.BigDecimal;
19 | import java.util.Collections;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | import javax.persistence.Column;
24 | import javax.persistence.ElementCollection;
25 | import javax.persistence.Entity;
26 |
27 | import org.springframework.util.Assert;
28 |
29 | /**
30 | * A product.
31 | *
32 | * @author Oliver Gierke
33 | */
34 | @Entity
35 | public class Product extends AbstractEntity {
36 |
37 | @Column(nullable = false)
38 | private String name;
39 | private String description;
40 |
41 | @Column(nullable = false)
42 | private BigDecimal price;
43 |
44 | @ElementCollection
45 | private Map attributes = new HashMap();
46 |
47 | /**
48 | * Creates a new {@link Product} with the given name.
49 | *
50 | * @param name must not be {@literal null} or empty.
51 | * @param price must not be {@literal null} or less than or equal to zero.
52 | */
53 | public Product(String name, BigDecimal price) {
54 | this(name, price, null);
55 | }
56 |
57 | /**
58 | * Creates a new {@link Product} from the given name and description.
59 | *
60 | * @param name must not be {@literal null} or empty.
61 | * @param price must not be {@literal null} or less than or equal to zero.
62 | * @param description
63 | */
64 | public Product(String name, BigDecimal price, String description) {
65 |
66 | Assert.hasText(name, "Name must not be null or empty!");
67 | Assert.isTrue(BigDecimal.ZERO.compareTo(price) < 0, "Price must be greater than zero!");
68 |
69 | this.name = name;
70 | this.price = price;
71 | this.description = description;
72 | }
73 |
74 | protected Product() {
75 |
76 | }
77 |
78 | /**
79 | * Sets the attribute with the given name to the given value.
80 | *
81 | * @param name must not be {@literal null} or empty.
82 | * @param value
83 | */
84 | public void setAttribute(String name, String value) {
85 |
86 | Assert.hasText(name);
87 |
88 | if (value == null) {
89 | this.attributes.remove(value);
90 | } else {
91 | this.attributes.put(name, value);
92 | }
93 | }
94 |
95 | /**
96 | * Returns the {@link Product}'s name.
97 | *
98 | * @return
99 | */
100 | public String getName() {
101 | return name;
102 | }
103 |
104 | /**
105 | * Returns the {@link Product}'s description.
106 | *
107 | * @return
108 | */
109 | public String getDescription() {
110 | return description;
111 | }
112 |
113 | /**
114 | * Returns all the custom attributes of the {@link Product}.
115 | *
116 | * @return
117 | */
118 | public Map getAttributes() {
119 | return Collections.unmodifiableMap(attributes);
120 | }
121 |
122 | /**
123 | * Returns the price of the {@link Product}.
124 | *
125 | * @return
126 | */
127 | public BigDecimal getPrice() {
128 | return price;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/test/java/de/olivergierke/deepdive/CustomerRepositoryIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.olivergierke.deepdive;
17 |
18 | import static org.hamcrest.Matchers.*;
19 | import static org.junit.Assert.*;
20 |
21 | import java.util.List;
22 |
23 | import org.hamcrest.Matchers;
24 | import org.junit.Test;
25 | import org.springframework.beans.factory.annotation.Autowired;
26 | import org.springframework.data.domain.Page;
27 | import org.springframework.data.domain.PageRequest;
28 |
29 | import com.mysema.query.types.expr.BooleanExpression;
30 |
31 | /**
32 | * Integration tests for {@link CustomerRepository}.
33 | *
34 | * @author Oliver Gierke
35 | * @since Step 2
36 | */
37 | public class CustomerRepositoryIntegrationTest extends AbstractIntegrationTest {
38 |
39 | @Autowired CustomerRepository repository;
40 |
41 | /**
42 | * @since Step 2.1
43 | */
44 | @Test
45 | public void findsCustomerById() {
46 |
47 | Customer customer = repository.findOne(1L);
48 |
49 | assertThat(customer, is(notNullValue()));
50 | assertThat(customer.getFirstname(), is("Dave"));
51 | assertThat(customer.getLastname(), is("Matthews"));
52 | }
53 |
54 | /**
55 | * @since Step 2.2
56 | */
57 | @Test
58 | public void savesNewCustomer() {
59 |
60 | Customer stefan = new Customer("Stefan", "Lassard");
61 | Customer result = repository.save(stefan);
62 |
63 | assertThat(result, is(notNullValue()));
64 | assertThat(result.getId(), is(notNullValue()));
65 | assertThat(result.getFirstname(), is("Stefan"));
66 | assertThat(result.getLastname(), is("Lassard"));
67 | }
68 |
69 | /**
70 | * @since Step 2.3
71 | */
72 | @Test
73 | public void savesExistingCustomer() {
74 |
75 | Customer dave = repository.findOne(1L);
76 | dave.setEmailAddress(new EmailAddress("davematthews@dmband.com"));
77 | repository.save(dave);
78 |
79 | Customer result = repository.findOne(1L);
80 |
81 | assertThat(result, is(notNullValue()));
82 | assertThat(result.getId(), is(notNullValue()));
83 | assertThat(result.getFirstname(), is("Dave"));
84 | assertThat(result.getEmailAddress(), is(new EmailAddress("davematthews@dmband.com")));
85 | }
86 |
87 | /**
88 | * @since Step 2.4
89 | */
90 | @Test
91 | public void findsCustomersByEmailAddress() {
92 |
93 | Customer result = repository.findByEmailAddress(new EmailAddress("dave@dmband.com"));
94 |
95 | assertThat(result, is(notNullValue()));
96 | assertThat(result.getFirstname(), is("Dave"));
97 | assertThat(result.getLastname(), is("Matthews"));
98 | }
99 |
100 | /**
101 | * @since Step 3.1
102 | */
103 | @Test
104 | public void findsAllCustomers() {
105 |
106 | List customers = repository.findAll();
107 | assertThat(customers, hasSize(3));
108 | }
109 |
110 | /**
111 | * @since Step 3.2
112 | */
113 | @Test
114 | public void deletesCustomer() {
115 |
116 | repository.delete(1L);
117 | assertThat(repository.findOne(1L), is(nullValue()));
118 | }
119 |
120 | /**
121 | * @since Step 4.1
122 | */
123 | @Test
124 | public void accessesCustomersPageByPage() {
125 |
126 | Page result = repository.findAll(new PageRequest(1, 1));
127 |
128 | assertThat(result, is(notNullValue()));
129 | assertThat(result.isFirst(), is(false));
130 | assertThat(result.isLast(), is(false));
131 | assertThat(result.getNumberOfElements(), is(1));
132 | }
133 |
134 | /**
135 | * @since Step 8
136 | */
137 | @Test
138 | public void executesQuerydslPredicate() {
139 |
140 | Customer dave = repository.findByEmailAddress(new EmailAddress("dave@dmband.com"));
141 | Customer carter = repository.findByEmailAddress(new EmailAddress("carter@dmband.com"));
142 |
143 | QCustomer customer = QCustomer.customer;
144 |
145 | BooleanExpression firstnameStartsWithDa = customer.firstname.startsWith("Da");
146 | BooleanExpression lastnameContainsEau = customer.lastname.contains("eau");
147 |
148 | Iterable result = repository.findAll(firstnameStartsWithDa.or(lastnameContainsEau));
149 |
150 | assertThat(result, is(Matchers. iterableWithSize(2)));
151 | assertThat(result, hasItems(dave, carter));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Spring Data Repositories - A Deep Dive
2 |
3 | This repository contains the sample code for the _"Spring Data Repositories - A Deep Dive"_ presentation that demonstrates the feature of the Spring Data repositories abstraction in detail.
4 |
5 | The repository is not a continuous one in the classical sense. Every commit represents a step in the presentation and implements a new requirement as the code continues to grow.
6 |
7 | __Attention:__ Expect the repository to be rebased at any time as changes to the codebases have to be applied to the commit the relevant code was introduced.
8 |
9 | ## The individual steps in detail
10 |
11 | The domain model is a tiny CRM system in the first place but will be extended into an e-commerce system later on. Core abstractions are `Customer`s that have an `EmailAddress` as well as `Address`es, `Product`s and `Order`s carrying `LineItem`s placed by `Customers`.
12 |
13 | ### Step 0 - Basic project setup
14 |
15 | __Objective:__ Learn how to quickly create a new Maven based project using Spring Boot for easy dependency management and Spring auto configuration.
16 |
17 | > How to get a Spring Data JPA based project up and running quickly
18 |
19 | - Create `pom.xml`
20 | - `spring-boot-starter-parent` - defaults dependency versions
21 | - `spring-boot-starter-data-jpa` - declares default dependencies (Spring Data JPA, Hibernate 4, Spring 4)
22 | - Sample domain classes
23 |
24 | ### Step 1 - Basic JPA infrastructure setup
25 |
26 | __Objective:__ Learn in how far Boot simplifies the setup and the configuration of an `ApplicationContext`. Learn about JPA extensions introduced with Spring 3.1, and possibilities to populate a `DataSource`.
27 |
28 | > Persistence technology of choice is JPA. The application uses JavaConfig and sample data contained in data.sql
29 |
30 | - Add JavaConfig with `@EnableAutoConfiguration` to let Boot automatically configure an HSQL `DataSource`, a `LocalContainerEntityManagerFactoryBean` for Hibernate as well as a JPA transaction manager.
31 | - Add `application.properties` to tweak details of the auto-configuration
32 | - Add integration test to bootstrap application
33 | - Add integration base test to populate the database with `data.sql`
34 | - Explain ResourceDatabasePopulator
35 |
36 | ### Step 2 - Quickstart
37 |
38 | __Objective:__ Show basics of interface based programming model, query derivation and infrastructure setup.
39 |
40 | > The implementation of the persistence layer will be based on the Spring Data repositories abstraction. Customers can be saved, looked up by their id, email address.
41 |
42 | - Add `CustomerRepository extends Repository`
43 | - Add methods `findOne(…)`, `save(…)`, `findByEmailAddress(String emailAddress)`
44 | - `@EnableJpaRepositories` / ``
45 | - Add `CustomerRepositoryIntegrationTest` to execute repository methods
46 | - Show IDE integration
47 |
48 | ### Step 3 - Extended CRUD methods
49 |
50 | __Objective:__ Explain CRUD interface.
51 |
52 | > Customers can be deleted and obtained all at once
53 |
54 | - Move to `CustomerRepository extends CrudRepository`
55 | - Discuss consequences
56 | - *all* CRUD method exposed, flexibility on the client side
57 |
58 | ### Step 4 - Pagination
59 |
60 | __Objective:__ Explain PagingAndSortingRepository interface.
61 |
62 | > Customers can be accessed page by page.
63 |
64 | - Switch to `CustomerRepository extends PagingAndSortingRepository`
65 | - Discuss consequences
66 | - *even more* methods exposed, but maximum flexibility for clients
67 |
68 | ### Step 5 - Re-declaring existing CRUD methods
69 |
70 | __Objective:__ Show how users can tweak the configuration of CRUD methods or even alter return types if necessary.
71 |
72 | > `CustomerRepository.findAll()` should rather return a `List`. The transaction timeout for `save(…)` should be customized to 10 seconds.
73 |
74 | - Re-declare `findAll` and use `List` as return type
75 | - Re-declare `save(…)` and annotate with `@Transactional`
76 |
77 | ### Step 6 - Introducing a read-only repository base interface
78 |
79 | __Objective:__ Explain possibilities to define custom repository base interfaces.
80 |
81 | > Orders shall be accessible in read-only mode only.
82 |
83 | - Introduce `ReadOnlyRepository` base interface
84 | - Include reading methods of `CrudRepository` and `PagingAndSortingRepository`
85 | - Fix ID type to `Long`
86 | - Introduce `ProductRepository extends ReadOnlyRepository`
87 |
88 | ### Step 7 - Using manually defined queries
89 |
90 | __Objective:__ Learn how to manually define a query using the `@Query` annotation or named queries
91 |
92 | > As a user, I want to look up products by their custom attributes
93 |
94 | - Introduce the `@Query` annotation
95 | - Mention `jpa-named-queries.properties`
96 |
97 | ### Step 8 - Flexible predicate execution
98 |
99 | __Objective:__ Learn how to define atomic business predicates and execute them in flexible ways.
100 |
101 | > As a user, I want to search for customers by first name, last name, email address and any combination of them
102 |
103 | - Introduce Querydsl
104 | - Add Querydsl dependency and set up APT as well as IDE
105 | - Show generated query classes
106 | - Add `QuerydslPredicateExecutor`
107 | - Show usage in test case
108 |
109 | ### Step 9 - Custom implementation for repositories
110 |
111 | __Objective:__ Lear how to extend a repository with manually implemented code.
112 |
113 | > As an admin user, I'd like to delete all products beyond a given price.
114 |
115 | - Add `ProductRepositoryCustom` to declare custom method
116 | - Add custom implementation extending `QueryDslRepositorySupport`
117 |
--------------------------------------------------------------------------------