├── .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 | --------------------------------------------------------------------------------