├── .editorconfig ├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── aggregate.drawio ├── aggregate.jpg ├── db.drawio └── db.jpg ├── settings.gradle ├── spring-data-jdbc-plus-sql-groovy-sample ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── navercorp │ │ │ └── spring │ │ │ └── sql │ │ │ └── groovy │ │ │ ├── Application.java │ │ │ ├── account │ │ │ ├── Account.java │ │ │ ├── AccountRepository.java │ │ │ ├── AccountRepositoryCustom.java │ │ │ ├── AccountRepositoryImpl.java │ │ │ └── AccountState.java │ │ │ ├── comment │ │ │ ├── Comment.java │ │ │ ├── CommentContent.java │ │ │ └── CommentRepository.java │ │ │ ├── config │ │ │ └── JdbcConfig.java │ │ │ ├── issue │ │ │ ├── Issue.java │ │ │ ├── IssueAttachedLabel.java │ │ │ ├── IssueContent.java │ │ │ ├── IssueRepository.java │ │ │ ├── IssueRepositoryCustom.java │ │ │ ├── IssueRepositoryImpl.java │ │ │ ├── Status.java │ │ │ └── sql │ │ │ │ └── IssueSql.groovy │ │ │ ├── label │ │ │ ├── Label.java │ │ │ └── LabelRepository.java │ │ │ ├── query │ │ │ ├── QuerySideDao.java │ │ │ ├── criteria │ │ │ │ ├── IssueGridCriteria.java │ │ │ │ └── IssueViewCriteria.java │ │ │ ├── grid │ │ │ │ ├── AccountGrid.java │ │ │ │ ├── CommentGrid.java │ │ │ │ ├── IssueGrid.java │ │ │ │ └── IssueRepoGrid.java │ │ │ ├── sql │ │ │ │ └── QuerySideSql.groovy │ │ │ └── view │ │ │ │ ├── AccountView.java │ │ │ │ ├── CommentView.java │ │ │ │ ├── IssueLabelView.java │ │ │ │ ├── IssueRepoView.java │ │ │ │ └── IssueView.java │ │ │ ├── repo │ │ │ ├── Repo.java │ │ │ └── RepoRepository.java │ │ │ └── support │ │ │ ├── EncryptString.java │ │ │ ├── Encryptor.java │ │ │ └── SimpleEncryptor.java │ └── resources │ │ ├── application.yml │ │ └── db │ │ └── changelog │ │ ├── changelog-0.0.1.xml │ │ └── changelog-master.xml │ └── test │ └── java │ └── com │ └── navercorp │ └── spring │ └── sql │ └── groovy │ ├── account │ └── AccountRepositoryTest.java │ ├── comment │ └── CommentRepositoryTest.java │ ├── issue │ └── IssueRepositoryTest.java │ ├── label │ └── LabelRepositoryTest.java │ ├── query │ └── QuerySideDaoTest.java │ ├── repo │ └── RepoRepositoryTest.java │ └── test │ └── DataInitializeExecutionListener.java ├── spring-data-jdbc-sample ├── build.gradle └── src │ ├── main │ ├── java │ │ └── spring │ │ │ └── data │ │ │ └── jdbc │ │ │ ├── Application.java │ │ │ ├── account │ │ │ ├── Account.java │ │ │ ├── AccountRepository.java │ │ │ ├── AccountRepositoryCustom.java │ │ │ ├── AccountRepositoryImpl.java │ │ │ └── AccountState.java │ │ │ ├── comment │ │ │ ├── Comment.java │ │ │ ├── CommentContent.java │ │ │ └── CommentRepository.java │ │ │ ├── config │ │ │ └── JdbcConfig.java │ │ │ ├── issue │ │ │ ├── Issue.java │ │ │ ├── IssueAttachedLabel.java │ │ │ ├── IssueContent.java │ │ │ ├── IssueRepository.java │ │ │ ├── IssueRepositoryCustom.java │ │ │ ├── IssueRepositoryImpl.java │ │ │ ├── Status.java │ │ │ └── sql │ │ │ │ └── IssueSql.java │ │ │ ├── label │ │ │ ├── Label.java │ │ │ └── LabelRepository.java │ │ │ ├── repo │ │ │ ├── Repo.java │ │ │ └── RepoRepository.java │ │ │ └── support │ │ │ ├── AggregateReferenceValueExtractor.java │ │ │ ├── EncryptString.java │ │ │ ├── Encryptor.java │ │ │ ├── SimpleEncryptor.java │ │ │ ├── WithInsert.java │ │ │ └── WithInsertImpl.java │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── javax.validation.valueextraction.ValueExtractor │ │ ├── application.yml │ │ └── db │ │ └── changelog │ │ ├── changelog-0.0.1.xml │ │ └── changelog-master.xml │ └── test │ └── java │ └── spring │ └── data │ └── jdbc │ ├── account │ └── AccountRepositoryTest.java │ ├── comment │ └── CommentRepositoryTest.java │ ├── issue │ └── IssueRepositoryTest.java │ ├── label │ └── LabelRepositoryTest.java │ ├── repo │ └── RepoRepositoryTest.java │ └── test │ └── DataInitializeExecutionListener.java ├── spring-data-jpa-sample ├── build.gradle └── src │ ├── main │ ├── java │ │ └── spring │ │ │ └── data │ │ │ └── jpa │ │ │ ├── Application.java │ │ │ ├── account │ │ │ ├── Account.java │ │ │ ├── AccountRepository.java │ │ │ └── AccountState.java │ │ │ ├── comment │ │ │ ├── Comment.java │ │ │ ├── CommentContent.java │ │ │ └── CommentRepository.java │ │ │ ├── issue │ │ │ ├── Issue.java │ │ │ ├── IssueAttachedLabel.java │ │ │ ├── IssueContent.java │ │ │ ├── IssueRepository.java │ │ │ └── Status.java │ │ │ ├── label │ │ │ ├── Label.java │ │ │ └── LabelRepository.java │ │ │ └── repo │ │ │ ├── Repo.java │ │ │ └── RepoRepository.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── spring │ └── data │ └── jpa │ ├── account │ └── AccountRepositoryTest.java │ ├── comment │ └── CommentRepositoryTest.java │ ├── issue │ └── IssueRepositoryTest.java │ ├── label │ └── LabelRepositoryTest.java │ ├── repo │ └── RepoRepositoryTest.java │ └── test │ └── DataInitializeExecutionListener.java └── spring-data-r2dbc-sample ├── build.gradle ├── lombok.config └── src ├── main ├── java │ └── spring │ │ └── data │ │ └── r2dbc │ │ ├── Application.java │ │ ├── account │ │ ├── Account.java │ │ ├── AccountRepository.java │ │ ├── AccountRepositoryCustom.java │ │ ├── AccountRepositoryImpl.java │ │ └── AccountState.java │ │ ├── comment │ │ ├── Comment.java │ │ ├── CommentContent.java │ │ └── CommentRepository.java │ │ ├── config │ │ └── R2dbcConfig.java │ │ ├── issue │ │ ├── Issue.java │ │ ├── IssueAttachedLabel.java │ │ ├── IssueAttachedLabelRepository.java │ │ ├── IssueContent.java │ │ ├── IssueRepository.java │ │ ├── IssueRepositoryCustom.java │ │ ├── IssueRepositoryImpl.java │ │ ├── Status.java │ │ └── sql │ │ │ └── IssueSql.java │ │ ├── label │ │ ├── Label.java │ │ └── LabelRepository.java │ │ ├── repo │ │ ├── Repo.java │ │ └── RepoRepository.java │ │ └── support │ │ ├── ClobJsonReadingConverter.java │ │ ├── ClobReadingConverter.java │ │ ├── EncryptString.java │ │ ├── Encryptor.java │ │ ├── JsonStringWritingConverter.java │ │ ├── PersistableMarkNotNewAop.java │ │ ├── RepositoryValidationAop.java │ │ ├── SimpleEncryptor.java │ │ ├── WithInsert.java │ │ └── WithInsertImpl.java └── resources │ ├── application.yml │ └── db │ └── changelog │ ├── changelog-0.0.1.xml │ └── changelog-master.xml └── test └── java └── spring └── data └── r2dbc ├── account └── AccountRepositoryTest.java ├── comment └── CommentRepositoryTest.java ├── issue └── IssueRepositoryTest.java ├── label └── LabelRepositoryTest.java ├── repo └── RepoRepositoryTest.java └── test └── DataInitializeExecutionListener.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*.{kt, kts}] 5 | disabled_rules = import-ordering 6 | 7 | # 4 space indentation 8 | [*.java] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 4 12 | indent_style = space 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | max_line_length = 120 16 | tab_width = 4 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | /bin/ 5 | /env.bat 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | out/ 22 | 23 | ### Mac OS ### 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Data Sample Codes 2 | 3 | [발표 자료](https://docs.google.com/presentation/d/1E7Y_L8TO6ZRZfFjBO6f_GBZxzdV0Klw0XNuH1Kv4JWA/edit) 4 | 5 | ![aggregates](./img/aggregate.jpg) 6 | ![db](./img/db.jpg) 7 | 8 | ## [Spring Data JPA Sample](./spring-data-jpa-sample) 9 | 10 | - 2.2.6.RELEASE 11 | - hibernate-orm 5.4.12.Final 12 | - spring-data-commons 2.6.6.RELEASE 13 | 14 | ## [Spring Data JDBC Sample](./spring-data-jdbc-sample) 15 | 16 | - 2.0.0.RC2 17 | - spring-data-relational 2.0.0.RC2 18 | - spring-data-commons 2.3.0.RC2 19 | 20 | ## [Spring Data R2DBC Sample](./spring-data-r2dbc-sample) 21 | 22 | - 1.1.0.RC2 23 | - spring-data-relational 2.0.0.RC2 24 | - spring-data-commons 2.3.0.RC2 25 | 26 | ## [Spring Data JDBC Plus Sample](./spring-data-jdbc-plus-sql-groovy-sample) 27 | 28 | - [Spring JDBC Plus](https://github.com/naver/spring-jdbc-plus) 2.0.0.RC2 29 | - spring-data-jdbc 2.0.0.RC2 30 | - spring-data-relational 2.0.0.RC2 31 | - spring-data-commons 2.3.0.RC2 32 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.springframework.boot.gradle.plugin.SpringBootPlugin 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | mavenLocal() 7 | maven { 8 | url "https://repo.spring.io/milestone/" 9 | } 10 | } 11 | dependencies { 12 | classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.6.RELEASE") 13 | classpath("org.hibernate.build.gradle:gradle-maven-publish-auth:2.0.1") 14 | } 15 | } 16 | 17 | allprojects { 18 | group = "com.navercorp" 19 | version "1.0.0-SNAPSHOT" 20 | } 21 | 22 | subprojects { 23 | apply plugin: "java" 24 | apply plugin: "idea" 25 | apply plugin: "io.spring.dependency-management" 26 | apply plugin: "maven" 27 | 28 | sourceCompatibility = JavaVersion.VERSION_11 29 | targetCompatibility = JavaVersion.VERSION_11 30 | 31 | repositories { 32 | mavenCentral() 33 | maven { 34 | url "https://repo.spring.io/milestone/" 35 | } 36 | maven { 37 | name "navercorp.release" 38 | url "http://repo.navercorp.com/maven2/" 39 | } 40 | maven { 41 | name "navercorp.snapshot" 42 | url "http://repo.navercorp.com/m2-snapshot-repository/" 43 | } 44 | } 45 | 46 | dependencies { 47 | compileOnly("com.google.code.findbugs:jsr305:3.0.2") 48 | testCompile("org.assertj:assertj-core") 49 | } 50 | 51 | dependencyManagement { 52 | imports { 53 | mavenBom SpringBootPlugin.BOM_COORDINATES 54 | } 55 | } 56 | 57 | test { 58 | useJUnitPlatform() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhyeon-lee/spring-data-sample-codes/99c96ad491e379682969e8342f70073749493086/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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='"-Xmx64m"' 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 | -------------------------------------------------------------------------------- /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="-Xmx64m" 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 | -------------------------------------------------------------------------------- /img/aggregate.drawio: -------------------------------------------------------------------------------- 1 | 7Vtdc5s4FP01fnQGSWDjx9hNdzvT3ck0s7vtU0cG2WaDkVfIjd1fvwIkPiSMXX9g4sQvQRdJSOceHeleSA9NlpvfGF4t/qA+CXvQ8jc99KEHIbBGUPxJLNvM4oJBZpizwM9MVmF4Cn4S1VJa14FPYmnLTJzSkAerqtGjUUQ8XrFhxuhLtdqMhn7FsMJzYhiePBya1n8Cny/kLOCwsP9OgvlCPRkMRtmdJVaV5UziBfbpS8mEHnpowijl2dVyMyFhAl4Vl4877uYDYyTihzQI19GXx+8wht7fwV8OvZ/8N930gZN18wOHazljOVq+VRAs+DIUV6CHxrIyYZxsdg4D5JMTrCB0STjbiiqyge1KAkhCQADv5CBeCoDBUMK4KIOLZFMsnTrPey/mLS7k1H8FhsF+GIT/VsmlR5crGqWzHf9LON8qXrjKkBPCPhSyBt/sBBIYoCmmlTFTNkZCzIMfVVbX4Sif90gDMbj8YX1FeOm1YbUDOpvFhBtuyAd9gmeG1yYoct2r09PtIj2H7/TsA3R1esKrs9PuIjvROzv7ALTNTnWe2FaLJZyRg2rIORjuBvU0csIuknMPiG+CnKMrcxMiE+dacsKheylyWh0k5+idm31TNz/Fsbg0nMMZfc4DQzGNMaPryCe+ZC1mngxphavR+GURcPK0wl5ieREBs7CVOI7DYB4lrhYOIkwYZjTiqv3BTm1eBNC2q6tgYBkeBVaNS3Pj2VeBqdAp2PecY29B/M94muQUXj3yyK0AXxtW2G4d8PBCwJtn1xT4iZh7MqfXD/lA47ozMiFXktsO5OZR+d7zBIq3gLYDqgy3a9BuV1nM5NaELpe3wW1dyK+PtplDk2jfjqDY+xmOWpVwMzv2hazoDSCti0ndab1lfptJuFs5mhjKraLm64ENzPDQ2yEjJPLvkzc9ojQNqfcsMBGmj0GoICyhSfw5eZJNKeMLOqcRDh8Ka443y2Y5ToANPBzeSzuniYNMH4cJFx5pHPCAJvVCMis3/6zdnlLO6TJ3XzKuI52ngpUTYy0R+1Y4ACwt2orpmnlEtiq8a3QERuBuT1ccsznhRlfnit6gGVYDgzWMxMFPPE2LVv2S01yoGJC7LnX5GHvP83SBT2hIWdo5mqU/bbFaubfly0r59F4eLh+VS2qO18/Ejr4NKi7ttxaKq/P5uxC0JgTVgyUCRwuBpQmB0dWlhcCMcG9XCLKFcsNCYMbOuJwiijuqB2QT8K8Jt+6gI4vf1EPF9YeN5F1a2KpCJMD6qqolhW9pFzbKDUXDtFRp+UhYIPBOzoup8dKaJGBPRaFpn8rqZSu+yc2taJw6XkoWO1BL8R+scUNH0zijq0trnJnk+PN2Nc5uReOG1dcOKo3YgsaZORQm4vlPfpO20RWJKtJmHSttOzhQp2xJxz6OF3lUm90u0cJKfzXiowto509EyNZPRNqnFIeqhe2i5o4urRVmCsNjBHPij7dvkmH5NpvvnNk26+zZZA/fF/fud3s+VDkbhasbHhyi4yjsONW1APSOLk3hmrzQm6Zwd0RSe1dvH0cw/fs2vZ8L8wuZuaMgeSna1hb8K9HFCfR6dUmJkSY7zpFbsN4RVPmltvhlflqS+uaN8qsIjgfuqBwd31l7AmRR0KPcYjeXobba0PfFzIdv53vDXHjott/89fS5RNmqiukAWMctG9t2mjvasWwEgfG2VG2VVIh3DxjYVuU5jnzjuvM4gkBTfXGRjeC8a9hMSXcgSsvXVGtnkEwziiVcTm9Zjau3K9uKM9BefFpH5oH0jpDT8rHFTI5241h8LVZ2lWEQHnlw0TsCzkhlHk/mmCgW/4eYVS/+mxM9/A8= -------------------------------------------------------------------------------- /img/aggregate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhyeon-lee/spring-data-sample-codes/99c96ad491e379682969e8342f70073749493086/img/aggregate.jpg -------------------------------------------------------------------------------- /img/db.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /img/db.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhyeon-lee/spring-data-sample-codes/99c96ad491e379682969e8342f70073749493086/img/db.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "spring-data-sample-codes" 2 | 3 | include "spring-data-jpa-sample" 4 | include "spring-data-jdbc-sample" 5 | include "spring-data-r2dbc-sample" 6 | include "spring-data-jdbc-plus-sql-groovy-sample" 7 | 8 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java" 3 | id "groovy" 4 | } 5 | 6 | compileJava { 7 | dependsOn compileGroovy 8 | } 9 | 10 | compileGroovy { 11 | options.encoding = "UTF-8" 12 | groovyOptions.encoding = "UTF-8" 13 | } 14 | 15 | tasks.withType(JavaCompile) { task -> 16 | dependsOn task.name.replace("Java", "Groovy") 17 | } 18 | 19 | tasks.withType(GroovyCompile) { 20 | dependsOn = [] 21 | } 22 | 23 | sourceSets { 24 | main { 25 | java { srcDirs = [] } 26 | groovy { srcDirs += ["src/main/java"] } 27 | } 28 | } 29 | 30 | if (project.convention.findPlugin(JavaPluginConvention)) { 31 | // Change the output directory for the main and test source sets back to the old path 32 | sourceSets.main.java.outputDir = new File(buildDir, "classes/main") 33 | sourceSets.main.groovy.outputDir = new File(buildDir, "classes/main") 34 | sourceSets.test.java.outputDir = new File(buildDir, "classes/test") 35 | sourceSets.test.groovy.outputDir = new File(buildDir, "classes/test") 36 | } 37 | 38 | dependencies { 39 | implementation("com.navercorp.spring:spring-boot-starter-data-jdbc-plus-sql:2.0.0.RC2") 40 | implementation("com.navercorp.spring:spring-boot-starter-data-jdbc-plus-repository:2.0.0.RC2") 41 | 42 | implementation("org.springframework.boot:spring-boot-starter") 43 | implementation("org.springframework.data:spring-data-jdbc:2.0.0.RC2") 44 | implementation("org.springframework.data:spring-data-relational:2.0.0.RC2") 45 | implementation("org.springframework.data:spring-data-commons:2.3.0.RC2") 46 | implementation("org.liquibase:liquibase-core") 47 | 48 | implementation("org.codehaus.groovy:groovy:3.0.3") 49 | 50 | compileOnly("org.projectlombok:lombok") 51 | annotationProcessor("org.projectlombok:lombok") 52 | 53 | runtimeOnly("com.h2database:h2") 54 | 55 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 56 | exclude group: "org.junit.vintage", module: "junit-vintage-engine" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/Application.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/account/Account.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | import com.navercorp.spring.sql.groovy.support.EncryptString; 4 | import lombok.Builder; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.relational.core.mapping.Table; 10 | 11 | import java.time.Instant; 12 | import java.util.UUID; 13 | 14 | @Table 15 | @Builder 16 | @Getter 17 | @EqualsAndHashCode(of = "id") 18 | @ToString 19 | public class Account { 20 | @Id 21 | private UUID id; 22 | 23 | private String loginId; 24 | 25 | private String name; 26 | 27 | private AccountState state; 28 | 29 | private EncryptString email; 30 | 31 | @Builder.Default 32 | private Instant createdAt = Instant.now(); 33 | 34 | public void lock() { 35 | this.state = AccountState.LOCKED; 36 | } 37 | 38 | public void delete() { 39 | this.state = AccountState.DELETED; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/account/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | import com.navercorp.spring.data.jdbc.plus.repository.JdbcRepository; 4 | 5 | import java.util.EnumSet; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.UUID; 9 | 10 | public interface AccountRepository extends JdbcRepository, AccountRepositoryCustom { 11 | @Override 12 | void deleteById(UUID id); 13 | 14 | @Override 15 | void delete(Account entity); 16 | 17 | @Override 18 | void deleteAll(Iterable entities); 19 | 20 | @Override 21 | void deleteAll(); 22 | 23 | Optional findByIdAndStateIn(UUID uuid, Set states); 24 | 25 | default Optional findByIdExcludeDeleted(UUID id) { 26 | return this.findByIdAndStateIn(id, EnumSet.of(AccountState.ACTIVE, AccountState.LOCKED)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/account/AccountRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | public interface AccountRepositoryCustom { 4 | } 5 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/account/AccountRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | import org.springframework.dao.TransientDataAccessResourceException; 4 | import org.springframework.data.jdbc.core.JdbcAggregateOperations; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import java.util.UUID; 8 | 9 | public class AccountRepositoryImpl implements AccountRepositoryCustom { 10 | private final JdbcAggregateOperations jdbcAggregateOperations; 11 | 12 | public AccountRepositoryImpl(JdbcAggregateOperations jdbcAggregateOperations) { 13 | this.jdbcAggregateOperations = jdbcAggregateOperations; 14 | } 15 | 16 | @Transactional 17 | public void deleteById(UUID id) { 18 | Account account = this.jdbcAggregateOperations.findById(id, Account.class); 19 | if (account == null) { 20 | throw new TransientDataAccessResourceException("account does not exist.id: " + id); 21 | } 22 | 23 | this.delete(account); 24 | } 25 | 26 | @Transactional 27 | public void delete(Account entity) { 28 | entity.delete(); 29 | this.jdbcAggregateOperations.update(entity); 30 | } 31 | 32 | @Transactional 33 | public void deleteAll(Iterable entities) { 34 | entities.forEach(this::delete); 35 | } 36 | 37 | @Transactional 38 | public void deleteAll() { 39 | Iterable accounts = this.jdbcAggregateOperations.findAll(Account.class); 40 | this.deleteAll(accounts); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/account/AccountState.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | public enum AccountState { 4 | ACTIVE, LOCKED, DELETED 5 | } 6 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/comment/Comment.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.comment; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import com.navercorp.spring.sql.groovy.issue.Issue; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.annotation.Version; 10 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 11 | import org.springframework.data.relational.core.mapping.Column; 12 | import org.springframework.data.relational.core.mapping.Table; 13 | 14 | import java.time.Instant; 15 | import java.util.UUID; 16 | 17 | @Table 18 | @Getter 19 | @EqualsAndHashCode(of = "id") 20 | @ToString 21 | public class Comment { 22 | @Id 23 | private Long id; 24 | 25 | @Version 26 | private long version; 27 | 28 | private AggregateReference issueId; 29 | 30 | @Column("ID") // PK MAPPING, default: "COMMENT" 31 | private CommentContent content; 32 | 33 | private AggregateReference createdBy; 34 | 35 | private Instant createdAt; 36 | 37 | public Comment( 38 | AggregateReference issueId, 39 | CommentContent content, 40 | AggregateReference createdBy) { 41 | 42 | this.issueId = issueId; 43 | this.content = content; 44 | this.createdBy = createdBy; 45 | this.createdAt = Instant.now(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/comment/CommentContent.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.comment; 2 | 3 | import lombok.*; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.annotation.PersistenceConstructor; 6 | 7 | @Value 8 | @AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = @__(@PersistenceConstructor)) 9 | public class CommentContent { 10 | @Id 11 | @With 12 | Long id; 13 | 14 | String body; 15 | 16 | String mimeType; 17 | 18 | public CommentContent(String body, String mimeType) { 19 | this.id = null; 20 | this.body = body; 21 | this.mimeType = mimeType; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/comment/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.comment; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | public interface CommentRepository extends CrudRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/config/JdbcConfig.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.config; 2 | 3 | import com.navercorp.spring.data.jdbc.plus.sql.config.JdbcPlusSqlConfiguration; 4 | import com.navercorp.spring.data.jdbc.plus.sql.parametersource.EntityConvertibleSqlParameterSourceFactory; 5 | import com.navercorp.spring.data.jdbc.plus.sql.parametersource.SqlParameterSourceFactory; 6 | import com.navercorp.spring.jdbc.plus.support.parametersource.ConvertibleParameterSourceFactory; 7 | import com.navercorp.spring.jdbc.plus.support.parametersource.converter.DefaultJdbcParameterSourceConverter; 8 | import com.navercorp.spring.jdbc.plus.support.parametersource.converter.JdbcParameterSourceConverter; 9 | import com.navercorp.spring.jdbc.plus.support.parametersource.converter.Unwrapper; 10 | import com.navercorp.spring.sql.groovy.label.Label.LabelAfterSaveEventListener; 11 | import com.navercorp.spring.sql.groovy.repo.Repo.RepoBeforeSaveCallback; 12 | import com.navercorp.spring.sql.groovy.support.EncryptString; 13 | import com.navercorp.spring.sql.groovy.support.Encryptor; 14 | import com.navercorp.spring.sql.groovy.support.SimpleEncryptor; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.core.convert.converter.Converter; 18 | import org.springframework.data.convert.ReadingConverter; 19 | import org.springframework.data.convert.WritingConverter; 20 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 21 | import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; 22 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 23 | import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; 24 | import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; 25 | import org.springframework.data.relational.core.dialect.Dialect; 26 | import org.springframework.data.relational.core.dialect.H2Dialect; 27 | import org.springframework.data.relational.core.sql.IdentifierProcessing; 28 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 29 | import org.springframework.lang.Nullable; 30 | 31 | import java.sql.Clob; 32 | import java.sql.SQLException; 33 | import java.util.List; 34 | 35 | @Configuration 36 | public class JdbcConfig extends AbstractJdbcConfiguration { 37 | @Bean 38 | public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { 39 | return new H2Dialect() { 40 | @Override 41 | public IdentifierProcessing getIdentifierProcessing() { 42 | // SQL 작성시 컬럼 대상을 Quoting 해야 할 경우 불편하기 때문에 NONE 으로 설정한다. 43 | // Quoting 여부에 따른 호환은 DBMS 구현에 따른다. 44 | return IdentifierProcessing.create( 45 | IdentifierProcessing.Quoting.NONE, IdentifierProcessing.LetterCasing.UPPER_CASE); 46 | } 47 | }; 48 | } 49 | 50 | @Bean 51 | @Override 52 | public JdbcCustomConversions jdbcCustomConversions() { 53 | Encryptor encryptor = new SimpleEncryptor(); 54 | return new JdbcCustomConversions(List.of( 55 | new EncryptStringWritingConverter(encryptor), 56 | new EncryptStringReadingConverter(encryptor), 57 | new Converter() { 58 | @Nullable 59 | @Override 60 | public String convert(Clob clob) { 61 | try { 62 | return Math.toIntExact(clob.length()) == 0 63 | ? "" : clob.getSubString(1, Math.toIntExact(clob.length())); 64 | 65 | } catch (SQLException e) { 66 | throw new IllegalStateException("Failed to convert CLOB to String.", e); 67 | } 68 | } 69 | })); 70 | } 71 | 72 | @Bean 73 | LabelAfterSaveEventListener labelAfterSaveEventListener() { 74 | return new LabelAfterSaveEventListener(); 75 | } 76 | 77 | @Bean 78 | RepoBeforeSaveCallback repoBeforeSaveCallback() { 79 | return new RepoBeforeSaveCallback(); 80 | } 81 | 82 | @WritingConverter 83 | static class EncryptStringWritingConverter implements Converter { 84 | private final Encryptor encryptor; 85 | 86 | public EncryptStringWritingConverter(Encryptor encryptor) { 87 | this.encryptor = encryptor; 88 | } 89 | 90 | @Override 91 | public byte[] convert(EncryptString source) { 92 | return this.encryptor.encrypt(source.getValue()); 93 | } 94 | } 95 | 96 | @ReadingConverter 97 | static class EncryptStringReadingConverter implements Converter { 98 | private final Encryptor encryptor; 99 | 100 | public EncryptStringReadingConverter(Encryptor encryptor) { 101 | this.encryptor = encryptor; 102 | } 103 | 104 | @Override 105 | public EncryptString convert(byte[] source) { 106 | String value = this.encryptor.decrypt(source); 107 | if (value == null) { 108 | return null; 109 | } 110 | 111 | return new EncryptString(value); 112 | } 113 | } 114 | 115 | @Configuration 116 | static class JdbcSqlConfig extends JdbcPlusSqlConfiguration { 117 | @Bean 118 | @Override 119 | public SqlParameterSourceFactory sqlParameterSourceFactory( 120 | JdbcMappingContext jdbcMappingContext, JdbcConverter jdbcConverter, Dialect dialect) { 121 | 122 | return new EntityConvertibleSqlParameterSourceFactory( 123 | this.parameterSourceConverter(), 124 | jdbcMappingContext, 125 | jdbcConverter, 126 | dialect.getIdentifierProcessing()); 127 | } 128 | 129 | private ConvertibleParameterSourceFactory parameterSourceConverter() { 130 | JdbcParameterSourceConverter converter = new DefaultJdbcParameterSourceConverter( 131 | List.of(), List.of(new IdOnlyAggregateReferenceUnwrapper()) 132 | ); 133 | ConvertibleParameterSourceFactory parameterSourceFactory = new ConvertibleParameterSourceFactory(converter, null); 134 | parameterSourceFactory.setPaddingIterableParam(true); 135 | return parameterSourceFactory; 136 | } 137 | } 138 | 139 | static class IdOnlyAggregateReferenceUnwrapper implements Unwrapper { 140 | @Nullable 141 | @Override 142 | public Object unwrap(AggregateReference.IdOnlyAggregateReference source) { 143 | return source.getId(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/Issue.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import com.navercorp.spring.sql.groovy.repo.Repo; 5 | import lombok.Builder; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.Getter; 8 | import lombok.ToString; 9 | import org.springframework.data.annotation.Id; 10 | import org.springframework.data.annotation.Version; 11 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 12 | import org.springframework.data.relational.core.mapping.Column; 13 | import org.springframework.data.relational.core.mapping.MappedCollection; 14 | import org.springframework.data.relational.core.mapping.Table; 15 | 16 | import java.time.Instant; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.UUID; 21 | 22 | @Table 23 | @Builder 24 | @Getter 25 | @EqualsAndHashCode(of = "id") 26 | @ToString 27 | public class Issue { 28 | @Id 29 | private UUID id; 30 | 31 | @Version 32 | private long version; 33 | 34 | private AggregateReference repoId; 35 | 36 | private Long issueNo; 37 | 38 | private Status status; 39 | 40 | private String title; 41 | 42 | @Column("ISSUE_ID") // default: "ISSUE" 43 | private IssueContent content; 44 | 45 | @MappedCollection(idColumn = "ISSUE_ID", keyColumn = "ATTACHED_AT") 46 | @Builder.Default 47 | private List attachedLabels = new ArrayList<>(); 48 | 49 | private AggregateReference createdBy; 50 | 51 | @Builder.Default 52 | private Instant createdAt = Instant.now(); 53 | 54 | public void changeContent(IssueContent content) { 55 | this.content = content; 56 | } 57 | 58 | public List getAttachedLabels() { 59 | return Collections.unmodifiableList(this.attachedLabels); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/IssueAttachedLabel.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import com.navercorp.spring.sql.groovy.label.Label; 4 | import lombok.Builder; 5 | import lombok.Value; 6 | import lombok.With; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 9 | 10 | import java.time.Instant; 11 | import java.util.UUID; 12 | 13 | @Value 14 | @Builder 15 | public class IssueAttachedLabel { 16 | @Id 17 | @With 18 | Long id; 19 | 20 | AggregateReference labelId; 21 | 22 | Instant attachedAt; 23 | } 24 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/IssueContent.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import lombok.*; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.annotation.PersistenceConstructor; 6 | 7 | @Value 8 | @AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = @__(@PersistenceConstructor)) 9 | public class IssueContent { 10 | @Id 11 | @With 12 | Long id; 13 | 14 | String body; 15 | 16 | String mimeType; 17 | 18 | public IssueContent(String body, String mimeType) { 19 | this.id = null; 20 | this.body = body; 21 | this.mimeType = mimeType; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/IssueRepository.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.jdbc.repository.query.Query; 5 | import org.springframework.data.repository.PagingAndSortingRepository; 6 | import org.springframework.data.repository.query.Param; 7 | 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | public interface IssueRepository extends PagingAndSortingRepository, IssueRepositoryCustom { 12 | List findByTitleLikeAndStatus(String titleStartAt, Status status, Pageable pageable); 13 | 14 | @Query("SELECT COUNT(*) FROM ISSUE WHERE title LIKE :titleStartAt AND status = :status") 15 | long countByTitleLikeAndStatus(@Param("titleStartAt") String titleStartAt, @Param("status") Status status); 16 | } 17 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/IssueRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import com.navercorp.spring.sql.groovy.label.Label; 4 | import com.navercorp.spring.sql.groovy.repo.Repo; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 8 | 9 | import java.util.UUID; 10 | 11 | public interface IssueRepositoryCustom { 12 | Page findByRepoIdAndAttachedLabelsLabelId( 13 | AggregateReference repoId, 14 | AggregateReference labelId, 15 | Pageable pageable); 16 | } 17 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/IssueRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | import com.navercorp.spring.data.jdbc.plus.sql.provider.EntityJdbcProvider; 4 | import com.navercorp.spring.data.jdbc.plus.sql.support.JdbcRepositorySupport; 5 | import com.navercorp.spring.data.jdbc.plus.sql.support.trait.SingleValueSelectTrait; 6 | import com.navercorp.spring.sql.groovy.issue.sql.IssueSql; 7 | import com.navercorp.spring.sql.groovy.label.Label; 8 | import com.navercorp.spring.sql.groovy.repo.Repo; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 12 | import org.springframework.data.repository.support.PageableExecutionUtils; 13 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 14 | 15 | import java.util.List; 16 | import java.util.UUID; 17 | 18 | public class IssueRepositoryImpl extends JdbcRepositorySupport 19 | implements IssueRepositoryCustom, SingleValueSelectTrait { 20 | 21 | private final IssueSql sqls; 22 | 23 | @SuppressWarnings("unchecked") 24 | public IssueRepositoryImpl(EntityJdbcProvider entityJdbcProvider) { 25 | 26 | super(Issue.class, entityJdbcProvider); 27 | this.sqls = sqls(IssueSql::new); 28 | } 29 | 30 | @Override 31 | public Page findByRepoIdAndAttachedLabelsLabelId( 32 | AggregateReference repoId, 33 | AggregateReference labelId, 34 | Pageable pageable) { 35 | 36 | SqlParameterSource parameterSource = mapParameterSource() 37 | .addValue("repoId", repoId.getId()) 38 | .addValue("labelId", labelId.getId()) 39 | .addValue("offset", pageable.getOffset()) 40 | .addValue("pageSize", pageable.getPageSize()); 41 | 42 | List content = find(this.sqls.selectByRepoIdAndAttachedLabelsLabelId(pageable.getSort()), parameterSource); 43 | return PageableExecutionUtils.getPage(content, pageable, 44 | () -> selectSingleValue(this.sqls.countByRepoIdAndAttachedLabelsLabelId(), parameterSource, Long.class)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/Status.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue; 2 | 3 | public enum Status { 4 | OPEN, CLOSED 5 | } 6 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/issue/sql/IssueSql.groovy: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.issue.sql 2 | 3 | import com.navercorp.spring.data.jdbc.plus.sql.support.SqlGeneratorSupport 4 | import com.navercorp.spring.sql.groovy.issue.Issue 5 | import org.springframework.data.domain.Sort 6 | 7 | import static java.util.stream.Collectors.joining 8 | 9 | class IssueSql extends SqlGeneratorSupport { 10 | 11 | String selectByRepoIdAndAttachedLabelsLabelId(Sort sort) { 12 | """ 13 | SELECT ${sql.aggregateColumns(Issue)} 14 | FROM ${sql.aggregateTables(Issue)} 15 | WHERE 16 | REPO_ID = :repoId 17 | AND attachedLabels.LABEL_ID = :labelId 18 | ORDER BY ${orderBy(sort)} 19 | LIMIT :pageSize OFFSET :offset 20 | """ 21 | } 22 | 23 | String countByRepoIdAndAttachedLabelsLabelId() { 24 | """ 25 | SELECT COUNT(*) 26 | FROM ${sql.aggregateTables(Issue)} 27 | WHERE 28 | REPO_ID = :repoId 29 | AND attachedLabels.LABEL_ID = :labelId 30 | """ 31 | } 32 | 33 | // TODO: SORT property ORDER BY 는 COLUMN 으로 변환해야 한다. 34 | // (https://github.com/spring-projects/spring-data-jdbc/pull/210) 35 | static CharSequence orderBy(Sort sort) { 36 | return sort.stream() 37 | .map(order -> order.getProperty() + " " + order.getDirection().name()) 38 | .collect(joining(", ")) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/label/Label.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.label; 2 | 3 | import com.navercorp.spring.sql.groovy.repo.Repo; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.core.Ordered; 9 | import org.springframework.data.annotation.Id; 10 | import org.springframework.data.annotation.PersistenceConstructor; 11 | import org.springframework.data.annotation.Transient; 12 | import org.springframework.data.domain.Persistable; 13 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 14 | import org.springframework.data.relational.core.mapping.Table; 15 | import org.springframework.data.relational.core.mapping.event.AfterSaveEvent; 16 | 17 | import java.util.UUID; 18 | 19 | @Table 20 | @Getter 21 | @EqualsAndHashCode(of = "id") 22 | @ToString 23 | public class Label implements Persistable { 24 | @Id 25 | private UUID id; 26 | 27 | private AggregateReference repoId; 28 | 29 | private String name; 30 | 31 | private String color; 32 | 33 | @Transient 34 | private boolean isNew = true; 35 | 36 | public Label(AggregateReference repoId, String name, String color) { 37 | this.id = UUID.randomUUID(); 38 | this.repoId = repoId; 39 | this.name = name; 40 | this.color = color; 41 | } 42 | 43 | @PersistenceConstructor 44 | private Label(UUID id, AggregateReference repoId, String name, String color) { 45 | this.id = id; 46 | this.repoId = repoId; 47 | this.name = name; 48 | this.color = color; 49 | this.isNew = false; 50 | } 51 | 52 | public static class LabelAfterSaveEventListener implements ApplicationListener>, Ordered { 53 | @Override 54 | public void onApplicationEvent(AfterSaveEvent event) { 55 | if (event.getEntity() instanceof Label) { 56 | ((Label) event.getEntity()).isNew = false; 57 | } 58 | } 59 | 60 | @Override 61 | public int getOrder() { 62 | return Ordered.HIGHEST_PRECEDENCE; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/label/LabelRepository.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.label; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import java.util.UUID; 6 | 7 | public interface LabelRepository extends CrudRepository { 8 | } 9 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/QuerySideDao.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query; 2 | 3 | import com.navercorp.spring.data.jdbc.plus.sql.provider.EntityJdbcProvider; 4 | import com.navercorp.spring.data.jdbc.plus.sql.support.JdbcDaoSupport; 5 | import com.navercorp.spring.data.jdbc.plus.sql.support.trait.SingleValueSelectTrait; 6 | import com.navercorp.spring.sql.groovy.query.criteria.IssueGridCriteria; 7 | import com.navercorp.spring.sql.groovy.query.criteria.IssueViewCriteria; 8 | import com.navercorp.spring.sql.groovy.query.grid.IssueGrid; 9 | import com.navercorp.spring.sql.groovy.query.sql.QuerySideSql; 10 | import com.navercorp.spring.sql.groovy.query.view.IssueView; 11 | import org.springframework.data.domain.Page; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.data.repository.support.PageableExecutionUtils; 14 | import org.springframework.jdbc.core.SingleColumnRowMapper; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.List; 18 | import java.util.UUID; 19 | 20 | @Component 21 | public class QuerySideDao extends JdbcDaoSupport implements SingleValueSelectTrait { 22 | private final QuerySideSql sqls; 23 | 24 | protected QuerySideDao(EntityJdbcProvider entityJdbcProvider) { 25 | super(entityJdbcProvider); 26 | this.sqls = super.sqls(QuerySideSql::new); 27 | } 28 | 29 | public Page selectIssueGrid(IssueGridCriteria criteria, Pageable pageable) { 30 | List content = this.select( 31 | this.sqls.selectIssueGrids(criteria, pageable.getSort()), 32 | compositeSqlParameterSource( 33 | beanParameterSource(criteria), 34 | mapParameterSource() 35 | .addValue("pageSize", pageable.getPageSize()) 36 | .addValue("offset", pageable.getOffset()) 37 | ), 38 | IssueGrid.class 39 | ); 40 | 41 | return PageableExecutionUtils.getPage(content, pageable, () -> 42 | this.selectSingleValue(this.sqls.countIssueGrids(criteria), beanParameterSource(criteria), Long.class)); 43 | } 44 | 45 | public IssueView selectIssueView(UUID issueId) { 46 | return this.requiredOne( 47 | this.sqls.selectIssueView(), 48 | mapParameterSource().addValue("issueId", issueId), 49 | this.getAggregateResultSetExtractor(IssueView.class)); 50 | } 51 | 52 | public Page selectIssueViews(IssueViewCriteria criteria, Pageable pageable) { 53 | // 1. 결과 대상 filter 54 | List issueIds = this.select( 55 | this.sqls.selectIssueViewIds(criteria, pageable.getSort()), 56 | compositeSqlParameterSource( 57 | beanParameterSource(criteria), 58 | mapParameterSource() 59 | .addValue("pageSize", pageable.getPageSize()) 60 | .addValue("offset", pageable.getOffset()) 61 | ), new SingleColumnRowMapper<>(UUID.class) 62 | ); 63 | 64 | // 2. filter 된 ID 기반으로 Aggregate 조회 65 | List content = this.select( 66 | this.sqls.selectIssueViews(pageable.getSort()), 67 | mapParameterSource() 68 | .addValue("issueIds", issueIds), 69 | this.getAggregateResultSetExtractor(IssueView.class) 70 | ); 71 | 72 | // 3. 결과 + COUNT -> Paging 73 | return PageableExecutionUtils.getPage(content, pageable, () -> 74 | this.selectSingleValue(this.sqls.countIssueViews(criteria), beanParameterSource(criteria), Long.class)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/criteria/IssueGridCriteria.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.criteria; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import com.navercorp.spring.sql.groovy.issue.Status; 5 | import lombok.Builder; 6 | import lombok.Value; 7 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 8 | 9 | import java.util.UUID; 10 | 11 | @Value 12 | @Builder 13 | public class IssueGridCriteria { 14 | Status status; 15 | 16 | AggregateReference createdBy; 17 | 18 | String searchRepoName; 19 | 20 | String searchContent; 21 | } 22 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/criteria/IssueViewCriteria.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.criteria; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import com.navercorp.spring.sql.groovy.issue.Status; 5 | import com.navercorp.spring.sql.groovy.label.Label; 6 | import lombok.Builder; 7 | import lombok.Value; 8 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 9 | 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | @Value 14 | @Builder 15 | public class IssueViewCriteria { 16 | Status status; 17 | 18 | AggregateReference createdBy; 19 | 20 | List> labelIds; 21 | 22 | String searchRepoName; 23 | 24 | String searchContent; 25 | } 26 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/grid/AccountGrid.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.grid; 2 | 3 | import lombok.Getter; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | import java.util.UUID; 8 | 9 | @Table("ACCOUNT") 10 | @Getter 11 | public class AccountGrid { 12 | @Id 13 | UUID id; 14 | 15 | String loginId; 16 | 17 | String name; 18 | } 19 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/grid/CommentGrid.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.grid; 2 | 3 | import com.navercorp.spring.sql.groovy.comment.CommentContent; 4 | import lombok.Value; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | import java.time.Instant; 9 | 10 | @Table("COMMENT") 11 | @Value 12 | public class CommentGrid { 13 | @Id 14 | Long id; 15 | 16 | CommentContent content; 17 | 18 | AccountGrid creator; 19 | 20 | Instant createdAt; 21 | } 22 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/grid/IssueGrid.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.grid; 2 | 3 | import com.navercorp.spring.sql.groovy.issue.IssueContent; 4 | import com.navercorp.spring.sql.groovy.issue.Status; 5 | import com.navercorp.spring.sql.groovy.query.view.CommentView; 6 | import com.navercorp.spring.sql.groovy.query.view.IssueRepoView; 7 | import lombok.Value; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.relational.core.mapping.Table; 10 | 11 | import java.time.Instant; 12 | import java.util.UUID; 13 | 14 | @Table("ISSUE") 15 | @Value 16 | public class IssueGrid { 17 | @Id 18 | UUID id; 19 | 20 | Long issueNo; 21 | 22 | Status status; 23 | 24 | String title; 25 | 26 | IssueContent content; 27 | 28 | AccountGrid creator; 29 | 30 | IssueRepoView repo; 31 | 32 | Instant createdAt; 33 | 34 | CommentView comment; 35 | } 36 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/grid/IssueRepoGrid.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.grid; 2 | 3 | import lombok.Value; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | @Table("REPO") 8 | @Value 9 | public class IssueRepoGrid { 10 | @Id 11 | String id; 12 | 13 | String name; 14 | } 15 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/view/AccountView.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.view; 2 | 3 | import com.navercorp.spring.sql.groovy.account.AccountState; 4 | import com.navercorp.spring.sql.groovy.support.EncryptString; 5 | import lombok.Value; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.relational.core.mapping.Table; 8 | 9 | import java.util.UUID; 10 | 11 | @Table("ACCOUNT") 12 | @Value 13 | public class AccountView { 14 | @Id 15 | UUID id; 16 | 17 | String loginId; 18 | 19 | String name; 20 | 21 | AccountState state; 22 | 23 | EncryptString email; 24 | } 25 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/view/CommentView.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.view; 2 | 3 | import com.navercorp.spring.sql.groovy.comment.CommentContent; 4 | import lombok.Value; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | import java.time.Instant; 9 | 10 | @Table("COMMENT") 11 | @Value 12 | public class CommentView { 13 | @Id 14 | Long id; 15 | 16 | CommentContent content; 17 | 18 | AccountView creator; 19 | 20 | Instant createdAt; 21 | } 22 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/view/IssueLabelView.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.view; 2 | 3 | import lombok.Value; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | import java.util.UUID; 8 | 9 | @Table("LABEL") 10 | @Value 11 | public class IssueLabelView { 12 | @Id 13 | UUID id; 14 | 15 | String name; 16 | 17 | String color; 18 | } 19 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/view/IssueRepoView.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.view; 2 | 3 | import lombok.Value; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | @Table("REPO") 8 | @Value 9 | public class IssueRepoView { 10 | @Id 11 | String id; 12 | 13 | String name; 14 | 15 | String description; 16 | } 17 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/query/view/IssueView.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.query.view; 2 | 3 | import com.navercorp.spring.sql.groovy.issue.IssueContent; 4 | import com.navercorp.spring.sql.groovy.issue.Status; 5 | import lombok.Value; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.relational.core.mapping.Table; 8 | 9 | import java.time.Instant; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | @Table("ISSUE") 14 | @Value 15 | public class IssueView { 16 | @Id 17 | UUID id; 18 | 19 | long version; 20 | 21 | Long issueNo; 22 | 23 | Status status; 24 | 25 | String title; 26 | 27 | IssueContent content; 28 | 29 | AccountView creator; 30 | 31 | IssueRepoView repo; 32 | 33 | Instant createdAt; 34 | 35 | List labels; 36 | 37 | List comments; 38 | } 39 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/repo/Repo.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.repo; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import lombok.*; 5 | import org.springframework.core.Ordered; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.annotation.PersistenceConstructor; 8 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 9 | import org.springframework.data.relational.core.conversion.MutableAggregateChange; 10 | import org.springframework.data.relational.core.mapping.Table; 11 | import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback; 12 | 13 | import java.time.Instant; 14 | import java.time.ZoneId; 15 | import java.time.format.DateTimeFormatter; 16 | import java.util.StringJoiner; 17 | import java.util.UUID; 18 | 19 | @Table 20 | @Getter 21 | @EqualsAndHashCode(of = "id") 22 | @AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = @__(@PersistenceConstructor)) 23 | @ToString 24 | public class Repo { 25 | @Id 26 | private String id; 27 | 28 | private String name; 29 | 30 | private String description; 31 | 32 | private AggregateReference createdBy; 33 | 34 | private Instant createdAt; 35 | 36 | public Repo(String name, String description, AggregateReference createdBy) { 37 | this.name = name; 38 | this.description = description; 39 | this.createdBy = createdBy; 40 | this.createdAt = Instant.now(); 41 | } 42 | 43 | public void changeName(String name) { 44 | this.name = name; 45 | } 46 | 47 | public void changeDescription(String description) { 48 | this.description = description; 49 | } 50 | 51 | public static class RepoBeforeSaveCallback implements BeforeSaveCallback, Ordered { 52 | private static final DateTimeFormatter ID_PREFIX_FORMAT = DateTimeFormatter 53 | .ofPattern("yyyyMMddHHmmss") 54 | .withZone(ZoneId.of("Asia/Seoul")); 55 | 56 | private static String generateId(Repo repo) { 57 | return new StringJoiner("-") 58 | .add(ID_PREFIX_FORMAT.format(repo.getCreatedAt())) 59 | .add(repo.getName()) 60 | .toString(); 61 | } 62 | 63 | @Override 64 | public Repo onBeforeSave(Repo aggregate, MutableAggregateChange aggregateChange) { 65 | if (aggregate.id == null) { 66 | aggregate.id = generateId(aggregate); 67 | } 68 | return aggregate; 69 | } 70 | 71 | @Override 72 | public int getOrder() { 73 | return Ordered.LOWEST_PRECEDENCE; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/repo/RepoRepository.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.repo; 2 | 3 | import org.springframework.data.jdbc.repository.query.Query; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.data.repository.query.Param; 6 | 7 | import java.util.Optional; 8 | 9 | public interface RepoRepository extends CrudRepository { 10 | @Query("SELECT * FROM REPO repo WHERE repo.id = :id FOR UPDATE") 11 | @Override 12 | Optional findById(@Param("id") String id); 13 | } 14 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/support/EncryptString.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.support; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class EncryptString { 7 | String value; 8 | } 9 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/support/Encryptor.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.support; 2 | 3 | public interface Encryptor { 4 | byte[] encrypt(String value); 5 | 6 | String decrypt(byte[] encrypted); 7 | } 8 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/java/com/navercorp/spring/sql/groovy/support/SimpleEncryptor.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.support; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.SecretKey; 5 | import javax.crypto.spec.SecretKeySpec; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | public class SimpleEncryptor implements Encryptor { 9 | private final String transformation = "AES/ECB/PKCS5Padding"; 10 | private final String algorithm = "AES"; 11 | private final byte[] keyBytes = "thisisa128bitkey".getBytes(StandardCharsets.UTF_8); 12 | 13 | @Override 14 | public byte[] encrypt(String value) { 15 | if (value == null) { 16 | return null; 17 | } 18 | 19 | try { 20 | Cipher cipher = Cipher.getInstance(transformation); 21 | SecretKey secretKey = new SecretKeySpec(keyBytes, algorithm); 22 | cipher.init(Cipher.ENCRYPT_MODE, secretKey); 23 | return cipher.doFinal(value.getBytes(StandardCharsets.UTF_8)); 24 | } catch (Exception ex) { 25 | throw new RuntimeException("Encrypt value is failed.", ex); 26 | } 27 | } 28 | 29 | @Override 30 | public String decrypt(byte[] encrypted) { 31 | if (encrypted == null) { 32 | return null; 33 | } 34 | 35 | try { 36 | Cipher cipher = Cipher.getInstance(transformation); 37 | SecretKey secretKey = new SecretKeySpec(keyBytes, algorithm); 38 | cipher.init(Cipher.DECRYPT_MODE, secretKey); 39 | byte[] decrypted = cipher.doFinal(encrypted); 40 | return new String(decrypted, StandardCharsets.UTF_8); 41 | } catch (Exception ex) { 42 | throw new RuntimeException("Decrypt email is failed.", ex); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | liquibase: 3 | enabled: true 4 | change-log: classpath:/db/changelog/changelog-master.xml 5 | 6 | logging: 7 | level: 8 | org.springframework.jdbc.core: TRACE 9 | org.springframework.jdbc.core.StatementCreatorUtils: TRACE 10 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/main/resources/db/changelog/changelog-master.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/test/java/com/navercorp/spring/sql/groovy/account/AccountRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.account; 2 | 3 | import com.navercorp.spring.sql.groovy.support.EncryptString; 4 | import com.navercorp.spring.sql.groovy.test.DataInitializeExecutionListener; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.TestExecutionListeners; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.UUID; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | @SpringBootTest 17 | @TestExecutionListeners( 18 | listeners = DataInitializeExecutionListener.class, 19 | mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 20 | class AccountRepositoryTest { 21 | @Autowired 22 | private AccountRepository sut; 23 | 24 | private final List accounts = List.of( 25 | Account.builder() 26 | .id(UUID.randomUUID()) 27 | .loginId("navercorp.com") 28 | .name("naver") 29 | .state(AccountState.ACTIVE) 30 | .email(new EncryptString("naver@navercorp.com")) 31 | .build(), 32 | Account.builder() 33 | .id(UUID.randomUUID()) 34 | .loginId("mhyeon.lee") 35 | .name("Myeonghyeon Lee") 36 | .state(AccountState.ACTIVE) 37 | .email(new EncryptString("mhyeon.lee@navercorp.com")) 38 | .build() 39 | ); 40 | 41 | @Test 42 | void insert() { 43 | // given 44 | Account account = this.accounts.get(0); 45 | 46 | // when 47 | Account actual = this.sut.insert(account); 48 | 49 | // then 50 | assertThat(account).isSameAs(actual); 51 | } 52 | 53 | @Test 54 | void encryptDecrypt() { 55 | // given 56 | Account account = this.accounts.get(0); 57 | this.sut.insert(account); 58 | 59 | // when 60 | Optional actual = this.sut.findById(account.getId()); 61 | 62 | // then 63 | assertThat(actual.get().getEmail().getValue()).isEqualTo("naver@navercorp.com"); 64 | } 65 | 66 | @Test 67 | void softDelete() { 68 | // given 69 | Account account = this.accounts.get(1); 70 | account = this.sut.insert(account); 71 | 72 | // when 73 | this.sut.delete(account); 74 | 75 | // then 76 | assertThat(account.getState()).isEqualTo(AccountState.DELETED); 77 | 78 | Optional load = this.sut.findById(account.getId()); 79 | assertThat(load).isPresent(); 80 | assertThat(load.get().getState()).isEqualTo(AccountState.DELETED); 81 | 82 | Optional exclude = this.sut.findByIdExcludeDeleted(account.getId()); 83 | assertThat(exclude).isEmpty(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/test/java/com/navercorp/spring/sql/groovy/comment/CommentRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.comment; 2 | 3 | import com.navercorp.spring.sql.groovy.account.Account; 4 | import com.navercorp.spring.sql.groovy.issue.Issue; 5 | import com.navercorp.spring.sql.groovy.test.DataInitializeExecutionListener; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 10 | import org.springframework.test.context.TestExecutionListeners; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.UUID; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | 18 | @SpringBootTest 19 | @TestExecutionListeners( 20 | listeners = DataInitializeExecutionListener.class, 21 | mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 22 | class CommentRepositoryTest { 23 | @Autowired 24 | private CommentRepository sut; 25 | 26 | private final AggregateReference issue1Id = AggregateReference.to(UUID.randomUUID()); 27 | private final AggregateReference creatorId = AggregateReference.to(UUID.randomUUID()); 28 | private final List comments = List.of( 29 | new Comment(this.issue1Id, new CommentContent("comment 1", "text/plain"), this.creatorId), 30 | new Comment(this.issue1Id, new CommentContent("comment 2", "text/plain"), this.creatorId) 31 | ); 32 | 33 | @Test 34 | void insert() { 35 | // given 36 | Comment comment = this.comments.get(0); 37 | 38 | // when 39 | Comment actual = this.sut.save(comment); 40 | 41 | // then 42 | assertThat(actual.getVersion()).isEqualTo(1L); 43 | assertThat(comment.getContent()).isSameAs(actual.getContent()); 44 | 45 | Optional load = this.sut.findById(comment.getId()); 46 | assertThat(load).isPresent(); 47 | assertThat(load.get().getCreatedBy()).isEqualTo(this.creatorId); 48 | assertThat(load.get().getContent()).isNotNull(); 49 | assertThat(load.get().getContent().getBody()).isEqualTo("comment 1"); 50 | assertThat(load.get().getContent().getMimeType()).isEqualTo("text/plain"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-data-jdbc-plus-sql-groovy-sample/src/test/java/com/navercorp/spring/sql/groovy/label/LabelRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.navercorp.spring.sql.groovy.label; 2 | 3 | import com.navercorp.spring.sql.groovy.repo.Repo; 4 | import com.navercorp.spring.sql.groovy.test.DataInitializeExecutionListener; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.data.jdbc.core.mapping.AggregateReference; 9 | import org.springframework.test.context.TestExecutionListeners; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | @SpringBootTest 17 | @TestExecutionListeners( 18 | listeners = DataInitializeExecutionListener.class, 19 | mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 20 | class LabelRepositoryTest { 21 | @Autowired 22 | private LabelRepository sut; 23 | 24 | private final AggregateReference repoId = AggregateReference.to("20200501120611-naver"); 25 | private final List