├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── net │ │ └── anyjava │ │ └── springdatajpasample │ │ ├── SpringDataJpaSampleApplication.java │ │ ├── embeddedid │ │ ├── Pay.java │ │ ├── PayDetail.java │ │ ├── PayDetailId.java │ │ ├── PayId.java │ │ ├── PayRepository.java │ │ ├── PayRepositoryCustom.java │ │ ├── PayRepositoryImpl.java │ │ ├── PayShop.java │ │ ├── PayShopId.java │ │ └── PayShopRepository.java │ │ └── idclass │ │ ├── Pay2.java │ │ ├── Pay2Repository.java │ │ ├── Pay2RepositoryCustom.java │ │ ├── Pay2RepositoryImpl.java │ │ ├── PayDetail2.java │ │ ├── PayDetail2Repository.java │ │ ├── PayDetailId2.java │ │ ├── PayId2.java │ │ ├── PayShop2.java │ │ ├── PayShop2Repository.java │ │ └── PayShopId2.java └── resources │ └── application.yml └── test └── java └── net └── anyjava └── springdatajpasample ├── SpringDataJpaSampleApplicationTests.java ├── embeddedid ├── PayRepositoryImplTest.java ├── PayRepositoryTest.java ├── PayShopIdTest.java └── PayShopRepositoryTest.java └── idclass ├── Pay2RepositoryImplTest.java └── PayShop2RepositoryTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | /out/ 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | 28 | ### Querydsl ### 29 | generated -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## spring-data-jpa-sample (Compsit Key Mapping) 2 | 3 | > 이 리포는 우아한형제들 기술블로그의 [Legacy DB의 JPA Entity Mapping (복합키 매핑 편)](https://techblog.woowahan.com/2595/) 아티클의 예제 코드입니다. 4 | 5 | Legacy System 이 JPA 를 사용하지 않는 상황에서 JPA 로 전환하려 할때, 복합키 매핑에 관한 예제소스입니다. 6 | 7 | 첫번째 `@EmbededId`방식과 두번째 `@IdClass` 방식을 서로 비교해 볼 수 있는 예제입니다. 8 | 9 | * `@EmbededId`: 객체지향적인 방식의 매핑방법이다. 10 | * `@IdClass`: 컬럼의 중복이 발생하지만 RDB 친화적인 방식이다. 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.1.RELEASE' 4 | } 5 | repositories { 6 | maven { url "https://plugins.gradle.org/m2/" } 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10" 11 | } 12 | } 13 | 14 | apply plugin: 'java' 15 | apply plugin: 'eclipse' 16 | apply plugin: 'idea' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | apply plugin: 'com.ewerk.gradle.plugins.querydsl' 20 | 21 | group = 'net.anyjava' 22 | version = '0.0.1-SNAPSHOT' 23 | sourceCompatibility = 1.8 24 | 25 | repositories { 26 | mavenCentral() 27 | maven { url "https://plugins.gradle.org/m2/" } 28 | } 29 | 30 | 31 | ext { 32 | queryDslVersion = "4.1.4" 33 | } 34 | 35 | dependencies { 36 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 37 | compile('org.springframework.boot:spring-boot-devtools') 38 | compile("com.querydsl:querydsl-apt:$queryDslVersion") 39 | compile "com.querydsl:querydsl-jpa:$queryDslVersion" 40 | compile('org.projectlombok:lombok') 41 | 42 | testCompile('org.springframework.boot:spring-boot-starter-test') 43 | 44 | runtime('com.h2database:h2') 45 | } 46 | 47 | 48 | ext { 49 | querydslSrcDir = 'src/main/generated' 50 | } 51 | 52 | querydsl { 53 | library = "com.querydsl:querydsl-apt:$queryDslVersion" 54 | jpa = true 55 | querydslSourcesDir = querydslSrcDir 56 | } 57 | 58 | sourceSets { 59 | main { 60 | java { 61 | srcDirs += file(querydslSrcDir) 62 | } 63 | } 64 | } 65 | 66 | idea { 67 | module { 68 | generatedSourceDirs += file(querydslSrcDir) // just hint 69 | } 70 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowabros/spring-data-jpa-sample/0f12608dc77c0fa03812550c175e348f3476f089/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jan 02 00:00:31 KST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-data-jpa-sample' 2 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/SpringDataJpaSampleApplication.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | import javax.persistence.EntityManager; 9 | import javax.persistence.PersistenceContext; 10 | 11 | @SpringBootApplication 12 | public class SpringDataJpaSampleApplication { 13 | 14 | @PersistenceContext 15 | private EntityManager entityManager; 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SpringDataJpaSampleApplication.class, args); 19 | } 20 | 21 | @Bean 22 | public JPAQueryFactory jpaQueryFactory() { 23 | return new JPAQueryFactory(entityManager); 24 | } 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/Pay.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Getter 12 | @Entity 13 | @Table(name = "pay_1") 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Pay { 17 | 18 | @EmbeddedId 19 | @EqualsAndHashCode.Include 20 | private PayId id; 21 | 22 | @OneToMany(fetch = FetchType.LAZY, mappedBy = "pay") 23 | private List payShops = new ArrayList<>(); 24 | 25 | public Pay(PayId id) { 26 | this.id = id; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayDetail.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import lombok.NoArgsConstructor; 4 | 5 | import javax.persistence.EmbeddedId; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Table; 8 | 9 | @Entity 10 | @Table(name = "pay_detail_1") 11 | @NoArgsConstructor 12 | public class PayDetail { 13 | 14 | @EmbeddedId 15 | private PayDetailId id; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayDetailId.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Embeddable; 9 | import java.io.Serializable; 10 | 11 | @Getter 12 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 13 | @Embeddable 14 | @NoArgsConstructor 15 | public class PayDetailId implements Serializable { 16 | 17 | @EqualsAndHashCode.Include 18 | private PayId payId; 19 | 20 | @EqualsAndHashCode.Include 21 | @Column 22 | private Long payDetailId; 23 | 24 | public PayDetailId(PayId payId, 25 | Long payDetailId) { 26 | this.payId = payId; 27 | this.payDetailId = payDetailId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayId.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Embeddable; 9 | import java.io.Serializable; 10 | 11 | @Getter 12 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 13 | @Embeddable 14 | @NoArgsConstructor 15 | public class PayId implements Serializable { 16 | 17 | /** 18 | * 결제번호 (비지니스적으로 유의미한 번호) 19 | */ 20 | @EqualsAndHashCode.Include 21 | @Column 22 | private Long payNumber; 23 | 24 | @EqualsAndHashCode.Include 25 | @Column 26 | private Long paySeq; 27 | 28 | public PayId(Long payNumber, 29 | Long paySeq) { 30 | this.payNumber = payNumber; 31 | this.paySeq = paySeq; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayRepository.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PayRepository extends JpaRepository, PayRepositoryCustom { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import java.util.List; 4 | 5 | public interface PayRepositoryCustom { 6 | List findAllByShopName(String shopNumber); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | import static net.anyjava.springdatajpasample.embeddedid.QPay.pay; 10 | import static net.anyjava.springdatajpasample.embeddedid.QPayShop.payShop; 11 | 12 | @Repository 13 | public class PayRepositoryImpl extends QuerydslRepositorySupport implements PayRepositoryCustom { 14 | 15 | private final JPAQueryFactory jpaQueryFactory; 16 | 17 | public PayRepositoryImpl(JPAQueryFactory jpaQueryFactory) { 18 | super(Pay.class); 19 | this.jpaQueryFactory = jpaQueryFactory; 20 | } 21 | 22 | @Override 23 | public List findAllByShopName(String shopName) { 24 | return jpaQueryFactory.from(pay) 25 | .innerJoin(pay.payShops, payShop) 26 | .where(payShop.shopName.contains(shopName)) 27 | .select(pay) 28 | .fetch(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayShop.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.persistence.*; 7 | 8 | @Getter 9 | @Entity 10 | @NoArgsConstructor 11 | public class PayShop { 12 | 13 | @EmbeddedId 14 | private PayShopId id; 15 | 16 | private String shopName; 17 | 18 | @MapsId(value = "payId") 19 | @ManyToOne(fetch = FetchType.LAZY) 20 | private Pay pay; 21 | 22 | public PayShop(PayShopId id, 23 | String shopName) { 24 | this.id = id; 25 | this.shopName = shopName; 26 | } 27 | 28 | public void setPay(Pay pay) { 29 | if (pay != null) { 30 | pay.getPayShops().remove(this); 31 | } 32 | this.pay = pay; 33 | this.pay.getPayShops().add(this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayShopId.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import com.google.common.base.Preconditions; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Embeddable; 10 | import java.io.Serializable; 11 | 12 | @Getter 13 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 14 | @Embeddable 15 | @NoArgsConstructor 16 | public class PayShopId implements Serializable { 17 | 18 | /** 19 | * 테이블에서 정의하는 컬럼 사이즈 20 | */ 21 | public static final int SHOP_NUMBER_SIZE = 12; 22 | 23 | @EqualsAndHashCode.Include 24 | private PayDetailId payDetailId; 25 | 26 | @EqualsAndHashCode.Include 27 | @Column 28 | private String shopNumber; 29 | 30 | public PayShopId(PayDetailId payDetailId, 31 | String shopNumber) { 32 | Preconditions.checkArgument(shopNumber.length() <= SHOP_NUMBER_SIZE); 33 | 34 | this.payDetailId = payDetailId; 35 | this.shopNumber = shopNumber; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/embeddedid/PayShopRepository.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PayShopRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/Pay2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import net.anyjava.springdatajpasample.embeddedid.PayId; 6 | 7 | import javax.persistence.*; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Getter 12 | @IdClass(PayId.class) 13 | @Entity 14 | @Table(name = "pay_2") 15 | @NoArgsConstructor 16 | public class Pay2 { 17 | 18 | @Id 19 | private Long payNumber; 20 | 21 | @Id 22 | private Long paySeq; 23 | 24 | @OneToMany(fetch = FetchType.LAZY, mappedBy = "pay2") 25 | private List payShop2List = new ArrayList<>(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/Pay2Repository.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface Pay2Repository extends JpaRepository, Pay2RepositoryCustom { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/Pay2RepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import java.util.List; 4 | 5 | public interface Pay2RepositoryCustom { 6 | List findAllByShopName(String shopName); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/Pay2RepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | import static net.anyjava.springdatajpasample.idclass.QPay2.pay2; 10 | import static net.anyjava.springdatajpasample.idclass.QPayShop2.payShop2; 11 | 12 | @Repository 13 | public class Pay2RepositoryImpl extends QuerydslRepositorySupport implements Pay2RepositoryCustom { 14 | 15 | private final JPAQueryFactory jpaQueryFactory; 16 | 17 | public Pay2RepositoryImpl(JPAQueryFactory jpaQueryFactory) { 18 | super(Pay2.class); 19 | this.jpaQueryFactory = jpaQueryFactory; 20 | } 21 | 22 | @Override 23 | public List findAllByShopName(String shopName) { 24 | return jpaQueryFactory.from(pay2) 25 | .innerJoin(pay2.payShop2List, payShop2) 26 | .where(payShop2.shopName.contains(shopName)) 27 | .select(pay2) 28 | .fetch(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayDetail2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.persistence.*; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Getter 11 | @Entity 12 | @Table(name = "pay_detail_2") 13 | @IdClass(PayDetailId2.class) 14 | @NoArgsConstructor 15 | public class PayDetail2 { 16 | 17 | @Id 18 | private Long payNumber; 19 | 20 | @Id 21 | private Long paySeq; 22 | 23 | @Id 24 | private Long payDetailId; 25 | 26 | @OneToMany(mappedBy = "payDetail2", fetch = FetchType.LAZY) 27 | @org.hibernate.annotations.ForeignKey(name = "none") 28 | private List payShops = new ArrayList<>(); 29 | 30 | public PayDetail2(Long payNumber, 31 | Long paySeq, 32 | Long payDetailId) { 33 | this.payNumber = payNumber; 34 | this.paySeq = paySeq; 35 | this.payDetailId = payDetailId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayDetail2Repository.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PayDetail2Repository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayDetailId2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.NoArgsConstructor; 5 | import net.anyjava.springdatajpasample.embeddedid.PayId; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Embeddable; 9 | import javax.persistence.Id; 10 | import java.io.Serializable; 11 | 12 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 13 | @NoArgsConstructor 14 | public class PayDetailId2 implements Serializable { 15 | 16 | @EqualsAndHashCode.Include 17 | @Id 18 | private Long payNumber; 19 | 20 | @EqualsAndHashCode.Include 21 | @Id 22 | private Long paySeq; 23 | 24 | @EqualsAndHashCode.Include 25 | @Id 26 | private Long payDetailId; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayId2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.persistence.Id; 7 | import java.io.Serializable; 8 | 9 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 10 | @NoArgsConstructor 11 | public class PayId2 implements Serializable { 12 | 13 | @EqualsAndHashCode.Include 14 | @Id 15 | private Long payNumber; 16 | 17 | @EqualsAndHashCode.Include 18 | @Id 19 | private Long paySeq; 20 | 21 | public PayId2(Long payNumber, 22 | Long paySeq) { 23 | this.payNumber = payNumber; 24 | this.paySeq = paySeq; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayShop2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import net.anyjava.springdatajpasample.embeddedid.Pay; 6 | 7 | import javax.persistence.*; 8 | 9 | @Getter 10 | @Entity 11 | @Table(name = "pay_shop_2") 12 | @IdClass(PayShopId2.class) 13 | @NoArgsConstructor 14 | public class PayShop2 { 15 | 16 | @Id 17 | private Long payNumber; 18 | 19 | @Id 20 | private Long paySeq; 21 | 22 | @Id 23 | private Long payDetailId; 24 | 25 | @Id 26 | private String shopNumber; 27 | 28 | private String shopName; 29 | 30 | @ManyToOne(fetch = FetchType.LAZY) 31 | private Pay2 pay2; 32 | 33 | @ManyToOne(fetch = FetchType.LAZY, optional = true) 34 | @JoinColumns(value = { 35 | @JoinColumn(name = "payNumber", updatable = false, insertable = false), 36 | @JoinColumn(name = "paySeq", updatable = false, insertable = false), 37 | @JoinColumn(name = "payDetailId", updatable = false, insertable = false) 38 | }, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) 39 | private PayDetail2 payDetail2; 40 | 41 | public PayShop2(Long payNumber, 42 | Long paySeq, 43 | Long payDetailId, 44 | String shopNumber, 45 | String shopName) { 46 | this.payNumber = payNumber; 47 | this.paySeq = paySeq; 48 | this.payDetailId = payDetailId; 49 | this.shopNumber = shopNumber; 50 | this.shopName = shopName; 51 | } 52 | 53 | public void setPay2(Pay2 pay2) { 54 | if (pay2 != null) { 55 | pay2.getPayShop2List().remove(this); 56 | } 57 | this.pay2 = pay2; 58 | this.pay2.getPayShop2List().add(this); 59 | } 60 | 61 | public void setPayDetail2(PayDetail2 payDetail2) { 62 | if (payDetail2 != null) { 63 | payDetail2.getPayShops().remove(this); 64 | } 65 | this.payDetail2 = payDetail2; 66 | this.payDetail2.getPayShops().add(this); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayShop2Repository.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PayShop2Repository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/anyjava/springdatajpasample/idclass/PayShopId2.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.persistence.Id; 7 | import java.io.Serializable; 8 | 9 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 10 | @NoArgsConstructor 11 | public class PayShopId2 implements Serializable { 12 | 13 | @EqualsAndHashCode.Include 14 | @Id 15 | private Long payNumber; 16 | 17 | @EqualsAndHashCode.Include 18 | @Id 19 | private Long paySeq; 20 | 21 | @EqualsAndHashCode.Include 22 | @Id 23 | private Long payDetailId; 24 | 25 | @EqualsAndHashCode.Include 26 | @Id 27 | private String shopNumber; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging.level.org.hibernate.type: DEBUG 2 | logging.level.org.hibernate.type.BasicTypeRegistry: DEBUG 3 | logging.level.org.hibernate.tool.hbm2ddl: DEBUG 4 | logging.level.org.hibernate.SQL: DEBUG 5 | logging.level.org.hibernate.stat: DEBUG 6 | 7 | spring: 8 | jpa: 9 | properties: 10 | hibernate: 11 | format_sql: true 12 | show_sql: false 13 | -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/SpringDataJpaSampleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringDataJpaSampleApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/embeddedid/PayRepositoryImplTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @RunWith(SpringRunner.class) 10 | @DataJpaTest 11 | public class PayRepositoryImplTest { 12 | 13 | @Autowired 14 | private PayRepository payRepository; 15 | 16 | @Test 17 | public void testFindAllByShopName() { 18 | payRepository.findAllByShopName("가게"); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/embeddedid/PayRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(SpringRunner.class) 13 | @DataJpaTest 14 | public class PayRepositoryTest { 15 | 16 | @Autowired 17 | private PayRepository payRepository; 18 | 19 | @Autowired 20 | private TestEntityManager testEntityManager; 21 | 22 | @Test 23 | public void testFindById() { 24 | payRepository.findById(new PayId(1L, 2L)); 25 | } 26 | 27 | @Test 28 | public void saveOne() { 29 | // given 30 | // PayId 를 왜 null ? @MapsId 로 할당됨 31 | PayShop payShop = new PayShop(new PayShopId(new PayDetailId(null, 1L), "9999"), "업소"); 32 | testEntityManager.persist(payShop); 33 | 34 | Pay pay = new Pay(new PayId(123L, 1L)); 35 | 36 | payShop.setPay(pay); 37 | 38 | // when 39 | Pay savedPay = payRepository.save(pay); 40 | 41 | // then 42 | assertThat(savedPay.getId().getPayNumber()).isEqualTo(123L); 43 | assertThat(payShop.getPay()).isEqualTo(savedPay); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/embeddedid/PayShopIdTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | import static org.junit.Assert.fail; 7 | 8 | public class PayShopIdTest { 9 | 10 | @Test 11 | public void testCheckShopNumberColumnSize_SUCCESS() { 12 | // given 13 | String anyShopNumber = "123"; 14 | 15 | // when 16 | PayShopId payShopId = new PayShopId(null, anyShopNumber); 17 | 18 | // then 19 | assertThat(payShopId.getShopNumber()).isEqualTo(anyShopNumber); 20 | } 21 | 22 | @Test(expected = IllegalArgumentException.class) 23 | public void testCheckShopNumberColumnSize_FAIL() { 24 | // given 25 | String anyShopNumber = "12345678901234"; 26 | 27 | // when 28 | new PayShopId(null, anyShopNumber); 29 | 30 | // then 31 | fail("파라미터검증 테스트 실패"); 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/embeddedid/PayShopRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.embeddedid; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | @RunWith(SpringRunner.class) 12 | @DataJpaTest 13 | public class PayShopRepositoryTest { 14 | 15 | @Autowired 16 | private PayShopRepository payShopRepository; 17 | 18 | @Test 19 | public void testFindById() { 20 | assertThat(payShopRepository.findById(new PayShopId(new PayDetailId(new PayId(1L, 2L), 2L), "1234"))).isNotPresent(); 21 | } 22 | 23 | @Test 24 | public void testSave() { 25 | // given 26 | PayShopId payShopId = new PayShopId( 27 | new PayDetailId(new PayId(1L, 2L), 1L), 28 | "1234" 29 | ); 30 | PayShop payShop = new PayShop(payShopId, "자장면집"); 31 | 32 | // when 33 | PayShop savedPayShop = payShopRepository.save(payShop); 34 | 35 | // then 36 | assertThat(savedPayShop.getShopName()).isEqualTo("자장면집"); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/idclass/Pay2RepositoryImplTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @RunWith(SpringRunner.class) 10 | @DataJpaTest 11 | public class Pay2RepositoryImplTest { 12 | 13 | @Autowired 14 | private Pay2Repository pay2Repository; 15 | 16 | @Test 17 | public void test() { 18 | pay2Repository.findAllByShopName("가게"); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/net/anyjava/springdatajpasample/idclass/PayShop2RepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.anyjava.springdatajpasample.idclass; 2 | 3 | 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 8 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @RunWith(SpringRunner.class) 14 | @DataJpaTest 15 | public class PayShop2RepositoryTest { 16 | 17 | @Autowired 18 | private PayShop2Repository payShop2Repository; 19 | 20 | @Autowired 21 | private PayDetail2Repository payDetail2Repository; 22 | 23 | @Autowired 24 | private TestEntityManager em; 25 | 26 | @Test 27 | public void testSRve() { 28 | // given 29 | Long payNumber = 1234555L; 30 | PayDetail2 payDetail2 = payDetail2Repository.save(new PayDetail2(payNumber, 1L, 1L)); 31 | 32 | PayShop2 payShop2 = new PayShop2(payNumber, 1L, 1L, "12312", "자장면집"); 33 | payShop2.setPayDetail2(payDetail2); 34 | 35 | // when 36 | PayShop2 savedPayShop2 = payShop2Repository.save(payShop2); 37 | 38 | // then 39 | em.flush(); 40 | assertThat(savedPayShop2.getPayNumber()).isEqualTo(payNumber); 41 | } 42 | } --------------------------------------------------------------------------------