├── HHH-13740 ├── README.md ├── hibernate-HHH-13740 │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── hibernate │ │ │ ├── HibernateUtil.java │ │ │ └── entity │ │ │ ├── Author.java │ │ │ ├── Book.java │ │ │ └── Category.java │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── hibernate │ │ └── BookFetchModeJoinWithSetTests.java └── spring-data-jpa-HHH-13740 │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── spring │ │ │ └── data │ │ │ └── jpa │ │ │ ├── JpaHibernateFetchingStrategiesApplication.java │ │ │ ├── entity │ │ │ ├── Author.java │ │ │ ├── Book.java │ │ │ └── Category.java │ │ │ └── repository │ │ │ ├── AuthorRepository.java │ │ │ ├── BookRepository.java │ │ │ └── CategoryRepository.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── example │ └── spring │ └── data │ └── jpa │ └── BookFetchModeJoinWithSetTests.java ├── LICENSE ├── README.md ├── img └── application-performance-database-related-problems.png ├── spring-data-jdbc-examples ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img │ ├── classes.png │ ├── classes.puml │ ├── tables.png │ └── tables.puml ├── settings.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── example │ │ └── spring │ │ └── data │ │ └── jdbc │ │ ├── SpringDataJdbcApplication.java │ │ ├── dto │ │ ├── AuthorDto.java │ │ ├── BookDto.java │ │ └── CategoryDto.java │ │ ├── entity │ │ ├── Author.java │ │ ├── Book.java │ │ ├── BookAuthor.java │ │ ├── BookCategory.java │ │ ├── BookRating.java │ │ └── Category.java │ │ ├── mapper │ │ ├── BookMapper.java │ │ └── BookMapperDecorator.java │ │ └── repository │ │ ├── AuthorRepository.java │ │ ├── BookRatingRepository.java │ │ ├── BookRepository.java │ │ └── CategoryRepository.java │ └── test │ ├── java │ └── com │ │ └── example │ │ └── spring │ │ └── data │ │ └── jdbc │ │ ├── AbstractContainerBaseTest.java │ │ ├── BookRatingRepositoryTest.java │ │ ├── BookRepositoryTest.java │ │ └── ProxyDataSourceConfig.java │ └── resources │ ├── application.properties │ └── schema.sql ├── spring-data-jpa-examples ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img │ ├── classes.png │ ├── classes.puml │ ├── classes_simplified.png │ ├── classes_simplified.puml │ ├── tables.png │ └── tables.puml ├── settings.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── example │ │ └── spring │ │ └── data │ │ └── jpa │ │ ├── SpringDataJpaApplication.java │ │ ├── dto │ │ ├── AuthorDto.java │ │ ├── BookDto.java │ │ └── CategoryDto.java │ │ ├── entity │ │ ├── AbstractBook.java │ │ ├── Author.java │ │ ├── Book.java │ │ ├── BookRating.java │ │ ├── BookWithBatchSize.java │ │ ├── BookWithFetchModeJoin.java │ │ ├── BookWithFetchModeSelect.java │ │ ├── BookWithFetchModeSubselect.java │ │ ├── BookWithMultipleBags.java │ │ └── Category.java │ │ ├── mapper │ │ └── BookMapper.java │ │ └── repository │ │ ├── AbstractBookRepository.java │ │ ├── AuthorRepository.java │ │ ├── BookRatingRepository.java │ │ ├── BookRepository.java │ │ ├── BookRepositoryCustom.java │ │ ├── BookRepositoryCustomImpl.java │ │ ├── BookWithBatchSizeRepository.java │ │ ├── BookWithFetchModeJoinRepository.java │ │ ├── BookWithFetchModeSelectRepository.java │ │ ├── BookWithFetchModeSubselectRepository.java │ │ ├── BookWithMultipleBagsRepository.java │ │ └── CategoryRepository.java │ └── test │ ├── java │ └── com │ │ └── example │ │ └── spring │ │ └── data │ │ └── jpa │ │ ├── AbstractBookRepositoryBaseTest.java │ │ ├── AbstractContainerBaseTest.java │ │ ├── BookRatingRepositoryTest.java │ │ ├── BookRepositoryTest.java │ │ ├── BookWithBatchSizeRepositoryTest.java │ │ ├── BookWithFetchModeJoinRepositoryTest.java │ │ ├── BookWithFetchModeSelectRepositoryTest.java │ │ ├── BookWithFetchModeSubselectRepositoryTest.java │ │ ├── BookWithMultipleBagsRepositoryTest.java │ │ └── ProxyDataSourceConfig.java │ └── resources │ └── application.properties └── spring-data-r2dbc-examples ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── classes.png ├── classes.puml ├── tables.png └── tables.puml ├── settings.gradle └── src ├── main └── java │ └── com │ └── example │ └── spring │ └── data │ └── r2dbc │ ├── SpringDataR2dbcApplication.java │ ├── dto │ ├── AuthorDto.java │ ├── BookDto.java │ └── CategoryDto.java │ ├── entity │ ├── Author.java │ ├── Book.java │ ├── BookAuthor.java │ ├── BookCategory.java │ └── Category.java │ ├── mapper │ ├── BookMapper.java │ └── BookMappingService.java │ └── repository │ ├── AuthorRepository.java │ ├── BookAuthorRepository.java │ ├── BookCategoryRepository.java │ ├── BookRepository.java │ └── CategoryRepository.java └── test ├── java └── com │ └── example │ └── spring │ └── data │ └── r2dbc │ ├── AbstractContainerBaseTest.java │ ├── BookRepositoryTest.java │ └── ConnectionFactoryConfig.java └── resources ├── application.properties └── schema.sql /HHH-13740/README.md: -------------------------------------------------------------------------------- 1 | # HHH-13740 - Problem with duplicates when fetching multiple many-to-many relations with Hibernate FetchMode.JOIN 2 | 3 | [HHH-13740](https://hibernate.atlassian.net/browse/HHH-13740) is an issue with duplicates, when multiple child collections are fetched using `JOIN`. 4 | 5 | When entity has multiple many-to-many relations with `FetchMode.JOIN` (one of type `List` and others of type `Set`), searching for such entity Hibernate returns a result with a child relation of type `List` containing duplicates. 6 | 7 | The bug can be reproduced on Hibernate 5.4.12.Final and OpenJDK 11.0.6 using both plain Hibernate API and Spring Data JPA 2.2.6.RELEASE. 8 | 9 | The detailed description of the problem is available in the samples: 10 | * [HHH-13740 plain Hibernate sample](hibernate-HHH-13740/) 11 | * [HHH-13740 Spring Data JPA sample](spring-data-jpa-HHH-13740/) 12 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build/ 26 | 27 | .idea/ 28 | out 29 | 30 | .gradle -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/README.md: -------------------------------------------------------------------------------- 1 | # Problem with duplicates when fetching multiple many-to-many relations with Hibernate FetchMode.JOIN 2 | 3 | * Java version - OpenJDK 11.0.6 4 | * Hibernate version - 5.4.12.Final 5 | 6 | When there are multiple many-to-many relations with FetchMode.JOIN, 7 | `session.find` returns a result with a child relation of type `List` containing duplicates. 8 | 9 | To avoid `MultipleBagFetchException: cannot simultaneously fetch multiple bags` when multiple `@ManyToMany` collections have `@Fetch(FetchMode.JOIN)`, 10 | only one collection has type `List` and others have type `Set`. 11 | 12 | ```java 13 | @Entity 14 | @Data 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Book implements Serializable { 17 | 18 | @Id 19 | @GeneratedValue 20 | private Long id; 21 | 22 | @NaturalId 23 | @EqualsAndHashCode.Include 24 | private String isbn; 25 | 26 | private String title; 27 | 28 | private LocalDate publicationDate; 29 | 30 | @ManyToMany 31 | @Fetch(FetchMode.JOIN) 32 | private List authors = new ArrayList<>(); 33 | 34 | @ManyToMany 35 | @Fetch(FetchMode.JOIN) 36 | private Set categories = new LinkedHashSet<>(); 37 | } 38 | ``` 39 | 40 | With the following test data 41 | 42 | ```java 43 | softwareDevelopment = new Category("Software development"); 44 | session.save(softwareDevelopment); 45 | 46 | systemDesign = new Category("System design"); 47 | session.save(systemDesign); 48 | 49 | martinFowler = new Author("Martin Fowler"); 50 | session.save(martinFowler); 51 | 52 | gregorHohpe = new Author("Gregor Hohpe"); 53 | session.save(gregorHohpe); 54 | 55 | gregorHohpe = new Author(); 56 | gregorHohpe.setFullName("Gregor Hohpe"); 57 | session.save(gregorHohpe); 58 | 59 | bobbyWoolf = new Author(); 60 | bobbyWoolf.setFullName("Bobby Woolf"); 61 | session.save(bobbyWoolf); 62 | 63 | poeaa = new Book(); 64 | poeaa.setIsbn("007-6092019909"); 65 | poeaa.setTitle("Patterns of Enterprise Application Architecture"); 66 | poeaa.setPublicationDate(LocalDate.parse("2002-11-15")); 67 | poeaa.getAuthors().addAll(List.of(martinFowler)); 68 | poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 69 | session.save(poeaa); 70 | 71 | eip = new Book(); 72 | eip.setIsbn("978-0321200686"); 73 | eip.setTitle("Enterprise Integration Patterns"); 74 | eip.setPublicationDate(LocalDate.parse("2003-10-20")); 75 | eip.getAuthors().addAll(List.of(gregorHohpe, bobbyWoolf)); 76 | eip.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 77 | session.save(eip); 78 | ``` 79 | 80 | a `Book` entity found by ID contains duplicates in `List authors` 81 | because `Set categories` has size 2: `["Software development", "System design"]`. 82 | 83 | ```java 84 | @Test 85 | void findByIdOneAuthor() { 86 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 87 | Transaction transaction = session.beginTransaction(); 88 | 89 | Book poeaa = session.find(Book.class, this.poeaa.getId()); 90 | assertThat(poeaa.getTitle()).isEqualTo(this.poeaa.getTitle()); 91 | // The following line results in exception 92 | // because the actual poeaa.authors contains duplicates: ["Martin Fowler", "Martin Fowler"] 93 | assertThatHasAuthors(poeaa, martinFowler.getFullName()); 94 | 95 | transaction.commit(); 96 | } 97 | } 98 | 99 | @Test 100 | void findByIdTwoAuthors() { 101 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 102 | Transaction transaction = session.beginTransaction(); 103 | 104 | Book eip = session.find(Book.class, this.eip.getId()); 105 | assertThat(eip.getTitle()).isEqualTo(this.eip.getTitle()); 106 | // The following line results in exception 107 | // because the actual eip.authors contains duplicates: ["Gregor Hohpe", "Gregor Hohpe", "Bobby Woolf", "Bobby Woolf"] 108 | assertThatHasAuthors(eip, gregorHohpe.getFullName(), bobbyWoolf.getFullName()); 109 | 110 | transaction.commit(); 111 | } 112 | } 113 | ``` 114 | 115 | See the test [`com.example.hibernate.BookFetchModeJoinWithSetTests`](src/test/java/com/example/hibernate/BookFetchModeJoinWithSetTests.java). 116 | 117 | There are no duplicates in `List authors` when `Set categories` has size 1. -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | plugins { 18 | id 'java' 19 | } 20 | 21 | group = 'com.example.hibernate' 22 | sourceCompatibility = '11' 23 | 24 | configurations { 25 | compileOnly { 26 | extendsFrom annotationProcessor 27 | } 28 | } 29 | 30 | repositories { 31 | mavenCentral() 32 | } 33 | 34 | dependencies { 35 | implementation "org.hibernate:hibernate-core:${hibernate_version}" 36 | 37 | runtimeOnly "com.h2database:h2:${h2_version}" 38 | 39 | compileOnly "org.projectlombok:lombok:${lombok_version}" 40 | annotationProcessor "org.projectlombok:lombok:${lombok_version}" 41 | 42 | testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_version}" 43 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_version}" 44 | 45 | testImplementation "org.assertj:assertj-core:${assertj_version}" 46 | } 47 | 48 | test { 49 | useJUnitPlatform() 50 | } 51 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | version=1.0.0 18 | 19 | hibernate_version=5.4.12.Final 20 | h2_version=1.4.200 21 | lombok_version=1.18.10 22 | junit_version=5.5.2 23 | assertj_version=3.14.0 -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | rootProject.name = 'hibernate-HHH-13740' 18 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/src/main/java/com/example/hibernate/HibernateUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.hibernate; 2 | 3 | import com.example.hibernate.entity.Author; 4 | import com.example.hibernate.entity.Book; 5 | import com.example.hibernate.entity.Category; 6 | import org.hibernate.SessionFactory; 7 | import org.hibernate.boot.registry.StandardServiceRegistryBuilder; 8 | import org.hibernate.cfg.Configuration; 9 | import org.hibernate.cfg.Environment; 10 | import org.hibernate.service.ServiceRegistry; 11 | 12 | import java.util.Properties; 13 | 14 | public class HibernateUtil { 15 | 16 | private static SessionFactory sessionFactory; 17 | 18 | public static synchronized SessionFactory getSessionFactory() { 19 | if (sessionFactory == null) { 20 | try { 21 | Configuration configuration = new Configuration(); 22 | Properties settings = new Properties(); 23 | settings.put(Environment.DRIVER, "org.h2.Driver"); 24 | settings.put(Environment.URL, "jdbc:h2:mem:test_mem"); 25 | settings.put(Environment.USER, "sa"); 26 | settings.put(Environment.PASS, "sa"); 27 | settings.put(Environment.DIALECT, "org.hibernate.dialect.H2Dialect"); 28 | settings.put(Environment.SHOW_SQL, "true"); 29 | settings.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread"); 30 | settings.put(Environment.HBM2DDL_AUTO, "create-drop"); 31 | configuration.setProperties(settings); 32 | 33 | configuration.addAnnotatedClass(Category.class); 34 | configuration.addAnnotatedClass(Author.class); 35 | configuration.addAnnotatedClass(Book.class); 36 | 37 | ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() 38 | .applySettings(configuration.getProperties()).build(); 39 | 40 | sessionFactory = configuration.buildSessionFactory(serviceRegistry); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | return sessionFactory; 46 | } 47 | } -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/src/main/java/com/example/hibernate/entity/Author.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.hibernate.entity; 17 | 18 | import java.io.Serializable; 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.Id; 22 | import lombok.Data; 23 | import lombok.EqualsAndHashCode; 24 | import lombok.NoArgsConstructor; 25 | import org.hibernate.annotations.NaturalId; 26 | 27 | @Entity 28 | @Data 29 | @NoArgsConstructor 30 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 31 | public class Author implements Serializable { 32 | 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | @NaturalId 38 | @EqualsAndHashCode.Include 39 | private String fullName; 40 | 41 | public Author(String fullName) { 42 | this.fullName = fullName; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/src/main/java/com/example/hibernate/entity/Book.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.hibernate.entity; 17 | 18 | import java.io.Serializable; 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.LinkedHashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | import javax.persistence.Entity; 25 | import javax.persistence.GeneratedValue; 26 | import javax.persistence.Id; 27 | import javax.persistence.ManyToMany; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | import org.hibernate.annotations.Fetch; 31 | import org.hibernate.annotations.FetchMode; 32 | import org.hibernate.annotations.NaturalId; 33 | 34 | @Entity 35 | @Data 36 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 37 | public class Book implements Serializable { 38 | 39 | @Id 40 | @GeneratedValue 41 | private Long id; 42 | 43 | @NaturalId 44 | @EqualsAndHashCode.Include 45 | private String isbn; 46 | 47 | private String title; 48 | 49 | private LocalDate publicationDate; 50 | 51 | @ManyToMany 52 | @Fetch(FetchMode.JOIN) 53 | private List authors = new ArrayList<>(); 54 | 55 | @ManyToMany 56 | @Fetch(FetchMode.JOIN) 57 | private Set categories = new LinkedHashSet<>(); 58 | } 59 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/src/main/java/com/example/hibernate/entity/Category.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.hibernate.entity; 17 | 18 | import java.io.Serializable; 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.Id; 22 | import lombok.Data; 23 | import lombok.EqualsAndHashCode; 24 | import lombok.NoArgsConstructor; 25 | import org.hibernate.annotations.NaturalId; 26 | 27 | @Entity 28 | @Data 29 | @NoArgsConstructor 30 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 31 | public class Category implements Serializable { 32 | 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | @NaturalId 38 | @EqualsAndHashCode.Include 39 | private String name; 40 | 41 | public Category(String name) { 42 | this.name = name; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /HHH-13740/hibernate-HHH-13740/src/test/java/com/example/hibernate/BookFetchModeJoinWithSetTests.java: -------------------------------------------------------------------------------- 1 | package com.example.hibernate; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.example.hibernate.entity.Author; 6 | import com.example.hibernate.entity.Book; 7 | import com.example.hibernate.entity.Category; 8 | import java.time.LocalDate; 9 | import java.util.List; 10 | import org.hibernate.Session; 11 | import org.hibernate.Transaction; 12 | import org.junit.jupiter.api.AfterEach; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | 16 | class BookFetchModeJoinWithSetTests { 17 | 18 | private Category softwareDevelopment; 19 | private Category systemDesign; 20 | 21 | private Author martinFowler; 22 | private Author gregorHohpe; 23 | private Author bobbyWoolf; 24 | 25 | private Book poeaa; 26 | private Book eip; 27 | 28 | @BeforeEach 29 | void baseSetUp() { 30 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 31 | Transaction transaction = session.beginTransaction(); 32 | 33 | softwareDevelopment = new Category("Software development"); 34 | session.save(softwareDevelopment); 35 | 36 | systemDesign = new Category("System design"); 37 | session.save(systemDesign); 38 | 39 | martinFowler = new Author("Martin Fowler"); 40 | session.save(martinFowler); 41 | 42 | gregorHohpe = new Author("Gregor Hohpe"); 43 | session.save(gregorHohpe); 44 | 45 | bobbyWoolf = new Author(); 46 | bobbyWoolf.setFullName("Bobby Woolf"); 47 | session.save(bobbyWoolf); 48 | 49 | poeaa = new Book(); 50 | poeaa.setIsbn("007-6092019909"); 51 | poeaa.setTitle("Patterns of Enterprise Application Architecture"); 52 | poeaa.setPublicationDate(LocalDate.parse("2002-11-15")); 53 | poeaa.getAuthors().addAll(List.of(martinFowler)); 54 | poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 55 | session.save(poeaa); 56 | 57 | eip = new Book(); 58 | eip.setIsbn("978-0321200686"); 59 | eip.setTitle("Enterprise Integration Patterns"); 60 | eip.setPublicationDate(LocalDate.parse("2003-10-20")); 61 | eip.getAuthors().addAll(List.of(gregorHohpe, bobbyWoolf)); 62 | eip.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 63 | session.save(eip); 64 | 65 | transaction.commit(); 66 | } 67 | } 68 | 69 | @AfterEach 70 | void cleanUp() { 71 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 72 | Transaction transaction = session.beginTransaction(); 73 | session.createQuery("delete from Book").executeUpdate(); 74 | session.createQuery("delete from Author").executeUpdate(); 75 | session.createQuery("delete from Category").executeUpdate(); 76 | transaction.commit(); 77 | } 78 | } 79 | 80 | @Test 81 | void findByIdOneAuthor() { 82 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 83 | Transaction transaction = session.beginTransaction(); 84 | 85 | Book poeaa = session.find(Book.class, this.poeaa.getId()); 86 | assertThat(poeaa.getTitle()).isEqualTo(this.poeaa.getTitle()); 87 | // The following line results in exception 88 | // because the actual poeaa.authors contains duplicates: ["Martin Fowler", "Martin Fowler"] 89 | assertThatHasAuthors(poeaa, martinFowler.getFullName()); 90 | 91 | transaction.commit(); 92 | } 93 | } 94 | 95 | @Test 96 | void findByIdTwoAuthors() { 97 | try (Session session = HibernateUtil.getSessionFactory().openSession()) { 98 | Transaction transaction = session.beginTransaction(); 99 | 100 | Book eip = session.find(Book.class, this.eip.getId()); 101 | assertThat(eip.getTitle()).isEqualTo(this.eip.getTitle()); 102 | // The following line results in exception 103 | // because the actual eip.authors contains duplicates: ["Gregor Hohpe", "Gregor Hohpe", "Bobby Woolf", "Bobby Woolf"] 104 | assertThatHasAuthors(eip, gregorHohpe.getFullName(), bobbyWoolf.getFullName()); 105 | 106 | transaction.commit(); 107 | } 108 | } 109 | 110 | private void assertThatHasAuthors(Book book, String... authors) { 111 | assertThat(book.getAuthors().stream().map(Author::getFullName)) 112 | .containsExactlyInAnyOrder(authors); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build/ 26 | 27 | .idea/ 28 | out 29 | 30 | .gradle -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/README.md: -------------------------------------------------------------------------------- 1 | # Problem with duplicates when fetching multiple many-to-many relations with Hibernate FetchMode.JOIN 2 | 3 | * Java version - OpenJDK 11.0.6 4 | * Spring Boot version - 2.2.6.RELEASE 5 | * Spring Data JPA version - 2.2.6.RELEASE 6 | * Hibernate version - 5.4.12.Final 7 | 8 | When there are multiple many-to-many relations with FetchMode.JOIN, 9 | `JpaRepository#getOne(id)` returns a result with a child relation of type `List` containing duplicates. 10 | 11 | To avoid `MultipleBagFetchException: cannot simultaneously fetch multiple bags` when multiple `@ManyToMany` collections have `@Fetch(FetchMode.JOIN)`, 12 | only one collection has type `List` and others have type `Set`. 13 | 14 | ```java 15 | @Entity 16 | @Data 17 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 18 | public class Book implements Serializable { 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | @NaturalId 25 | @EqualsAndHashCode.Include 26 | private String isbn; 27 | 28 | private String title; 29 | 30 | private LocalDate publicationDate; 31 | 32 | @ManyToMany 33 | @Fetch(FetchMode.JOIN) 34 | private List authors = new ArrayList<>(); 35 | 36 | @ManyToMany 37 | @Fetch(FetchMode.JOIN) 38 | private Set categories = new LinkedHashSet<>(); 39 | } 40 | ``` 41 | 42 | With the following test data 43 | 44 | ```java 45 | softwareDevelopment = new Category("Software development"); 46 | categoryRepository.save(softwareDevelopment); 47 | 48 | systemDesign = new Category("System design"); 49 | categoryRepository.save(systemDesign); 50 | 51 | martinFowler = new Author("Martin Fowler"); 52 | authorRepository.save(martinFowler); 53 | 54 | gregorHohpe = new Author("Gregor Hohpe"); 55 | authorRepository.save(gregorHohpe); 56 | 57 | bobbyWoolf = new Author("Bobby Woolf"); 58 | authorRepository.save(bobbyWoolf); 59 | 60 | poeaa = new Book(); 61 | poeaa.setIsbn("007-6092019909"); 62 | poeaa.setTitle("Patterns of Enterprise Application Architecture"); 63 | poeaa.setPublicationDate(LocalDate.parse("2002-11-15")); 64 | poeaa.getAuthors().addAll(List.of(martinFowler)); 65 | poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 66 | bookRepository.save(poeaa); 67 | 68 | eip = new Book(); 69 | eip.setIsbn("978-0321200686"); 70 | eip.setTitle("Enterprise Integration Patterns"); 71 | eip.setPublicationDate(LocalDate.parse("2003-10-20")); 72 | eip.getAuthors().addAll(List.of(gregorHohpe, bobbyWoolf)); 73 | eip.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 74 | bookRepository.save(eip); 75 | ``` 76 | 77 | a `Book` entity found by ID contains duplicates in `List authors` 78 | because `Set categories` has size 2: `["Software development", "System design"]`. 79 | 80 | ```java 81 | @Transactional(readOnly = true) 82 | @Test 83 | void testFindByIdOneAuthor() { 84 | Book poeaa = bookRepository.getOne(this.poeaa.getId()); 85 | assertThat(poeaa.getTitle()).isEqualTo(this.poeaa.getTitle()); 86 | // The following line results in exception 87 | // because the actual poeaa.authors contains duplicates: ["Martin Fowler", "Martin Fowler"] 88 | assertThatHasAuthors(poeaa, martinFowler.getFullName()); 89 | } 90 | 91 | @Transactional(readOnly = true) 92 | @Test 93 | void testFindByIdTwoAuthors() { 94 | Book eip = bookRepository.getOne(this.eip.getId()); 95 | assertThat(eip.getTitle()).isEqualTo(this.eip.getTitle()); 96 | // The following line results in exception 97 | // because the actual eip.authors contains duplicates: ["Gregor Hohpe", "Gregor Hohpe", "Bobby Woolf", "Bobby Woolf"] 98 | assertThatHasAuthors(eip, gregorHohpe.getFullName(), bobbyWoolf.getFullName()); 99 | } 100 | ``` 101 | 102 | See the test [`com.example.spring.data.jpa.BookFetchModeJoinWithSetTests`](src/test/java/com/example/spring/data/jpa/BookFetchModeJoinWithSetTests.java). 103 | 104 | There are no duplicates in `List authors` when `Set categories` has size 1. -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | plugins { 18 | id 'org.springframework.boot' version "${spring_boot_version}" 19 | id 'io.spring.dependency-management' version "${spring_dependency_management_version}" 20 | id 'java' 21 | } 22 | 23 | group = 'com.example.spring-data-jpa' 24 | sourceCompatibility = '11' 25 | 26 | configurations { 27 | compileOnly { 28 | extendsFrom annotationProcessor 29 | } 30 | } 31 | 32 | repositories { 33 | mavenCentral() 34 | } 35 | 36 | dependencies { 37 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 38 | runtimeOnly 'com.h2database:h2' 39 | compileOnly 'org.projectlombok:lombok' 40 | annotationProcessor 'org.projectlombok:lombok' 41 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 42 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 43 | } 44 | } 45 | 46 | test { 47 | useJUnitPlatform() 48 | } 49 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | version=1.0.0 18 | 19 | spring_boot_version=2.2.6.RELEASE 20 | spring_dependency_management_version=1.0.9.RELEASE -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | rootProject.name = 'spring-data-jpa-HHH-13740' 18 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/JpaHibernateFetchingStrategiesApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | @SpringBootApplication 23 | public class JpaHibernateFetchingStrategiesApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(JpaHibernateFetchingStrategiesApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/entity/Author.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.spring.data.jpa.entity; 17 | 18 | import java.io.Serializable; 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.Id; 22 | import lombok.Data; 23 | import lombok.EqualsAndHashCode; 24 | import lombok.NoArgsConstructor; 25 | import org.hibernate.annotations.NaturalId; 26 | 27 | @Entity 28 | @Data 29 | @NoArgsConstructor 30 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 31 | public class Author implements Serializable { 32 | 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | @NaturalId 38 | @EqualsAndHashCode.Include 39 | private String fullName; 40 | 41 | public Author(String fullName) { 42 | this.fullName = fullName; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/entity/Book.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.spring.data.jpa.entity; 17 | 18 | import java.io.Serializable; 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.LinkedHashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | import javax.persistence.Entity; 25 | import javax.persistence.GeneratedValue; 26 | import javax.persistence.Id; 27 | import javax.persistence.ManyToMany; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | import org.hibernate.annotations.Fetch; 31 | import org.hibernate.annotations.FetchMode; 32 | import org.hibernate.annotations.NaturalId; 33 | 34 | @Entity 35 | @Data 36 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 37 | public class Book implements Serializable { 38 | 39 | @Id 40 | @GeneratedValue 41 | private Long id; 42 | 43 | @NaturalId 44 | @EqualsAndHashCode.Include 45 | private String isbn; 46 | 47 | private String title; 48 | 49 | private LocalDate publicationDate; 50 | 51 | @ManyToMany 52 | @Fetch(FetchMode.JOIN) 53 | private List authors = new ArrayList<>(); 54 | 55 | @ManyToMany 56 | @Fetch(FetchMode.JOIN) 57 | private Set categories = new LinkedHashSet<>(); 58 | } 59 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/entity/Category.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 com.example.spring.data.jpa.entity; 17 | 18 | import java.io.Serializable; 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | import javax.persistence.Id; 22 | import lombok.Data; 23 | import lombok.EqualsAndHashCode; 24 | import lombok.NoArgsConstructor; 25 | import org.hibernate.annotations.NaturalId; 26 | 27 | @Entity 28 | @Data 29 | @NoArgsConstructor 30 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 31 | public class Category implements Serializable { 32 | 33 | @Id 34 | @GeneratedValue 35 | private Long id; 36 | 37 | @NaturalId 38 | @EqualsAndHashCode.Include 39 | private String name; 40 | 41 | public Category(String name) { 42 | this.name = name; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/repository/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Author; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | public interface AuthorRepository extends JpaRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/repository/BookRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Book; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | public interface BookRepository extends JpaRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/java/com/example/spring/data/jpa/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Category; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | public interface CategoryRepository extends JpaRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Evgeniy Khyst 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 | 17 | logging.level.org.hibernate.SQL=DEBUG 18 | logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE 19 | 20 | spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true -------------------------------------------------------------------------------- /HHH-13740/spring-data-jpa-HHH-13740/src/test/java/com/example/spring/data/jpa/BookFetchModeJoinWithSetTests.java: -------------------------------------------------------------------------------- 1 | package com.example.spring.data.jpa; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.example.spring.data.jpa.entity.Author; 6 | import com.example.spring.data.jpa.entity.Book; 7 | import com.example.spring.data.jpa.entity.Category; 8 | import com.example.spring.data.jpa.repository.AuthorRepository; 9 | import com.example.spring.data.jpa.repository.BookRepository; 10 | import com.example.spring.data.jpa.repository.CategoryRepository; 11 | import java.time.LocalDate; 12 | import java.util.List; 13 | import org.junit.jupiter.api.AfterAll; 14 | import org.junit.jupiter.api.BeforeAll; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestInstance; 17 | import org.junit.jupiter.api.TestInstance.Lifecycle; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.boot.test.context.SpringBootTest; 20 | import org.springframework.transaction.TransactionStatus; 21 | import org.springframework.transaction.annotation.Transactional; 22 | import org.springframework.transaction.support.TransactionCallbackWithoutResult; 23 | import org.springframework.transaction.support.TransactionTemplate; 24 | 25 | @SpringBootTest 26 | @TestInstance(Lifecycle.PER_CLASS) 27 | class BookFetchModeJoinWithSetTests { 28 | 29 | @Autowired 30 | private TransactionTemplate transactionTemplate; 31 | 32 | @Autowired 33 | private BookRepository bookRepository; 34 | 35 | @Autowired 36 | private CategoryRepository categoryRepository; 37 | 38 | @Autowired 39 | private AuthorRepository authorRepository; 40 | 41 | private Category softwareDevelopment; 42 | private Category systemDesign; 43 | 44 | private Author martinFowler; 45 | private Author gregorHohpe; 46 | private Author bobbyWoolf; 47 | 48 | private Book poeaa; 49 | private Book eip; 50 | 51 | @BeforeAll 52 | void baseSetUp() { 53 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { 54 | @Override 55 | protected void doInTransactionWithoutResult(TransactionStatus status) { 56 | softwareDevelopment = new Category("Software development"); 57 | categoryRepository.save(softwareDevelopment); 58 | 59 | systemDesign = new Category("System design"); 60 | categoryRepository.save(systemDesign); 61 | 62 | martinFowler = new Author("Martin Fowler"); 63 | authorRepository.save(martinFowler); 64 | 65 | gregorHohpe = new Author("Gregor Hohpe"); 66 | authorRepository.save(gregorHohpe); 67 | 68 | bobbyWoolf = new Author("Bobby Woolf"); 69 | authorRepository.save(bobbyWoolf); 70 | 71 | poeaa = new Book(); 72 | poeaa.setIsbn("007-6092019909"); 73 | poeaa.setTitle("Patterns of Enterprise Application Architecture"); 74 | poeaa.setPublicationDate(LocalDate.parse("2002-11-15")); 75 | poeaa.getAuthors().addAll(List.of(martinFowler)); 76 | poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 77 | bookRepository.save(poeaa); 78 | 79 | eip = new Book(); 80 | eip.setIsbn("978-0321200686"); 81 | eip.setTitle("Enterprise Integration Patterns"); 82 | eip.setPublicationDate(LocalDate.parse("2003-10-20")); 83 | eip.getAuthors().addAll(List.of(gregorHohpe, bobbyWoolf)); 84 | eip.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 85 | bookRepository.save(eip); 86 | } 87 | }); 88 | } 89 | 90 | @AfterAll 91 | void baseCleanUp() { 92 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { 93 | @Override 94 | protected void doInTransactionWithoutResult(TransactionStatus status) { 95 | bookRepository.deleteAll(); 96 | authorRepository.deleteAll(); 97 | categoryRepository.deleteAll(); 98 | } 99 | }); 100 | } 101 | 102 | @Transactional(readOnly = true) 103 | @Test 104 | void findByIdOneAuthor() { 105 | Book poeaa = bookRepository.getOne(this.poeaa.getId()); 106 | assertThat(poeaa.getTitle()).isEqualTo(this.poeaa.getTitle()); 107 | // The following line results in exception 108 | // because the actual poeaa.authors contains duplicates: ["Martin Fowler", "Martin Fowler"] 109 | assertThatHasAuthors(poeaa, martinFowler.getFullName()); 110 | } 111 | 112 | @Transactional(readOnly = true) 113 | @Test 114 | void findByIdTwoAuthors() { 115 | Book eip = bookRepository.getOne(this.eip.getId()); 116 | assertThat(eip.getTitle()).isEqualTo(this.eip.getTitle()); 117 | // The following line results in exception 118 | // because the actual eip.authors contains duplicates: ["Gregor Hohpe", "Gregor Hohpe", "Bobby Woolf", "Bobby Woolf"] 119 | assertThatHasAuthors(eip, gregorHohpe.getFullName(), bobbyWoolf.getFullName()); 120 | } 121 | 122 | private void assertThatHasAuthors(Book book, String... authors) { 123 | assertThat(book.getAuthors().stream().map(Author::getFullName)) 124 | .containsExactlyInAnyOrder(authors); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /img/application-performance-database-related-problems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/img/application-performance-database-related-problems.png -------------------------------------------------------------------------------- /spring-data-jdbc-examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build/ 26 | 27 | .idea/ 28 | out 29 | 30 | .gradle -------------------------------------------------------------------------------- /spring-data-jdbc-examples/README.md: -------------------------------------------------------------------------------- 1 | # Spring Data JDBC Examples 2 | 3 | The example evaluates Spring Data JDBC as an alternative to Spring Data JPA. 4 | The following topics are covered: 5 | * one-to-one, one-to-many, many-to-many relationships 6 | * optimistic locking 7 | * entity to DTO mapping 8 | 9 | ## Prerequisites 10 | 11 | * JDK 11 12 | * Docker at least 1.6.0 13 | 14 | ## How to run tests 15 | 16 | To build project and run all tests use command 17 | 18 | ```bash 19 | ./gradlew cleanTest test -i 20 | ``` 21 | 22 | ## Implementation details 23 | 24 | * JDK 11 25 | * Spring Boot 2.2.x 26 | * Spring Data Release Train Neumann-RC1 27 | * Spring Data JDBC 2.0.0.RC1 28 | * [MapStruct](https://mapstruct.org/) 1.3.1.Final 29 | * JUnit 5 30 | * [Testcontainers](https://www.testcontainers.org/) 31 | 32 | This example has a simple domain model. 33 | A book has at least one author and belongs to at least one category. 34 | A book can be rated. An average rating and a total number of ratings are tracked. 35 | 36 | Spring Data JDBC is inspired by Aggregate Roots and Repositories as described in the book Domain Driven Design by Eric Evans. 37 | Aggregate Root is an entity that controls the lifecycle of related entities forming together an Aggregate. 38 | Each Aggregate has only one Aggregate Root. 39 | You should have a Repository per Aggregate Root. 40 | Related entities doesn't exist alone without an Aggregate Root. 41 | When an Aggregate Root is deleted, all related entities get deleted too. 42 | 43 | If two entities have different life-cycles these are separate Aggregate Roots. 44 | Thus, in contrast to JPA, one-to-many and many-to-many relationships must be modeled by referencing the ID 45 | and join tables for many-to-many relationships must be mapped to a Java class and added to an Aggregate. 46 | 47 | In the example there are 3 Aggregates: 48 | 1. `Book` (Aggregate Root), `BookAuthor`, `BookCategory` 49 | 2. `Author` (Aggregate Root) 50 | 3. `Category` (Aggregate Root) 51 | 52 | **UML class diagram** 53 | 54 | ![Actual UML class diagram](img/classes.png) 55 | 56 | **Entity-relationship diagram** 57 | 58 | ![Entity-relationship diagram](img/tables.png) 59 | 60 | ## Test cases 61 | 62 | * Queries - `com.example.spring.data.jdbc.BookRepositoryTest` 63 | * `CrudRepository.save` - during saving all entities referenced from an aggregate root are deleted and recreated 64 | * `CrudRepository.findById` 65 | * `PagingAndSortingRepository.findAll(Pageable)` 66 | * `@Query` with SQL 67 | * `@Query` with SQL join 68 | * `@Query` with SQL and pagination 69 | * Mapping from entity to DTO using MapStruct 70 | * Reactive `Mono.fromCallable` wrapping synchronous call 71 | * Locking strategies - `com.example.spring.data.jdbc.BookRatingRepositoryTest` 72 | * Implicit optimistic lock of entity with `@Version` on modification 73 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | plugins { 18 | id 'java' 19 | id 'org.springframework.boot' version "${spring_boot_version}" 20 | id 'io.spring.dependency-management' version "${spring_dependency_management_version}" 21 | } 22 | 23 | group = 'com.example.spring-data-jdbc' 24 | sourceCompatibility = '11' 25 | 26 | configurations { 27 | compileOnly { 28 | extendsFrom annotationProcessor 29 | } 30 | testCompileOnly { 31 | extendsFrom testAnnotationProcessor 32 | } 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | maven { 38 | url 'https://repo.spring.io/libs-milestone' 39 | } 40 | } 41 | 42 | dependencyManagement { 43 | imports { 44 | mavenBom 'org.springframework.data:spring-data-releasetrain:Neumann-RC1' 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation "org.springframework.boot:spring-boot-starter-data-jdbc" 50 | implementation "org.postgresql:postgresql:${postgresql_version}" 51 | implementation "net.ttddyy:datasource-proxy:${datasource_proxy_version}" 52 | implementation "org.mapstruct:mapstruct:${mapstruct_version}" 53 | annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}" 54 | annotationProcessor 'org.projectlombok:lombok' 55 | testAnnotationProcessor 'org.projectlombok:lombok' 56 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 57 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 58 | } 59 | testImplementation "org.testcontainers:testcontainers:${testcontainers_version}" 60 | testImplementation "org.testcontainers:postgresql:${testcontainers_version}" 61 | testImplementation "io.projectreactor:reactor-core" 62 | } 63 | 64 | test { 65 | useJUnitPlatform() 66 | } 67 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | version=1.0.0 18 | 19 | spring_boot_version=2.2.6.RELEASE 20 | spring_dependency_management_version=1.0.9.RELEASE 21 | postgresql_version=42.2.12 22 | datasource_proxy_version=1.6 23 | mapstruct_version=1.3.1.Final 24 | testcontainers_version=1.14.0 -------------------------------------------------------------------------------- /spring-data-jdbc-examples/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/img/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-jdbc-examples/img/classes.png -------------------------------------------------------------------------------- /spring-data-jdbc-examples/img/classes.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Book o-- BookAuthor 4 | Book o-- BookCategory 5 | Author <-- BookAuthor 6 | Category <-- BookCategory 7 | Book <-- BookRating 8 | 9 | class Book { 10 | -id : Long 11 | -isbn : String 12 | -title : String 13 | -publicationDate : LocalDate 14 | -authors : List 15 | -categories : Set 16 | } 17 | 18 | class BookAuthor { 19 | -author : Long 20 | } 21 | 22 | class BookCategory { 23 | -category : Long 24 | } 25 | 26 | class Author { 27 | -id : Long 28 | -fullName : String 29 | } 30 | 31 | class Category { 32 | -id : Long 33 | -name : String 34 | } 35 | 36 | class BookRating { 37 | -id : Long 38 | -version : int 39 | -book : Long 40 | -rating : BigDecimal 41 | -numberOfRatings : int 42 | } 43 | 44 | @enduml -------------------------------------------------------------------------------- /spring-data-jdbc-examples/img/tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-jdbc-examples/img/tables.png -------------------------------------------------------------------------------- /spring-data-jdbc-examples/img/tables.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | hide circle 4 | 5 | skinparam linetype ortho 6 | 7 | entity "CATEGORY" as c { 8 | *ID : SERIAL <> 9 | -- 10 | *NAME : VARCHAR(255) <> 11 | } 12 | 13 | entity "AUTHOR" as a { 14 | *ID : SERIAL <> 15 | -- 16 | *FULL_NAME : VARCHAR(255) <> 17 | } 18 | 19 | entity "BOOK" as b { 20 | *ID : SERIAL <> 21 | -- 22 | *ISBN : VARCHAR(14) <> 23 | *TITLE : VARCHAR(255) <> 24 | *PUBLICATION_DATE : DATE <> 25 | } 26 | 27 | entity "BOOK_CATEGORY" as bc { 28 | *BOOK : INTEGER <> 29 | *CATEGORY : INTEGER <> 30 | -- 31 | } 32 | 33 | entity "BOOK_AUTHOR" as ba { 34 | *BOOK : INTEGER <> 35 | *AUTHOR : INTEGER <> 36 | -- 37 | *BOOK_KEY : INTEGER 38 | } 39 | 40 | entity "BOOK_RATING" as br { 41 | *ID : SERIAL <>, 42 | -- 43 | *VERSION : INTEGER <> 44 | *BOOK : INTEGER <> 45 | *RATING : NUMERIC(4, 3) <> 46 | *NUMBER_OF_RATINGS : INTEGER <> 47 | } 48 | 49 | b ||..|{ bc 50 | b ||..|{ ba 51 | b ||..o| br 52 | 53 | c ||..|{ bc 54 | a ||..|{ ba 55 | 56 | @enduml 57 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | rootProject.name = 'spring-data-jdbc-examples' 18 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/SpringDataJdbcApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | @SpringBootApplication 23 | public class SpringDataJdbcApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(SpringDataJdbcApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/dto/AuthorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class AuthorDto { 23 | 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/dto/BookDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.dto; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import lombok.Data; 23 | 24 | @Data 25 | public class BookDto { 26 | 27 | private String isbn; 28 | private String title; 29 | private LocalDate publicationDate; 30 | private List authors = new ArrayList<>(); 31 | private List categories = new ArrayList<>(); 32 | } 33 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/dto/CategoryDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class CategoryDto { 23 | 24 | private String label; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/Author.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.EqualsAndHashCode; 22 | import lombok.With; 23 | import org.springframework.data.annotation.Id; 24 | 25 | @Data 26 | @AllArgsConstructor 27 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 28 | public final class Author { 29 | 30 | @Id 31 | @With 32 | private final Long id; 33 | 34 | @EqualsAndHashCode.Include 35 | private final String fullName; 36 | 37 | public static Author of(String fullName) { 38 | return new Author(null, fullName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/Book.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | import lombok.AllArgsConstructor; 25 | import lombok.Data; 26 | import lombok.EqualsAndHashCode; 27 | import lombok.With; 28 | import org.springframework.data.annotation.Id; 29 | 30 | @Data 31 | @AllArgsConstructor 32 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 33 | public final class Book { 34 | 35 | @Id 36 | @With 37 | private final Long id; 38 | 39 | @EqualsAndHashCode.Include 40 | private final String isbn; 41 | 42 | private final String title; 43 | 44 | private final LocalDate publicationDate; 45 | 46 | private final List authors; 47 | 48 | private final Set categories; 49 | 50 | public static Book of(String isbn, String title, LocalDate publicationDate) { 51 | return new Book(null, isbn, title, publicationDate, new ArrayList<>(), new HashSet<>()); 52 | } 53 | 54 | public void addAuthor(Author author) { 55 | authors.add(new BookAuthor(author.getId())); 56 | } 57 | 58 | public void addCategory(Category category) { 59 | categories.add(new BookCategory(category.getId())); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/BookAuthor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | @Data 23 | @AllArgsConstructor 24 | public final class BookAuthor { 25 | 26 | private final Long author; 27 | } 28 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/BookCategory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | @Data 23 | @AllArgsConstructor 24 | public final class BookCategory { 25 | 26 | private final Long category; 27 | } 28 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/BookRating.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import java.math.BigDecimal; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Data; 22 | import lombok.EqualsAndHashCode; 23 | import lombok.With; 24 | import org.springframework.data.annotation.Id; 25 | import org.springframework.data.annotation.Version; 26 | 27 | @Data 28 | @AllArgsConstructor 29 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 30 | public class BookRating { 31 | 32 | @Id 33 | @With 34 | private Long id; 35 | 36 | @Version 37 | @With 38 | private final int version; 39 | 40 | @EqualsAndHashCode.Include 41 | private final Long book; 42 | 43 | private BigDecimal rating; 44 | 45 | private int numberOfRatings; 46 | 47 | public static BookRating of(Book book, BigDecimal rating, int numberOfRatings) { 48 | return new BookRating(null, 0, book.getId(), rating, numberOfRatings); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/entity/Category.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.EqualsAndHashCode; 22 | import lombok.With; 23 | import org.springframework.data.annotation.Id; 24 | 25 | @Data 26 | @AllArgsConstructor 27 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 28 | public final class Category { 29 | 30 | @Id 31 | @With 32 | private final Long id; 33 | 34 | @EqualsAndHashCode.Include 35 | private final String name; 36 | 37 | public static Category of(String name) { 38 | return new Category(null, name); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.mapper; 18 | 19 | import com.example.spring.data.jdbc.dto.AuthorDto; 20 | import com.example.spring.data.jdbc.dto.BookDto; 21 | import com.example.spring.data.jdbc.dto.CategoryDto; 22 | import com.example.spring.data.jdbc.entity.Author; 23 | import com.example.spring.data.jdbc.entity.Book; 24 | import com.example.spring.data.jdbc.entity.Category; 25 | import java.util.List; 26 | import org.mapstruct.DecoratedWith; 27 | import org.mapstruct.Mapper; 28 | import org.mapstruct.Mapping; 29 | 30 | @Mapper(componentModel = "spring") 31 | @DecoratedWith(BookMapperDecorator.class) 32 | public interface BookMapper { 33 | 34 | @Mapping(target = "authors", ignore = true) 35 | @Mapping(target = "categories", ignore = true) 36 | BookDto toBookDto(Book book); 37 | 38 | @Mapping(source = "fullName", target = "name") 39 | AuthorDto toAuthorDtos(Author author); 40 | 41 | @Mapping(source = "name", target = "label") 42 | CategoryDto toCategoryDto(Category category); 43 | 44 | List toAuthorDtos(List author); 45 | 46 | List toCategoryDtos(List category); 47 | } -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/mapper/BookMapperDecorator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.mapper; 18 | 19 | import com.example.spring.data.jdbc.dto.BookDto; 20 | import com.example.spring.data.jdbc.entity.Book; 21 | import com.example.spring.data.jdbc.repository.AuthorRepository; 22 | import com.example.spring.data.jdbc.repository.CategoryRepository; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.beans.factory.annotation.Qualifier; 25 | 26 | public abstract class BookMapperDecorator implements BookMapper { 27 | 28 | @Qualifier("delegate") 29 | @Autowired 30 | private BookMapper delegate; 31 | 32 | @Autowired 33 | private AuthorRepository authorRepository; 34 | 35 | @Autowired 36 | private CategoryRepository categoryRepository; 37 | 38 | @Override 39 | public BookDto toBookDto(Book book) { 40 | BookDto dto = delegate.toBookDto(book); 41 | dto.setAuthors(toAuthorDtos(authorRepository.findByBook(book.getId()))); 42 | dto.setCategories(toCategoryDtos(categoryRepository.findByBook(book.getId()))); 43 | return dto; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/repository/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.repository; 18 | 19 | import com.example.spring.data.jdbc.entity.Author; 20 | import java.util.List; 21 | import org.springframework.data.jdbc.repository.query.Query; 22 | import org.springframework.data.repository.PagingAndSortingRepository; 23 | 24 | public interface AuthorRepository extends PagingAndSortingRepository { 25 | 26 | @Query("SELECT a.* FROM AUTHOR a" 27 | + " JOIN BOOK_AUTHOR ba ON a.ID = ba.AUTHOR" 28 | + " WHERE ba.BOOK = :bookId" 29 | + " ORDER BY ba.BOOK_KEY") 30 | List findByBook(long bookId); 31 | } 32 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/repository/BookRatingRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.repository; 18 | 19 | import com.example.spring.data.jdbc.entity.BookRating; 20 | import org.springframework.data.repository.PagingAndSortingRepository; 21 | 22 | public interface BookRatingRepository extends PagingAndSortingRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/repository/BookRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.repository; 18 | 19 | import com.example.spring.data.jdbc.entity.Book; 20 | import java.time.LocalDate; 21 | import java.util.List; 22 | import org.springframework.data.jdbc.repository.query.Query; 23 | import org.springframework.data.repository.PagingAndSortingRepository; 24 | 25 | public interface BookRepository extends PagingAndSortingRepository { 26 | 27 | @Query("SELECT * FROM BOOK WHERE TITLE LIKE CONCAT('%', :title, '%')" 28 | + " ORDER BY PUBLICATION_DATE") 29 | List findByTitleContains(String title); 30 | 31 | @Query("SELECT * FROM BOOK WHERE TITLE LIKE CONCAT('%', :title, '%')" 32 | + " ORDER BY PUBLICATION_DATE" 33 | + " OFFSET :start" 34 | + " FETCH NEXT :rowCount ROWS ONLY") 35 | List findByTitleContains(String title, int start, int rowCount); 36 | 37 | @Query("SELECT COUNT(*) FROM BOOK WHERE TITLE LIKE CONCAT('%', :title, '%')") 38 | int countByTitleContains(String title); 39 | 40 | //JOIN in SQL query leads to duplicates in the result set. 41 | //Separate queries are still generated for List authors and Set categories. 42 | @Query("SELECT * FROM BOOK b" 43 | + " LEFT JOIN BOOK_AUTHOR ba ON b.ID = ba.BOOK" 44 | + " WHERE b.PUBLICATION_DATE > :date") 45 | List findByPublicationDateAfter(LocalDate date); 46 | } 47 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/main/java/com/example/spring/data/jdbc/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc.repository; 18 | 19 | import com.example.spring.data.jdbc.entity.Category; 20 | import java.util.List; 21 | import org.springframework.data.jdbc.repository.query.Query; 22 | import org.springframework.data.repository.PagingAndSortingRepository; 23 | 24 | public interface CategoryRepository extends PagingAndSortingRepository { 25 | 26 | @Query("SELECT c.* FROM CATEGORY c" 27 | + " JOIN BOOK_CATEGORY bc ON c.ID = bc.CATEGORY" 28 | + " WHERE bc.BOOK = :bookId") 29 | List findByBook(long bookId); 30 | } 31 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/test/java/com/example/spring/data/jdbc/AbstractContainerBaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc; 18 | 19 | import java.lang.reflect.Method; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.TestInfo; 23 | import org.testcontainers.containers.PostgreSQLContainer; 24 | 25 | @Slf4j 26 | abstract class AbstractContainerBaseTest { 27 | 28 | static final PostgreSQLContainer POSTGRE_SQL_CONTAINER; 29 | 30 | static { 31 | POSTGRE_SQL_CONTAINER = new PostgreSQLContainer(); 32 | POSTGRE_SQL_CONTAINER.start(); 33 | Runtime.getRuntime().addShutdownHook(new Thread(POSTGRE_SQL_CONTAINER::stop)); 34 | } 35 | 36 | @BeforeEach 37 | void logTestInfo(TestInfo testInfo) { 38 | log.info("{}.{}", 39 | testInfo.getTestClass().map(Class::getSimpleName).orElse("N/A"), 40 | testInfo.getTestMethod().map(Method::getName).orElse("N/A")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/test/java/com/example/spring/data/jdbc/BookRatingRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 21 | import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; 22 | 23 | import com.example.spring.data.jdbc.entity.Book; 24 | import com.example.spring.data.jdbc.entity.BookRating; 25 | import com.example.spring.data.jdbc.repository.BookRatingRepository; 26 | import com.example.spring.data.jdbc.repository.BookRepository; 27 | import java.math.BigDecimal; 28 | import java.time.LocalDate; 29 | import lombok.extern.slf4j.Slf4j; 30 | import org.junit.jupiter.api.AfterEach; 31 | import org.junit.jupiter.api.BeforeEach; 32 | import org.junit.jupiter.api.Test; 33 | import org.springframework.beans.factory.annotation.Autowired; 34 | import org.springframework.boot.test.context.SpringBootTest; 35 | import org.springframework.context.annotation.Import; 36 | import org.springframework.dao.OptimisticLockingFailureException; 37 | import org.springframework.data.relational.core.conversion.DbActionExecutionException; 38 | import org.springframework.test.annotation.Rollback; 39 | import org.springframework.transaction.TransactionStatus; 40 | import org.springframework.transaction.annotation.Transactional; 41 | import org.springframework.transaction.support.TransactionCallbackWithoutResult; 42 | import org.springframework.transaction.support.TransactionTemplate; 43 | 44 | @SpringBootTest 45 | @Transactional 46 | @Rollback(false) 47 | @Import(ProxyDataSourceConfig.class) 48 | @Slf4j 49 | class BookRatingRepositoryTest extends AbstractContainerBaseTest { 50 | 51 | @Autowired 52 | private BookRepository bookRepository; 53 | 54 | @Autowired 55 | private BookRatingRepository bookRatingRepository; 56 | 57 | @Autowired 58 | private TransactionTemplate txTemplate; 59 | 60 | private Book book; 61 | private BookRating rating; 62 | 63 | @BeforeEach 64 | void setUp() { 65 | txTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); 66 | 67 | doInNewTransaction(() -> { 68 | book = Book.of( 69 | "007-6092019909", 70 | "Patterns of Enterprise Application Architecture", 71 | LocalDate.parse("2002-11-15") 72 | ); 73 | book = bookRepository.save(book); 74 | 75 | rating = BookRating.of(book, new BigDecimal("4.4"), 240); 76 | rating = bookRatingRepository.save(rating); 77 | }); 78 | } 79 | 80 | @AfterEach 81 | void cleanUp() { 82 | bookRatingRepository.deleteAll(); 83 | bookRepository.deleteAll(); 84 | } 85 | 86 | @Test 87 | void implicitOptimisticLock() { 88 | log.info("@Version and OptimisticLockingFailureException"); 89 | 90 | assertThatThrownBy(() -> 91 | doInNewTransaction(() -> { 92 | BookRating ratingTx1 = bookRatingRepository.findById(this.rating.getId()).orElse(null); 93 | 94 | doInNewTransaction(() -> { 95 | BookRating ratingTx2 = bookRatingRepository.findById(this.rating.getId()).orElse(null); 96 | 97 | assertThat(ratingTx2).isNotNull(); 98 | assertThat(ratingTx2.getVersion()).isEqualTo(this.rating.getVersion()); 99 | 100 | ratingTx2.setRating(ratingTx2.getRating().add(new BigDecimal("0.1"))); 101 | ratingTx2.setNumberOfRatings(ratingTx2.getNumberOfRatings() + 1); 102 | 103 | bookRatingRepository.save(ratingTx2); 104 | }); 105 | 106 | assertThat(ratingTx1).isNotNull(); 107 | assertThat(ratingTx1.getVersion()).isEqualTo(this.rating.getVersion()); 108 | 109 | ratingTx1.setRating(ratingTx1.getRating().add(new BigDecimal("0.2"))); 110 | ratingTx1.setNumberOfRatings(ratingTx1.getNumberOfRatings() + 1); 111 | 112 | bookRatingRepository.save(ratingTx1); 113 | })) 114 | .isInstanceOf(DbActionExecutionException.class) 115 | .hasCauseInstanceOf(OptimisticLockingFailureException.class); 116 | 117 | BookRating rating = bookRatingRepository.findById(this.rating.getId()).orElse(null); 118 | 119 | assertThat(rating).isNotNull(); 120 | assertThat(rating.getVersion()).isEqualTo(this.rating.getVersion() + 1); 121 | assertThat(rating.getRating()) 122 | .isEqualByComparingTo(this.rating.getRating().add(new BigDecimal("0.1"))); 123 | assertThat(rating.getNumberOfRatings()).isEqualTo(this.rating.getNumberOfRatings() + 1); 124 | } 125 | 126 | private void doInNewTransaction(Runnable runnable) { 127 | txTemplate.execute(new TransactionCallbackWithoutResult() { 128 | @Override 129 | protected void doInTransactionWithoutResult(TransactionStatus status) { 130 | runnable.run(); 131 | } 132 | }); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/test/java/com/example/spring/data/jdbc/ProxyDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jdbc; 18 | 19 | import static com.example.spring.data.jdbc.AbstractContainerBaseTest.POSTGRE_SQL_CONTAINER; 20 | 21 | import javax.sql.DataSource; 22 | import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; 23 | import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; 24 | import org.springframework.boot.test.context.TestConfiguration; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 27 | 28 | @TestConfiguration 29 | public class ProxyDataSourceConfig { 30 | 31 | @Bean 32 | public DataSource dataSource() { 33 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); 34 | dataSource.setDriverClassName(POSTGRE_SQL_CONTAINER.getDriverClassName()); 35 | dataSource.setUrl(POSTGRE_SQL_CONTAINER.getJdbcUrl()); 36 | dataSource.setUsername(POSTGRE_SQL_CONTAINER.getUsername()); 37 | dataSource.setPassword(POSTGRE_SQL_CONTAINER.getPassword()); 38 | 39 | return ProxyDataSourceBuilder.create(dataSource) 40 | .logQueryBySlf4j(SLF4JLogLevel.INFO) 41 | .build(); 42 | } 43 | } -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | spring.datasource.initialization-mode=always 18 | -------------------------------------------------------------------------------- /spring-data-jdbc-examples/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE CATEGORY ( 2 | ID SERIAL PRIMARY KEY, 3 | NAME VARCHAR(255) UNIQUE NOT NULL 4 | ); 5 | 6 | CREATE TABLE AUTHOR ( 7 | ID SERIAL PRIMARY KEY, 8 | FULL_NAME VARCHAR(255) UNIQUE NOT NULL 9 | ); 10 | 11 | CREATE TABLE BOOK ( 12 | ID SERIAL PRIMARY KEY, 13 | ISBN VARCHAR(14) UNIQUE, 14 | TITLE VARCHAR(255) NOT NULL, 15 | PUBLICATION_DATE DATE NOT NULL 16 | ); 17 | 18 | CREATE TABLE BOOK_CATEGORY ( 19 | BOOK INTEGER REFERENCES BOOK(ID), 20 | CATEGORY INTEGER REFERENCES CATEGORY(ID), 21 | PRIMARY KEY(BOOK, CATEGORY) 22 | ); 23 | 24 | CREATE TABLE BOOK_AUTHOR ( 25 | BOOK INTEGER REFERENCES BOOK(ID), 26 | BOOK_KEY INTEGER, 27 | AUTHOR INTEGER REFERENCES AUTHOR(ID), 28 | PRIMARY KEY(BOOK, AUTHOR) 29 | ); 30 | 31 | CREATE TABLE BOOK_RATING ( 32 | ID SERIAL PRIMARY KEY, 33 | VERSION INTEGER NOT NULL, 34 | BOOK INTEGER REFERENCES BOOK(ID), 35 | RATING NUMERIC(4, 3) NOT NULL, 36 | NUMBER_OF_RATINGS INTEGER NOT NULL 37 | ); 38 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build/ 26 | 27 | .idea/ 28 | out 29 | 30 | .gradle -------------------------------------------------------------------------------- /spring-data-jpa-examples/README.md: -------------------------------------------------------------------------------- 1 | # Spring Data JPA Examples 2 | 3 | An example of some complicated aspects of Spring Data JPA such as 4 | 5 | * fetching strategies of `@OneToMany` and `@ManyToMany` collections 6 | * locking strategies 7 | * entity to DTO mapping 8 | 9 | ## Prerequisites 10 | 11 | * JDK 11 12 | * Docker at least 1.6.0 13 | 14 | ## How to run tests 15 | 16 | To build project and run all tests use command 17 | 18 | ```bash 19 | ./gradlew cleanTest test -i 20 | ``` 21 | 22 | ## Implementation details 23 | 24 | * JDK 11 25 | * Spring Boot 2.2.x 26 | * Spring Data JPA 2.2.x 27 | * [MapStruct](https://mapstruct.org/) 1.3.1.Final 28 | * JUnit 5 29 | * [Testcontainers](https://www.testcontainers.org/) 30 | 31 | This example has a simple domain model. 32 | A book has at least one author and belongs to at least one category. 33 | A book can be rated. An average rating and a total number of ratings are tracked. 34 | 35 | **Simplified UML class diagram** 36 | 37 | ![Simplified UML class diagram](img/classes_simplified.png) 38 | 39 | Many-to-many relationship `List authors` has `javax.persistence.FetchType.EAGER` 40 | and many-to-many relationship `Set categories` has `javax.persistence.FetchType.LAZY`. 41 | 42 | To demonstrate how different `org.hibernate.annotations.FetchMode`s work 43 | the hierarchy of book classes used: 44 | * `com.example.spring.data.jpa.entity.AbstractBook` - `@MappedSuperclass` 45 | * `com.example.spring.data.jpa.entity.Book` - no explicit `@Fetch` 46 | * `com.example.spring.data.jpa.entity.BookWithFetchModeJoin` - `@Fetch(JOIN)` 47 | * `com.example.spring.data.jpa.entity.BookWithFetchModeSelect` - `@Fetch(SELECT)` 48 | * `com.example.spring.data.jpa.entity.BookWithFetchModeSubselect` - `@Fetch(SUBSELECT)` 49 | * `com.example.spring.data.jpa.entity.BookWithBatchSize` - `@BatchSize` 50 | * `com.example.spring.data.jpa.entity.BookWithMultipleBags` - doesn't extend `AbstractBook` and has `List categories` instead of Set like in `AbstractBook` and its children what leads to `MultipleBagFetchException: cannot simultaneously fetch multiple bags` when both relations are fetched using join. 51 | 52 | To demonstrate locking strategies a `com.example.spring.data.jpa.entity.BookRating` class has `@Version int version` field. 53 | 54 | **Actual UML class diagram** 55 | 56 | ![Actual UML class diagram](img/classes.png) 57 | 58 | **Entity-relationship diagram** 59 | 60 | ![Entity-relationship diagram](img/tables.png) 61 | 62 | ## Test cases 63 | 64 | * Fetching strategies 65 | * Entity without explicit `@Fetch` - `com.example.spring.data.jpa.BookRepositoryTest` 66 | * `CrudRepository.findById` 67 | * Query method 68 | * Query method with `@EntityGraph` 69 | * Query method with `@EntityGraph` and `Pageable` 70 | * Query method with `@EntityGraph` with multiple attribute nodes (issue [HHH-13740](https://hibernate.atlassian.net/browse/HHH-13740)) 71 | * `@Query` with JPQL `join fetch` 72 | * `@Query` with JPQL join fetch and distinct 73 | * Custom `@Repository` with Criteria API query 74 | * Custom `@Repository` with Criteria API query with `fetch` 75 | * Custom `@Repository` with Criteria API query with `fetch` and `distinct` 76 | * Mapping from entity to DTO using MapStruct 77 | * Entity with `@Fetch(JOIN)` - `com.example.spring.data.jpa.BookWithFetchModeJoinRepositoryTest` 78 | * `CrudRepository.findById` 79 | * Query method 80 | * Entity with `@Fetch(SELECT)` - `com.example.spring.data.jpa.BookWithFetchModeSelectRepositoryTest` 81 | * `CrudRepository.findById` 82 | * Query method 83 | * Entity with `@Fetch(SUBSELECT)` - `com.example.spring.data.jpa.BookWithFetchModeSubselectRepositoryTest` 84 | * `CrudRepository.findById` 85 | * Query method 86 | * Entity with `@BatchSize` - `com.example.spring.data.jpa.BookWithBatchSizeRepositoryTest` 87 | * `CrudRepository.findById` 88 | * Query method 89 | * Entity with multiple bags (two `@ManyToMany` collections with type `List`) resulting in `MultipleBagFetchException` - `com.example.spring.data.jpa.BookWithMultipleBagsRepositoryTest` 90 | * Locking strategies - `com.example.spring.data.jpa.BookRatingRepositoryTest` 91 | * Implicit optimistic lock of entity with `@Version` on modification 92 | * Explicit optimistic lock `@Lock(OPTIMISTIC)` 93 | * Explicit optimistic lock `@Lock(OPTIMISTIC_FORCE_INCREMENT)` 94 | * Explicit pessimistic write lock `@Lock(PESSIMISTIC_WRITE)` 95 | * Explicit pessimistic read lock `@Lock(PESSIMISTIC_READ)` 96 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | plugins { 18 | id 'java' 19 | id 'org.springframework.boot' version "${spring_boot_version}" 20 | id 'io.spring.dependency-management' version "${spring_dependency_management_version}" 21 | } 22 | 23 | group = 'com.example.spring-data-jpa' 24 | sourceCompatibility = '11' 25 | 26 | configurations { 27 | compileOnly { 28 | extendsFrom annotationProcessor 29 | } 30 | testCompileOnly { 31 | extendsFrom testAnnotationProcessor 32 | } 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | } 38 | 39 | dependencies { 40 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 41 | implementation "net.ttddyy:datasource-proxy:${datasource_proxy_version}" 42 | implementation "org.postgresql:postgresql:${postgresql_version}" 43 | implementation "org.mapstruct:mapstruct:${mapstruct_version}" 44 | annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}" 45 | annotationProcessor 'org.projectlombok:lombok' 46 | testAnnotationProcessor 'org.projectlombok:lombok' 47 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 48 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 49 | } 50 | testImplementation "org.testcontainers:testcontainers:${testcontainers_version}" 51 | testImplementation "org.testcontainers:postgresql:${testcontainers_version}" 52 | } 53 | 54 | test { 55 | useJUnitPlatform() 56 | } 57 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | version=1.0.0 18 | 19 | spring_boot_version=2.2.6.RELEASE 20 | spring_dependency_management_version=1.0.9.RELEASE 21 | postgresql_version=42.2.12 22 | datasource_proxy_version=1.6 23 | mapstruct_version=1.3.1.Final 24 | testcontainers_version=1.14.0 -------------------------------------------------------------------------------- /spring-data-jpa-examples/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-jpa-examples/img/classes.png -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/classes.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | 4 | 5 | abstract class AbstractBook 6 | 7 | AbstractBook <|-- Book 8 | AbstractBook <|-- BookWithFetchModeJoin 9 | AbstractBook <|-- BookWithFetchModeSelect 10 | AbstractBook <|-- BookWithFetchModeSubselect 11 | AbstractBook <|-- BookWithBatchSize 12 | 13 | Book o-- Author 14 | BookWithFetchModeJoin o-- Author 15 | BookWithFetchModeSelect o-- Author 16 | BookWithFetchModeSubselect o-- Author 17 | BookWithBatchSize o-- Author 18 | BookWithMultipleBags o-- Author 19 | 20 | Book o-- Category 21 | BookWithFetchModeJoin o-- Category 22 | BookWithFetchModeSelect o-- Category 23 | BookWithFetchModeSubselect o-- Category 24 | BookWithBatchSize o-- Category 25 | BookWithMultipleBags o-- Category 26 | 27 | Book o--- BookRating 28 | 29 | abstract class AbstractBook { 30 | -id : Long 31 | -isbn : String 32 | -title : String 33 | -publicationDate : LocalDate 34 | {abstract} +List getAuthors() 35 | {abstract} +setAuthors(List authors) 36 | {abstract} +Set getCategories() 37 | {abstract} +setCategories(Set categories) 38 | } 39 | 40 | class Author { 41 | -id : Long 42 | -fullName : String 43 | } 44 | 45 | class Category { 46 | -id : Long 47 | -name : String 48 | } 49 | 50 | class Book { 51 | -authors : List 52 | -categories : Set 53 | } 54 | 55 | class BookWithFetchModeJoin { 56 | -authors : List 57 | -categories : Set 58 | } 59 | 60 | class BookWithFetchModeSelect { 61 | -authors : List 62 | -categories : Set 63 | } 64 | 65 | class BookWithFetchModeSubselect { 66 | -authors : List 67 | -categories : Set 68 | } 69 | 70 | class BookWithBatchSize { 71 | -authors : List 72 | -categories : Set 73 | } 74 | 75 | class BookWithMultipleBags { 76 | -id : Long 77 | -isbn : String 78 | -title : String 79 | -publicationDate : LocalDate 80 | -authors : List 81 | -categories : List 82 | } 83 | 84 | class BookRating { 85 | -id : Long 86 | -version : int 87 | -book : Book 88 | -rating : BigDecimal 89 | -numberOfRatings : int 90 | } 91 | 92 | note top of AbstractBook : @MappedSuperclass 93 | note top of Book : No explicit FetchMode 94 | note top of BookWithFetchModeJoin : @Fetch(JOIN) 95 | note top of BookWithFetchModeSelect : @Fetch(SELECT) 96 | note top of BookWithFetchModeSubselect : @Fetch(SUBSELECT) 97 | note top of BookWithBatchSize : @BatchSize(size = 2) 98 | note top of BookWithMultipleBags : MultipleBagFetchException:\ncannot simultaneously fetch multiple bags 99 | 100 | note "@ManyToMany(fetch = EAGER) authors\n@ManyToMany(fetch = LAZY) categories" as FetchType 101 | 102 | Book ... FetchType 103 | BookWithFetchModeJoin ... FetchType 104 | BookWithFetchModeSelect ... FetchType 105 | BookWithFetchModeSubselect ... FetchType 106 | BookWithBatchSize ... FetchType 107 | BookWithMultipleBags ... FetchType 108 | 109 | note top of BookRating : @Version 110 | 111 | @enduml -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/classes_simplified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-jpa-examples/img/classes_simplified.png -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/classes_simplified.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Book o-- Author 4 | Book o-- Category 5 | Book o-- BookRating 6 | 7 | class Book { 8 | -id : Long 9 | -isbn : String 10 | -title : String 11 | -publicationDate : LocalDate 12 | -authors : List 13 | -categories : Set 14 | } 15 | 16 | class Author { 17 | -id : Long 18 | -fullName : String 19 | } 20 | 21 | class Category { 22 | -id : Long 23 | -name : String 24 | } 25 | 26 | class BookRating { 27 | -id : Long 28 | -version : int 29 | -book : Book 30 | -rating : BigDecimal 31 | -numberOfRatings : int 32 | } 33 | 34 | @enduml -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-jpa-examples/img/tables.png -------------------------------------------------------------------------------- /spring-data-jpa-examples/img/tables.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | hide circle 4 | 5 | skinparam linetype ortho 6 | 7 | entity "CATEGORY" as c { 8 | *ID : SERIAL <> 9 | -- 10 | *NAME : VARCHAR(255) <> 11 | } 12 | 13 | entity "AUTHOR" as a { 14 | *ID : SERIAL <> 15 | -- 16 | *FULL_NAME : VARCHAR(255) <> 17 | } 18 | 19 | entity "BOOK" as b { 20 | *ID : SERIAL <> 21 | -- 22 | *ISBN : VARCHAR(14) <> 23 | *TITLE : VARCHAR(255) <> 24 | *PUBLICATION_DATE : DATE <> 25 | } 26 | 27 | entity "BOOK_CATEGORY" as bc { 28 | *BOOK : INTEGER <> 29 | *CATEGORY : INTEGER <> 30 | -- 31 | } 32 | 33 | entity "BOOK_AUTHOR" as ba { 34 | *BOOK : INTEGER <> 35 | *AUTHOR : INTEGER <> 36 | -- 37 | } 38 | 39 | entity "BOOK_RATING" as br { 40 | *ID : SERIAL <>, 41 | -- 42 | *VERSION : INTEGER <> 43 | *BOOK : INTEGER <> 44 | *RATING : NUMERIC(4, 3) <> 45 | *NUMBER_OF_RATINGS : INTEGER <> 46 | } 47 | 48 | b ||..|{ bc 49 | b ||..|{ ba 50 | b ||..o| br 51 | 52 | c ||..|{ bc 53 | a ||..|{ ba 54 | 55 | @enduml 56 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | rootProject.name = 'spring-data-jpa-examples' 18 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/SpringDataJpaApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | @SpringBootApplication 23 | public class SpringDataJpaApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(SpringDataJpaApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/dto/AuthorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class AuthorDto { 23 | 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/dto/BookDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.dto; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import lombok.Data; 23 | 24 | @Data 25 | public class BookDto { 26 | 27 | private String isbn; 28 | private String title; 29 | private LocalDate publicationDate; 30 | private List authors = new ArrayList<>(); 31 | private List categories = new ArrayList<>(); 32 | } 33 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/dto/CategoryDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class CategoryDto { 23 | 24 | private String label; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/AbstractBook.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import java.io.Serializable; 20 | import java.time.LocalDate; 21 | import java.util.List; 22 | import java.util.Set; 23 | import javax.persistence.GeneratedValue; 24 | import javax.persistence.Id; 25 | import javax.persistence.MappedSuperclass; 26 | import lombok.Data; 27 | import lombok.EqualsAndHashCode; 28 | import org.hibernate.annotations.NaturalId; 29 | 30 | @MappedSuperclass 31 | @Data 32 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 33 | public abstract class AbstractBook implements Serializable { 34 | 35 | @Id 36 | @GeneratedValue 37 | private Long id; 38 | 39 | @NaturalId 40 | @EqualsAndHashCode.Include 41 | private String isbn; 42 | 43 | private String title; 44 | 45 | private LocalDate publicationDate; 46 | 47 | public abstract List getAuthors(); 48 | 49 | public abstract void setAuthors(List authors); 50 | 51 | public abstract Set getCategories(); 52 | 53 | public abstract void setCategories(Set categories); 54 | } 55 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/Author.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import java.io.Serializable; 20 | import javax.persistence.Entity; 21 | import javax.persistence.GeneratedValue; 22 | import javax.persistence.Id; 23 | import lombok.Data; 24 | import lombok.EqualsAndHashCode; 25 | import lombok.NoArgsConstructor; 26 | import org.hibernate.annotations.NaturalId; 27 | 28 | @Entity 29 | @Data 30 | @NoArgsConstructor 31 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 32 | public class Author implements Serializable { 33 | 34 | @Id 35 | @GeneratedValue 36 | private Long id; 37 | 38 | @NaturalId 39 | @EqualsAndHashCode.Include 40 | private String fullName; 41 | 42 | public Author(String fullName) { 43 | this.fullName = fullName; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/Book.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | 21 | import java.util.ArrayList; 22 | import java.util.LinkedHashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | import javax.persistence.Entity; 26 | import javax.persistence.ManyToMany; 27 | import javax.persistence.NamedAttributeNode; 28 | import javax.persistence.NamedEntityGraph; 29 | import lombok.Data; 30 | import lombok.EqualsAndHashCode; 31 | import lombok.ToString; 32 | 33 | @Entity 34 | @NamedEntityGraph(name = "Book.authors", 35 | attributeNodes = @NamedAttributeNode("authors") 36 | ) 37 | @NamedEntityGraph(name = "Book.authors-categories", 38 | attributeNodes = { 39 | @NamedAttributeNode("authors"), 40 | @NamedAttributeNode("categories") 41 | } 42 | ) 43 | @Data 44 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 45 | @ToString(callSuper = true) 46 | public class Book extends AbstractBook { 47 | 48 | @ManyToMany(fetch = EAGER) 49 | private List authors = new ArrayList<>(); 50 | 51 | @ManyToMany 52 | private Set categories = new LinkedHashSet<>(); 53 | } 54 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookRating.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.LAZY; 20 | 21 | import java.math.BigDecimal; 22 | import javax.persistence.Entity; 23 | import javax.persistence.GeneratedValue; 24 | import javax.persistence.Id; 25 | import javax.persistence.JoinColumn; 26 | import javax.persistence.OneToOne; 27 | import javax.persistence.Version; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | 31 | @Entity 32 | @Data 33 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 34 | public class BookRating { 35 | 36 | @Id 37 | @GeneratedValue 38 | private Long id; 39 | 40 | @Version 41 | private int version; 42 | 43 | @OneToOne(fetch = LAZY) 44 | @JoinColumn(unique = true) 45 | @EqualsAndHashCode.Include 46 | private Book book; 47 | 48 | private BigDecimal rating; 49 | 50 | private int numberOfRatings; 51 | } 52 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookWithBatchSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | 21 | import java.util.ArrayList; 22 | import java.util.LinkedHashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | import javax.persistence.Entity; 26 | import javax.persistence.ManyToMany; 27 | import lombok.Data; 28 | import lombok.EqualsAndHashCode; 29 | import lombok.ToString; 30 | import org.hibernate.annotations.BatchSize; 31 | 32 | @Entity 33 | @Data 34 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 35 | @ToString(callSuper = true) 36 | public class BookWithBatchSize extends AbstractBook { 37 | 38 | @ManyToMany(fetch = EAGER) 39 | @BatchSize(size = 2) 40 | private List authors = new ArrayList<>(); 41 | 42 | @ManyToMany 43 | @BatchSize(size = 2) 44 | private Set categories = new LinkedHashSet<>(); 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookWithFetchModeJoin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | import static org.hibernate.annotations.FetchMode.JOIN; 21 | 22 | import java.util.ArrayList; 23 | import java.util.LinkedHashSet; 24 | import java.util.List; 25 | import java.util.Set; 26 | import javax.persistence.Entity; 27 | import javax.persistence.ManyToMany; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | import lombok.ToString; 31 | import org.hibernate.annotations.Fetch; 32 | 33 | @Entity 34 | @Data 35 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 36 | @ToString(callSuper = true) 37 | public class BookWithFetchModeJoin extends AbstractBook { 38 | 39 | @ManyToMany(fetch = EAGER) 40 | @Fetch(JOIN) 41 | private List authors = new ArrayList<>(); 42 | 43 | @ManyToMany 44 | @Fetch(JOIN) 45 | private Set categories = new LinkedHashSet<>(); 46 | } 47 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookWithFetchModeSelect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | import static org.hibernate.annotations.FetchMode.SELECT; 21 | 22 | import java.util.ArrayList; 23 | import java.util.LinkedHashSet; 24 | import java.util.List; 25 | import java.util.Set; 26 | import javax.persistence.Entity; 27 | import javax.persistence.ManyToMany; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | import lombok.ToString; 31 | import org.hibernate.annotations.Fetch; 32 | 33 | @Entity 34 | @Data 35 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 36 | @ToString(callSuper = true) 37 | public class BookWithFetchModeSelect extends AbstractBook { 38 | 39 | @ManyToMany(fetch = EAGER) 40 | @Fetch(SELECT) 41 | private List authors = new ArrayList<>(); 42 | 43 | @ManyToMany 44 | @Fetch(SELECT) 45 | private Set categories = new LinkedHashSet<>(); 46 | } 47 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookWithFetchModeSubselect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | import static org.hibernate.annotations.FetchMode.SUBSELECT; 21 | 22 | import java.util.ArrayList; 23 | import java.util.LinkedHashSet; 24 | import java.util.List; 25 | import java.util.Set; 26 | import javax.persistence.Entity; 27 | import javax.persistence.ManyToMany; 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | import lombok.ToString; 31 | import org.hibernate.annotations.Fetch; 32 | 33 | @Entity 34 | @Data 35 | @EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true) 36 | @ToString(callSuper = true) 37 | public class BookWithFetchModeSubselect extends AbstractBook { 38 | 39 | @ManyToMany(fetch = EAGER) 40 | @Fetch(SUBSELECT) 41 | private List authors = new ArrayList<>(); 42 | 43 | @ManyToMany 44 | @Fetch(SUBSELECT) 45 | private Set categories = new LinkedHashSet<>(); 46 | } 47 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/BookWithMultipleBags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import static javax.persistence.FetchType.EAGER; 20 | 21 | import java.time.LocalDate; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import javax.persistence.Entity; 25 | import javax.persistence.GeneratedValue; 26 | import javax.persistence.Id; 27 | import javax.persistence.ManyToMany; 28 | import javax.persistence.NamedAttributeNode; 29 | import javax.persistence.NamedEntityGraph; 30 | import lombok.Data; 31 | import lombok.EqualsAndHashCode; 32 | import org.hibernate.annotations.NaturalId; 33 | 34 | @Entity 35 | @NamedEntityGraph(name = "BookWithMultipleBags.authors-categories", 36 | attributeNodes = { 37 | @NamedAttributeNode("authors"), 38 | @NamedAttributeNode("categories") 39 | } 40 | ) 41 | @Data 42 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 43 | public class BookWithMultipleBags { 44 | 45 | @Id 46 | @GeneratedValue 47 | private Long id; 48 | 49 | @NaturalId 50 | @EqualsAndHashCode.Include 51 | private String isbn; 52 | 53 | private String title; 54 | 55 | private LocalDate publicationDate; 56 | 57 | @ManyToMany(fetch = EAGER) 58 | private List authors = new ArrayList<>(); 59 | 60 | @ManyToMany 61 | private List categories = new ArrayList<>(); 62 | //Instead of Set like in AbstractBook and its children 63 | //Multiple many-to-many relations of type List with 'FetchMode.JOIN' leads to 64 | //MultipleBagFetchException: cannot simultaneously fetch multiple bags 65 | } 66 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/entity/Category.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.entity; 18 | 19 | import java.io.Serializable; 20 | import javax.persistence.Entity; 21 | import javax.persistence.GeneratedValue; 22 | import javax.persistence.Id; 23 | import lombok.Data; 24 | import lombok.EqualsAndHashCode; 25 | import lombok.NoArgsConstructor; 26 | import org.hibernate.annotations.NaturalId; 27 | 28 | @Entity 29 | @Data 30 | @NoArgsConstructor 31 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 32 | public class Category implements Serializable { 33 | 34 | @Id 35 | @GeneratedValue 36 | private Long id; 37 | 38 | @NaturalId 39 | @EqualsAndHashCode.Include 40 | private String name; 41 | 42 | public Category(String name) { 43 | this.name = name; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.mapper; 18 | 19 | import com.example.spring.data.jpa.dto.AuthorDto; 20 | import com.example.spring.data.jpa.dto.BookDto; 21 | import com.example.spring.data.jpa.dto.CategoryDto; 22 | import com.example.spring.data.jpa.entity.Author; 23 | import com.example.spring.data.jpa.entity.Book; 24 | import com.example.spring.data.jpa.entity.Category; 25 | import org.mapstruct.Mapper; 26 | import org.mapstruct.Mapping; 27 | 28 | @Mapper(componentModel = "spring") 29 | public interface BookMapper { 30 | 31 | BookDto toBookDto(Book book); 32 | 33 | @Mapping(source = "fullName", target = "name") 34 | AuthorDto toAuthorDtos(Author author); 35 | 36 | @Mapping(source = "name", target = "label") 37 | CategoryDto toCategoryDto(Category category); 38 | } -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/AbstractBookRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.AbstractBook; 20 | import java.util.List; 21 | import org.springframework.data.domain.Pageable; 22 | import org.springframework.data.domain.Sort; 23 | import org.springframework.data.jpa.repository.JpaRepository; 24 | import org.springframework.data.repository.NoRepositoryBean; 25 | 26 | @NoRepositoryBean 27 | public interface AbstractBookRepository extends JpaRepository { 28 | 29 | List findByTitleContains(String title, Pageable pageable); 30 | } 31 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Author; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | public interface AuthorRepository extends JpaRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookRatingRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import static javax.persistence.LockModeType.OPTIMISTIC; 20 | import static javax.persistence.LockModeType.OPTIMISTIC_FORCE_INCREMENT; 21 | import static javax.persistence.LockModeType.PESSIMISTIC_READ; 22 | import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; 23 | 24 | import com.example.spring.data.jpa.entity.BookRating; 25 | import org.springframework.data.jpa.repository.JpaRepository; 26 | import org.springframework.data.jpa.repository.Lock; 27 | import org.springframework.data.jpa.repository.Query; 28 | 29 | public interface BookRatingRepository extends JpaRepository { 30 | 31 | BookRating findByBookIsbn(String isbn); 32 | 33 | @Lock(OPTIMISTIC) 34 | @Query("select br from BookRating br where br.book.isbn = :isbn") 35 | BookRating findByBookIsbnOptimisticLock(String isbn); 36 | 37 | @Lock(OPTIMISTIC_FORCE_INCREMENT) 38 | @Query("select br from BookRating br where br.book.isbn = :isbn") 39 | BookRating findByBookIsbnOptimisticForceIncrementLock(String isbn); 40 | 41 | @Lock(PESSIMISTIC_WRITE) 42 | @Query("select br from BookRating br where br.book.isbn = :isbn") 43 | BookRating findByBookIsbnPessimisticWriteLock(String isbn); 44 | 45 | @Lock(PESSIMISTIC_READ) 46 | @Query("select br from BookRating br where br.book.isbn = :isbn") 47 | BookRating findByBookIsbnPessimisticReadLock(String isbn); 48 | } 49 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Book; 20 | import java.time.LocalDate; 21 | import java.util.List; 22 | import org.springframework.data.domain.Pageable; 23 | import org.springframework.data.domain.Sort; 24 | import org.springframework.data.jpa.repository.EntityGraph; 25 | import org.springframework.data.jpa.repository.Query; 26 | 27 | public interface BookRepository 28 | extends AbstractBookRepository, BookRepositoryCustom { 29 | 30 | @EntityGraph("Book.authors") 31 | List findByPublicationDateAfter(LocalDate date, Sort sort); 32 | 33 | @EntityGraph("Book.authors") 34 | List findByPublicationDateAfter(LocalDate date, Pageable pageable); 35 | 36 | @EntityGraph("Book.authors-categories") 37 | List findByPublicationDateBetween(LocalDate startDate, LocalDate endDate, Sort sort); 38 | 39 | @Query("select b from Book b join fetch b.authors" 40 | + " where b.publicationDate > :date" 41 | + " order by b.publicationDate asc") 42 | List findByPublicationDateAfterJoinFetch(LocalDate date); 43 | 44 | @Query("select distinct b from Book b join fetch b.authors" 45 | + " where b.publicationDate > :date" 46 | + " order by b.publicationDate asc") 47 | List findByPublicationDateAfterJoinFetchDistinct(LocalDate date); 48 | } 49 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Book; 20 | import java.util.List; 21 | 22 | public interface BookRepositoryCustom { 23 | 24 | List findByAuthorNameAndTitle( 25 | boolean fetchAuthor, boolean distinct, String authorName, String title); 26 | } 27 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookRepositoryCustomImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Book; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import javax.persistence.EntityManager; 23 | import javax.persistence.criteria.CriteriaBuilder; 24 | import javax.persistence.criteria.CriteriaQuery; 25 | import javax.persistence.criteria.Path; 26 | import javax.persistence.criteria.Predicate; 27 | import javax.persistence.criteria.Root; 28 | import lombok.RequiredArgsConstructor; 29 | import org.springframework.stereotype.Repository; 30 | 31 | @Repository 32 | @RequiredArgsConstructor 33 | public class BookRepositoryCustomImpl implements BookRepositoryCustom { 34 | 35 | private final EntityManager em; 36 | 37 | @Override 38 | public List findByAuthorNameAndTitle( 39 | boolean fetchAuthor, boolean distinct, String authorName, String title) { 40 | 41 | CriteriaBuilder cb = em.getCriteriaBuilder(); 42 | CriteriaQuery cq = cb.createQuery(Book.class); 43 | 44 | Root book = cq.from(Book.class); 45 | Path authors; 46 | 47 | if (fetchAuthor) { 48 | book.fetch("authors"); 49 | authors = book.get("authors"); 50 | } else { 51 | authors = book.join("authors"); 52 | } 53 | 54 | List predicates = new ArrayList<>(); 55 | if (authorName != null) { 56 | predicates.add(cb.like(authors.get("fullName"), "%" + authorName + "%")); 57 | } 58 | if (title != null) { 59 | predicates.add(cb.like(book.get("title"), "%" + title + "%")); 60 | } 61 | 62 | cq.select(book) 63 | .distinct(distinct) 64 | .where(predicates.toArray(new Predicate[0])) 65 | .orderBy(cb.asc(book.get("publicationDate"))); 66 | 67 | return em.createQuery(cq).getResultList(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookWithBatchSizeRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithBatchSize; 20 | 21 | public interface BookWithBatchSizeRepository extends AbstractBookRepository { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookWithFetchModeJoinRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithFetchModeJoin; 20 | 21 | public interface BookWithFetchModeJoinRepository 22 | extends AbstractBookRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookWithFetchModeSelectRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithFetchModeSelect; 20 | 21 | public interface BookWithFetchModeSelectRepository 22 | extends AbstractBookRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookWithFetchModeSubselectRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithFetchModeSubselect; 20 | 21 | public interface BookWithFetchModeSubselectRepository 22 | extends AbstractBookRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/BookWithMultipleBagsRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithMultipleBags; 20 | import java.time.LocalDate; 21 | import java.util.List; 22 | import org.springframework.data.domain.Sort; 23 | import org.springframework.data.jpa.repository.EntityGraph; 24 | import org.springframework.data.jpa.repository.JpaRepository; 25 | 26 | public interface BookWithMultipleBagsRepository extends JpaRepository { 27 | 28 | @EntityGraph("BookWithMultipleBags.authors-categories") 29 | List findByPublicationDateBetween( 30 | LocalDate startDate, LocalDate endDate, Sort sort); 31 | } 32 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/main/java/com/example/spring/data/jpa/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa.repository; 18 | 19 | import com.example.spring.data.jpa.entity.Category; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | public interface CategoryRepository extends JpaRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/AbstractContainerBaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import java.lang.reflect.Method; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.TestInfo; 23 | import org.testcontainers.containers.PostgreSQLContainer; 24 | 25 | @Slf4j 26 | abstract class AbstractContainerBaseTest { 27 | 28 | static final PostgreSQLContainer POSTGRE_SQL_CONTAINER; 29 | 30 | static { 31 | POSTGRE_SQL_CONTAINER = new PostgreSQLContainer(); 32 | POSTGRE_SQL_CONTAINER.start(); 33 | Runtime.getRuntime().addShutdownHook(new Thread(POSTGRE_SQL_CONTAINER::stop)); 34 | } 35 | 36 | @BeforeEach 37 | void logTestInfo(TestInfo testInfo) { 38 | log.info("{}.{}", 39 | testInfo.getTestClass().map(Class::getSimpleName).orElse("N/A"), 40 | testInfo.getTestMethod().map(Method::getName).orElse("N/A")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/BookWithBatchSizeRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithBatchSize; 20 | import com.example.spring.data.jpa.repository.AbstractBookRepository; 21 | import com.example.spring.data.jpa.repository.BookWithBatchSizeRepository; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | 25 | @Slf4j 26 | class BookWithBatchSizeRepositoryTest extends AbstractBookRepositoryBaseTest { 27 | 28 | @Autowired 29 | private BookWithBatchSizeRepository bookRepository; 30 | 31 | @Override 32 | BookWithBatchSize createBook() { 33 | return new BookWithBatchSize(); 34 | } 35 | 36 | @Override 37 | AbstractBookRepository getBookRepository() { 38 | return bookRepository; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/BookWithFetchModeJoinRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import com.example.spring.data.jpa.entity.BookWithFetchModeJoin; 22 | import com.example.spring.data.jpa.repository.AbstractBookRepository; 23 | import com.example.spring.data.jpa.repository.BookWithFetchModeJoinRepository; 24 | import java.util.Optional; 25 | import lombok.extern.slf4j.Slf4j; 26 | import org.junit.jupiter.api.Test; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | 29 | @Slf4j 30 | class BookWithFetchModeJoinRepositoryTest 31 | extends AbstractBookRepositoryBaseTest { 32 | 33 | @Autowired 34 | private BookWithFetchModeJoinRepository bookRepository; 35 | 36 | @Override 37 | BookWithFetchModeJoin createBook() { 38 | return new BookWithFetchModeJoin(); 39 | } 40 | 41 | @Override 42 | AbstractBookRepository getBookRepository() { 43 | return bookRepository; 44 | } 45 | 46 | @Test 47 | @Override 48 | void findById() { 49 | log.info("CrudRepository.findById (HHH-13740)"); 50 | 51 | Optional poeaa = bookRepository.findById(this.poeaa.getId()); 52 | Optional eip = bookRepository.findById(this.eip.getId()); 53 | 54 | log.info("Books were loaded"); 55 | 56 | //Due to the issue https://hibernate.atlassian.net/browse/HHH-13740 57 | //List authors has duplicates: 58 | assertThat(poeaa).hasValueSatisfying(book -> 59 | assertThat(book.getAuthors()) 60 | .hasSize(2) 61 | .containsExactlyInAnyOrder(martinFowler, martinFowler)); 62 | 63 | assertThat(eip).hasValueSatisfying(book -> 64 | assertThat(book.getAuthors()) 65 | .hasSize(4) 66 | .containsExactlyInAnyOrder(gregorHohpe, gregorHohpe, bobbyWoolf, bobbyWoolf)); 67 | //For more details see the /HHH-13740/README.md 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/BookWithFetchModeSelectRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithFetchModeSelect; 20 | import com.example.spring.data.jpa.repository.AbstractBookRepository; 21 | import com.example.spring.data.jpa.repository.BookWithFetchModeSelectRepository; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | 24 | class BookWithFetchModeSelectRepositoryTest 25 | extends AbstractBookRepositoryBaseTest { 26 | 27 | @Autowired 28 | private BookWithFetchModeSelectRepository bookRepository; 29 | 30 | @Override 31 | BookWithFetchModeSelect createBook() { 32 | return new BookWithFetchModeSelect(); 33 | } 34 | 35 | @Override 36 | AbstractBookRepository getBookRepository() { 37 | return bookRepository; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/BookWithFetchModeSubselectRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import com.example.spring.data.jpa.entity.BookWithFetchModeSubselect; 20 | import com.example.spring.data.jpa.repository.AbstractBookRepository; 21 | import com.example.spring.data.jpa.repository.BookWithFetchModeSubselectRepository; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | 24 | class BookWithFetchModeSubselectRepositoryTest 25 | extends AbstractBookRepositoryBaseTest { 26 | 27 | @Autowired 28 | private BookWithFetchModeSubselectRepository bookRepository; 29 | 30 | @Override 31 | BookWithFetchModeSubselect createBook() { 32 | return new BookWithFetchModeSubselect(); 33 | } 34 | 35 | @Override 36 | AbstractBookRepository getBookRepository() { 37 | return bookRepository; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/BookWithMultipleBagsRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 20 | import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; 21 | import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE; 22 | import static org.springframework.data.domain.Sort.Direction.ASC; 23 | 24 | import com.example.spring.data.jpa.entity.Author; 25 | import com.example.spring.data.jpa.entity.BookWithMultipleBags; 26 | import com.example.spring.data.jpa.entity.Category; 27 | import com.example.spring.data.jpa.repository.AuthorRepository; 28 | import com.example.spring.data.jpa.repository.BookWithMultipleBagsRepository; 29 | import com.example.spring.data.jpa.repository.CategoryRepository; 30 | import java.lang.reflect.Method; 31 | import java.time.LocalDate; 32 | import java.util.List; 33 | import lombok.extern.slf4j.Slf4j; 34 | import org.hibernate.loader.MultipleBagFetchException; 35 | import org.junit.jupiter.api.AfterAll; 36 | import org.junit.jupiter.api.BeforeAll; 37 | import org.junit.jupiter.api.BeforeEach; 38 | import org.junit.jupiter.api.Test; 39 | import org.junit.jupiter.api.TestInfo; 40 | import org.junit.jupiter.api.TestInstance; 41 | import org.junit.jupiter.api.extension.ExtendWith; 42 | import org.springframework.beans.factory.annotation.Autowired; 43 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 44 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 45 | import org.springframework.context.annotation.Import; 46 | import org.springframework.data.domain.Sort; 47 | import org.springframework.test.context.junit.jupiter.SpringExtension; 48 | import org.springframework.transaction.TransactionStatus; 49 | import org.springframework.transaction.annotation.Transactional; 50 | import org.springframework.transaction.support.TransactionCallbackWithoutResult; 51 | import org.springframework.transaction.support.TransactionTemplate; 52 | 53 | @ExtendWith(SpringExtension.class) 54 | @TestInstance(PER_CLASS) 55 | @DataJpaTest(showSql = false) 56 | @AutoConfigureTestDatabase(replace = NONE) 57 | @Transactional(readOnly = true) 58 | @Import(ProxyDataSourceConfig.class) 59 | @Slf4j 60 | class BookWithMultipleBagsRepositoryTest extends AbstractContainerBaseTest { 61 | 62 | @Autowired 63 | private BookWithMultipleBagsRepository bookRepository; 64 | 65 | @Autowired 66 | private CategoryRepository categoryRepository; 67 | 68 | @Autowired 69 | private AuthorRepository authorRepository; 70 | 71 | @Autowired 72 | private TransactionTemplate transactionTemplate; 73 | 74 | Category softwareDevelopment; 75 | Category systemDesign; 76 | 77 | Author martinFowler; 78 | 79 | BookWithMultipleBags poeaa; 80 | 81 | @BeforeAll 82 | void baseSetUp() { 83 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { 84 | @Override 85 | protected void doInTransactionWithoutResult(TransactionStatus status) { 86 | softwareDevelopment = new Category("Software development"); 87 | categoryRepository.save(softwareDevelopment); 88 | 89 | systemDesign = new Category("System design"); 90 | categoryRepository.save(systemDesign); 91 | 92 | martinFowler = new Author("Martin Fowler"); 93 | authorRepository.save(martinFowler); 94 | 95 | poeaa = new BookWithMultipleBags(); 96 | poeaa.setIsbn("007-6092019909"); 97 | poeaa.setTitle("Patterns of Enterprise Application Architecture"); 98 | poeaa.setPublicationDate(LocalDate.parse("2002-11-15")); 99 | poeaa.getAuthors().addAll(List.of(martinFowler)); 100 | poeaa.getCategories().addAll(List.of(softwareDevelopment, systemDesign)); 101 | bookRepository.save(poeaa); 102 | } 103 | }); 104 | } 105 | 106 | @AfterAll 107 | void baseCleanUp() { 108 | transactionTemplate.execute(new TransactionCallbackWithoutResult() { 109 | @Override 110 | protected void doInTransactionWithoutResult(TransactionStatus status) { 111 | bookRepository.deleteAll(); 112 | authorRepository.deleteAll(); 113 | categoryRepository.deleteAll(); 114 | } 115 | }); 116 | } 117 | 118 | @Test 119 | void multipleBagFetchException() { 120 | log.info("MultipleBagFetchException on @EntityGraph with multiple attribute nodes"); 121 | 122 | assertThatThrownBy(() -> bookRepository.findByPublicationDateBetween( 123 | LocalDate.parse("2000-01-01"), LocalDate.parse("2020-01-01"), 124 | Sort.by(ASC, "publicationDate"))) 125 | .hasRootCauseInstanceOf(MultipleBagFetchException.class) 126 | .hasMessageContaining("cannot simultaneously fetch multiple bags"); 127 | //Trying to fetch multiple many-to-many relations 128 | //List authors and List categories 129 | //that both have type List results in exception 130 | //org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/java/com/example/spring/data/jpa/ProxyDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.jpa; 18 | 19 | import static com.example.spring.data.jpa.AbstractContainerBaseTest.POSTGRE_SQL_CONTAINER; 20 | 21 | import javax.sql.DataSource; 22 | import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; 23 | import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; 24 | import org.springframework.boot.test.context.TestConfiguration; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 27 | 28 | @TestConfiguration 29 | public class ProxyDataSourceConfig { 30 | 31 | @Bean 32 | public DataSource dataSource() { 33 | DriverManagerDataSource dataSource = new DriverManagerDataSource(); 34 | dataSource.setDriverClassName(POSTGRE_SQL_CONTAINER.getDriverClassName()); 35 | dataSource.setUrl(POSTGRE_SQL_CONTAINER.getJdbcUrl()); 36 | dataSource.setUsername(POSTGRE_SQL_CONTAINER.getUsername()); 37 | dataSource.setPassword(POSTGRE_SQL_CONTAINER.getPassword()); 38 | 39 | return ProxyDataSourceBuilder.create(dataSource) 40 | .logQueryBySlf4j(SLF4JLogLevel.INFO) 41 | .build(); 42 | } 43 | } -------------------------------------------------------------------------------- /spring-data-jpa-examples/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | spring.jpa.hibernate.ddl-auto=update 18 | 19 | logging.level.org.springframework.orm.jpa=DEBUG -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build/ 26 | 27 | .idea/ 28 | out 29 | 30 | .gradle -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/README.md: -------------------------------------------------------------------------------- 1 | # Spring Data R2DBC Examples 2 | 3 | The example evaluates Spring Data R2DBC as an alternative to Spring Data JPA and Spring Data JDBC. 4 | 5 | Spring Data R2DBC uses Reactive Relational Database Connectivity, a reactive programming APIs to relational databases. 6 | In contrast to the blocking nature of JDBC, R2DBC is non-blocking and has a reactive API. 7 | Spring Data R2DBC used together with Spring WebFlux allows to develop fully-reactive and non-blocking applications. 8 | 9 | ## Prerequisites 10 | 11 | * JDK 11 12 | * Docker at least 1.6.0 13 | 14 | ## How to run tests 15 | 16 | To build project and run all tests use command 17 | 18 | ```bash 19 | ./gradlew cleanTest test -i 20 | ``` 21 | 22 | ## Implementation details 23 | 24 | * JDK 11 25 | * Spring Boot 2.2.x 26 | * Spring Data Release Train Neumann-RC1 27 | * Spring Data R2DBC 1.1.0.RC1 28 | * [MapStruct](https://mapstruct.org/) 1.3.1.Final 29 | * JUnit 5 30 | * [Testcontainers](https://www.testcontainers.org/) 31 | 32 | This example has a simple domain model. 33 | A book has at least one author and belongs to at least one category. 34 | A book can be rated. An average rating and a total number of ratings are tracked. 35 | 36 | Spring Data R2DBC concept is to be a simple and easy to use object mapper and 37 | does **NOT** provide many features of ORM. 38 | 39 | One-to-one, one-to-many and many-to-many relationships are not supported. 40 | 41 | Thus, in contrast to JPA, relationships must be modeled by referencing the ID 42 | and join tables for many-to-many relationships must be mapped to a Java class. 43 | 44 | **UML class diagram** 45 | 46 | ![Actual UML class diagram](img/classes.png) 47 | 48 | **Entity-relationship diagram** 49 | 50 | ![Entity-relationship diagram](img/tables.png) 51 | 52 | ## Test cases 53 | 54 | * Queries - `com.example.spring.data.r2dbc.BookRepositoryTest` 55 | * `CrudRepository.findById` 56 | * Query method with `Sort` 57 | * `@Query` with SQL and pagination 58 | * Mapping from entity to DTO using MapStruct 59 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | plugins { 18 | id 'java' 19 | id 'org.springframework.boot' version "${spring_boot_version}" 20 | id 'io.spring.dependency-management' version "${spring_dependency_management_version}" 21 | } 22 | 23 | group = 'com.example.spring-data-jdbc' 24 | sourceCompatibility = '11' 25 | 26 | configurations { 27 | compileOnly { 28 | extendsFrom annotationProcessor 29 | } 30 | testCompileOnly { 31 | extendsFrom testAnnotationProcessor 32 | } 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | maven { 38 | url 'https://repo.spring.io/libs-milestone' 39 | } 40 | } 41 | 42 | dependencyManagement { 43 | imports { 44 | mavenBom 'org.springframework.boot.experimental:spring-boot-bom-r2dbc:0.1.0.M3' 45 | mavenBom 'org.springframework.data:spring-data-releasetrain:Neumann-RC1' 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation "org.springframework.boot.experimental:spring-boot-starter-data-r2dbc" 51 | implementation "io.r2dbc:r2dbc-postgresql" 52 | implementation "org.mapstruct:mapstruct:${mapstruct_version}" 53 | annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}" 54 | annotationProcessor 'org.projectlombok:lombok' 55 | testAnnotationProcessor 'org.projectlombok:lombok' 56 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 57 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 58 | } 59 | testImplementation 'org.springframework.boot.experimental:spring-boot-test-autoconfigure-r2dbc' 60 | testImplementation 'io.projectreactor:reactor-test' 61 | testImplementation "org.testcontainers:testcontainers:${testcontainers_version}" 62 | testImplementation "org.testcontainers:postgresql:${testcontainers_version}" 63 | } 64 | 65 | test { 66 | useJUnitPlatform() 67 | } 68 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | version=1.0.0 18 | 19 | spring_boot_version=2.2.6.RELEASE 20 | spring_dependency_management_version=1.0.9.RELEASE 21 | postgresql_version=42.2.12 22 | mapstruct_version=1.3.1.Final 23 | testcontainers_version=1.14.0 -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/img/classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-r2dbc-examples/img/classes.png -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/img/classes.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Book <-- BookAuthor 4 | Book <-- BookCategory 5 | Author <-- BookAuthor 6 | Category <-- BookCategory 7 | 8 | class Book { 9 | -id : Long 10 | -isbn : String 11 | -title : String 12 | -publicationDate : LocalDate 13 | } 14 | 15 | class BookAuthor { 16 | -id : Long 17 | -book : Long 18 | -author : Long 19 | } 20 | 21 | class BookCategory { 22 | -id : Long 23 | -book : Long 24 | -category : Long 25 | } 26 | 27 | class Author { 28 | -id : Long 29 | -fullName : String 30 | } 31 | 32 | class Category { 33 | -id : Long 34 | -name : String 35 | } 36 | 37 | @enduml -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/img/tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugene-khyst/spring-data-examples/c64e12a8a54ac6825c73daee9f468ea697026a52/spring-data-r2dbc-examples/img/tables.png -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/img/tables.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | hide circle 4 | 5 | skinparam linetype ortho 6 | 7 | entity "CATEGORY" as c { 8 | *ID : SERIAL <> 9 | -- 10 | *NAME : VARCHAR(255) <> 11 | } 12 | 13 | entity "AUTHOR" as a { 14 | *ID : SERIAL <> 15 | -- 16 | *FULL_NAME : VARCHAR(255) <> 17 | } 18 | 19 | entity "BOOK" as b { 20 | *ID : SERIAL <> 21 | -- 22 | *ISBN : VARCHAR(14) <> 23 | *TITLE : VARCHAR(255) <> 24 | *PUBLICATION_DATE : DATE <> 25 | } 26 | 27 | entity "BOOK_CATEGORY" as bc { 28 | *ID : SERIAL <> 29 | -- 30 | *BOOK : INTEGER <> 31 | *CATEGORY : INTEGER <> 32 | } 33 | 34 | entity "BOOK_AUTHOR" as ba { 35 | *ID : SERIAL <> 36 | -- 37 | *BOOK : INTEGER <> 38 | *AUTHOR : INTEGER <> 39 | } 40 | 41 | entity "BOOK_RATING" as br { 42 | *ID : SERIAL <>, 43 | -- 44 | *VERSION : INTEGER <> 45 | *BOOK : INTEGER <> 46 | *RATING : NUMERIC(4, 3) <> 47 | *NUMBER_OF_RATINGS : INTEGER <> 48 | } 49 | 50 | b ||..|{ bc 51 | b ||..|{ ba 52 | b ||..o| br 53 | 54 | c ||..|{ bc 55 | a ||..|{ ba 56 | 57 | @enduml 58 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | rootProject.name = 'spring-data-r2dbc-examples' 18 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/SpringDataR2dbcApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | @SpringBootApplication 23 | public class SpringDataR2dbcApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(SpringDataR2dbcApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/dto/AuthorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class AuthorDto { 23 | 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/dto/BookDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.dto; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import lombok.Data; 23 | 24 | @Data 25 | public class BookDto { 26 | 27 | private String isbn; 28 | private String title; 29 | private LocalDate publicationDate; 30 | private List authors = new ArrayList<>(); 31 | private List categories = new ArrayList<>(); 32 | } 33 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/dto/CategoryDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.dto; 18 | 19 | import lombok.Data; 20 | 21 | @Data 22 | public class CategoryDto { 23 | 24 | private String label; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/entity/Author.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.EqualsAndHashCode; 22 | import lombok.With; 23 | import org.springframework.data.annotation.Id; 24 | import org.springframework.data.relational.core.mapping.Table; 25 | 26 | @Table 27 | @Data 28 | @AllArgsConstructor 29 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 30 | public final class Author { 31 | 32 | @Id 33 | @With 34 | private final Long id; 35 | 36 | @EqualsAndHashCode.Include 37 | private final String fullName; 38 | 39 | public static Author of(String fullName) { 40 | return new Author(null, fullName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/entity/Book.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.entity; 18 | 19 | import java.time.LocalDate; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Data; 22 | import lombok.EqualsAndHashCode; 23 | import lombok.With; 24 | import org.springframework.data.annotation.Id; 25 | import org.springframework.data.relational.core.mapping.Table; 26 | 27 | @Table 28 | @Data 29 | @AllArgsConstructor 30 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 31 | public final class Book { 32 | 33 | @Id 34 | @With 35 | private final Long id; 36 | 37 | @EqualsAndHashCode.Include 38 | private final String isbn; 39 | 40 | private final String title; 41 | 42 | private final LocalDate publicationDate; 43 | 44 | public static Book of(String isbn, String title, LocalDate publicationDate) { 45 | return new Book(null, isbn, title, publicationDate); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/entity/BookAuthor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.With; 22 | import org.springframework.data.annotation.Id; 23 | import org.springframework.data.relational.core.mapping.Table; 24 | 25 | @Table 26 | @Data 27 | @AllArgsConstructor 28 | public final class BookAuthor { 29 | 30 | @Id 31 | @With 32 | private Long id; 33 | 34 | private final Long book; 35 | 36 | private final Long author; 37 | 38 | public static BookAuthor of(Book book, Author author) { 39 | return new BookAuthor(null, book.getId(), author.getId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/entity/BookCategory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.With; 22 | import org.springframework.data.annotation.Id; 23 | import org.springframework.data.relational.core.mapping.Table; 24 | 25 | @Table 26 | @Data 27 | @AllArgsConstructor 28 | public final class BookCategory { 29 | 30 | @Id 31 | @With 32 | private Long id; 33 | 34 | private final Long book; 35 | 36 | private final Long category; 37 | 38 | public static BookCategory of(Book book, Category category) { 39 | return new BookCategory(null, book.getId(), category.getId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/entity/Category.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.entity; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | import lombok.EqualsAndHashCode; 22 | import lombok.With; 23 | import org.springframework.data.annotation.Id; 24 | import org.springframework.data.relational.core.mapping.Table; 25 | 26 | @Table 27 | @Data 28 | @AllArgsConstructor 29 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 30 | public final class Category { 31 | 32 | @Id 33 | @With 34 | private final Long id; 35 | 36 | @EqualsAndHashCode.Include 37 | private final String name; 38 | 39 | public static Category of(String name) { 40 | return new Category(null, name); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.mapper; 18 | 19 | import com.example.spring.data.r2dbc.dto.AuthorDto; 20 | import com.example.spring.data.r2dbc.dto.BookDto; 21 | import com.example.spring.data.r2dbc.dto.CategoryDto; 22 | import com.example.spring.data.r2dbc.entity.Author; 23 | import com.example.spring.data.r2dbc.entity.Book; 24 | import com.example.spring.data.r2dbc.entity.Category; 25 | import java.util.List; 26 | import org.mapstruct.Mapper; 27 | import org.mapstruct.Mapping; 28 | 29 | @Mapper(componentModel = "spring") 30 | public interface BookMapper { 31 | 32 | @Mapping(target = "authors", ignore = true) 33 | @Mapping(target = "categories", ignore = true) 34 | BookDto toBookDto(Book book); 35 | 36 | @Mapping(source = "fullName", target = "name") 37 | AuthorDto toAuthorDtos(Author author); 38 | 39 | @Mapping(source = "name", target = "label") 40 | CategoryDto toCategoryDto(Category category); 41 | 42 | List toAuthorDtos(List author); 43 | 44 | List toCategoryDtos(List category); 45 | } -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/mapper/BookMappingService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.mapper; 18 | 19 | import com.example.spring.data.r2dbc.dto.BookDto; 20 | import com.example.spring.data.r2dbc.entity.Book; 21 | import com.example.spring.data.r2dbc.repository.AuthorRepository; 22 | import com.example.spring.data.r2dbc.repository.CategoryRepository; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | import org.springframework.stereotype.Service; 25 | import org.springframework.transaction.annotation.Transactional; 26 | import reactor.core.publisher.Mono; 27 | 28 | @Transactional 29 | @Service 30 | public class BookMappingService { 31 | 32 | @Autowired 33 | private BookMapper bookMapper; 34 | 35 | @Autowired 36 | private AuthorRepository authorRepository; 37 | 38 | @Autowired 39 | private CategoryRepository categoryRepository; 40 | 41 | public Mono toBookDto(Book book) { 42 | BookDto bookDto = bookMapper.toBookDto(book); 43 | return Mono.zip( 44 | authorRepository.findByBook(book.getId()).collectList().map(bookMapper::toAuthorDtos), 45 | categoryRepository.findByBook(book.getId()).collectList().map(bookMapper::toCategoryDtos), 46 | (authorDtos, categorieDtos) -> { 47 | bookDto.setAuthors(authorDtos); 48 | bookDto.setCategories(categorieDtos); 49 | return bookDto; 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/repository/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.repository; 18 | 19 | import com.example.spring.data.r2dbc.entity.Author; 20 | import org.springframework.data.r2dbc.repository.Query; 21 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 22 | import reactor.core.publisher.Flux; 23 | 24 | public interface AuthorRepository extends ReactiveCrudRepository { 25 | 26 | @Query("SELECT a.* FROM AUTHOR a" 27 | + " JOIN BOOK_AUTHOR ba ON a.ID = ba.AUTHOR" 28 | + " WHERE ba.BOOK = :bookId" 29 | + " ORDER BY ba.ID") 30 | Flux findByBook(long bookId); 31 | } 32 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/repository/BookAuthorRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.repository; 18 | 19 | import com.example.spring.data.r2dbc.entity.BookAuthor; 20 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 21 | 22 | public interface BookAuthorRepository extends ReactiveCrudRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/repository/BookCategoryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.repository; 18 | 19 | import com.example.spring.data.r2dbc.entity.BookCategory; 20 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 21 | 22 | public interface BookCategoryRepository extends ReactiveCrudRepository { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/repository/BookRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.repository; 18 | 19 | import com.example.spring.data.r2dbc.entity.Book; 20 | import org.springframework.data.domain.Sort; 21 | import org.springframework.data.r2dbc.repository.Query; 22 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 23 | import reactor.core.publisher.Flux; 24 | import reactor.core.publisher.Mono; 25 | 26 | public interface BookRepository extends ReactiveCrudRepository { 27 | 28 | Flux findByTitleContains(String title, Sort sort); 29 | 30 | @Query("SELECT * FROM BOOK WHERE TITLE LIKE CONCAT('%', :title, '%')" 31 | + " ORDER BY PUBLICATION_DATE" 32 | + " OFFSET :start" 33 | + " FETCH NEXT :rowCount ROWS ONLY") 34 | Flux findByTitleContains(String title, int start, int rowCount, Sort sort); 35 | 36 | @Query("SELECT COUNT(*) FROM BOOK WHERE TITLE LIKE CONCAT('%', :title, '%')") 37 | Mono countByTitleContains(String title); 38 | } 39 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/main/java/com/example/spring/data/r2dbc/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc.repository; 18 | 19 | import com.example.spring.data.r2dbc.entity.Category; 20 | import org.springframework.data.r2dbc.repository.Query; 21 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 22 | import reactor.core.publisher.Flux; 23 | 24 | public interface CategoryRepository extends ReactiveCrudRepository { 25 | 26 | @Query("SELECT c.* FROM CATEGORY c" 27 | + " JOIN BOOK_CATEGORY bc ON c.ID = bc.CATEGORY" 28 | + " WHERE bc.BOOK = :bookId" 29 | + " ORDER BY bc.ID") 30 | Flux findByBook(long bookId); 31 | } 32 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/test/java/com/example/spring/data/r2dbc/AbstractContainerBaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc; 18 | 19 | import java.lang.reflect.Method; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.TestInfo; 23 | import org.testcontainers.containers.PostgreSQLContainer; 24 | 25 | @Slf4j 26 | abstract class AbstractContainerBaseTest { 27 | 28 | static final PostgreSQLContainer POSTGRE_SQL_CONTAINER; 29 | 30 | static { 31 | POSTGRE_SQL_CONTAINER = new PostgreSQLContainer(); 32 | POSTGRE_SQL_CONTAINER.start(); 33 | Runtime.getRuntime().addShutdownHook(new Thread(POSTGRE_SQL_CONTAINER::stop)); 34 | } 35 | 36 | @BeforeEach 37 | void logTestInfo(TestInfo testInfo) { 38 | log.info("{}.{}", 39 | testInfo.getTestClass().map(Class::getSimpleName).orElse("N/A"), 40 | testInfo.getTestMethod().map(Method::getName).orElse("N/A")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/test/java/com/example/spring/data/r2dbc/ConnectionFactoryConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Evgeniy Khyst 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 | 17 | package com.example.spring.data.r2dbc; 18 | 19 | import static com.example.spring.data.r2dbc.AbstractContainerBaseTest.POSTGRE_SQL_CONTAINER; 20 | import static org.testcontainers.containers.PostgreSQLContainer.POSTGRESQL_PORT; 21 | 22 | import io.r2dbc.postgresql.PostgresqlConnectionConfiguration; 23 | import io.r2dbc.postgresql.PostgresqlConnectionFactory; 24 | import io.r2dbc.spi.ConnectionFactory; 25 | import org.springframework.boot.test.context.TestConfiguration; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.core.io.ClassPathResource; 28 | import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration; 29 | import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager; 30 | import org.springframework.data.r2dbc.connectionfactory.init.CompositeDatabasePopulator; 31 | import org.springframework.data.r2dbc.connectionfactory.init.ConnectionFactoryInitializer; 32 | import org.springframework.data.r2dbc.connectionfactory.init.ResourceDatabasePopulator; 33 | import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; 34 | import org.springframework.transaction.ReactiveTransactionManager; 35 | import org.springframework.transaction.annotation.EnableTransactionManagement; 36 | 37 | @TestConfiguration 38 | @EnableR2dbcRepositories 39 | @EnableTransactionManagement 40 | public class ConnectionFactoryConfig extends AbstractR2dbcConfiguration { 41 | 42 | @Bean 43 | @Override 44 | public ConnectionFactory connectionFactory() { 45 | return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder() 46 | .host(POSTGRE_SQL_CONTAINER.getContainerIpAddress()) 47 | .port(POSTGRE_SQL_CONTAINER.getMappedPort(POSTGRESQL_PORT)) 48 | .database(POSTGRE_SQL_CONTAINER.getDatabaseName()) 49 | .username(POSTGRE_SQL_CONTAINER.getUsername()) 50 | .password(POSTGRE_SQL_CONTAINER.getPassword()) 51 | .build()); 52 | } 53 | 54 | @Bean 55 | public ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) { 56 | return new R2dbcTransactionManager(connectionFactory); 57 | } 58 | 59 | @Bean 60 | public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { 61 | ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); 62 | initializer.setConnectionFactory(connectionFactory); 63 | 64 | CompositeDatabasePopulator populator = new CompositeDatabasePopulator(); 65 | populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql"))); 66 | initializer.setDatabasePopulator(populator); 67 | 68 | return initializer; 69 | } 70 | } -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Evgeniy Khyst 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 | 17 | logging.level.org.springframework.data.r2dbc=DEBUG -------------------------------------------------------------------------------- /spring-data-r2dbc-examples/src/test/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE CATEGORY ( 2 | ID SERIAL PRIMARY KEY, 3 | NAME VARCHAR(255) NOT NULL 4 | ); 5 | 6 | CREATE TABLE AUTHOR ( 7 | ID SERIAL PRIMARY KEY, 8 | FULL_NAME VARCHAR(255) NOT NULL 9 | ); 10 | 11 | CREATE TABLE BOOK ( 12 | ID SERIAL PRIMARY KEY, 13 | ISBN VARCHAR(14) UNIQUE, 14 | TITLE VARCHAR(255) NOT NULL, 15 | PUBLICATION_DATE DATE NOT NULL 16 | ); 17 | 18 | CREATE TABLE BOOK_CATEGORY ( 19 | ID SERIAL PRIMARY KEY, 20 | BOOK INTEGER REFERENCES BOOK(ID), 21 | CATEGORY INTEGER REFERENCES CATEGORY(ID), 22 | UNIQUE(BOOK, CATEGORY) 23 | ); 24 | 25 | CREATE TABLE BOOK_AUTHOR ( 26 | ID SERIAL PRIMARY KEY, 27 | BOOK INTEGER REFERENCES BOOK(ID), 28 | AUTHOR INTEGER REFERENCES AUTHOR(ID), 29 | UNIQUE(BOOK, AUTHOR) 30 | ); --------------------------------------------------------------------------------