PowerSet

- n개의 원소를 가지는 배열의 모든 부분집합(2^n개)을 구하는 문제


조건

- n개의 원소의 부분집합은 총 2^n개

풀이전략

(1) n개의 배열의 각 원소가 부분집합에 포함되는지 여부를 나타내는 부분집합포함 배열 선언
   + 예를 들어 { 0, 1, 2, 3 } 의 부분집합 중 { 0, 2 } 를 부분집합포함 배열을 이용하여 나타내면 { 1, 0, 1, 0 } 이다

(2) 부분집합포함 배열의 인덱스를 0부터 3까지 증가시키면서 인덱스에 해당하는 원소를 포함하는 경우(1)와
    포함하지 않는 경우(0)을 각각 백트래킹을 실행한다. 

구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
/**
 * n개의 원소를 가지는 배열에서 모든 부분집합(2^n개)를 
 * 백트래킹 기법으로 구함
 * @author troh
 *
 */
public class Stack백트래킹_PowerSet {
    // n개의 원소를 가지는 배열
    private static int[] arr = {01234};
    
    // arr의 각 인덱스가 부분집합에 포함되는지 0 또는 1로 표시하는 배열
    private static int[] inclusionArr = new int[arr.length];
    
    // 불포함, 포함 배열을 나타냄
    private static int[] candidates = {01};
    
    public static void main(String[] args) {
        backTracking(inclusionArr, -1, inclusionArr.length-1);
    }
    
    /**
     * @param inclusionArr 부분집합포함 배열
     * @param incIndex inclusionArr의 인덱스
     * @param incLastIndex inclusionArr의 마지막
     */
    public static void backTracking(int[] inclusionArr, int incIndex, int incLastIndex) {
        // 부분집합포함 배열의 마지막까지 백트래킹이 실행되었다면 결과출력
        if(incIndex == incLastIndex) {
            System.out.printf("{");
            for(int i=0; i<inclusionArr.length; i++) {
                // 부분집합포함배열에 포함될 경우에만 원소를 출력
                if(inclusionArr[i] == 1) {
                    System.out.printf(" %d,", arr[i]);
                }
            }
            System.out.printf("}\n");
        } 
        else {
            // 부분집합포함 배열의 인덱스를 증가시킴
            incIndex++;
            
            // 해당 인덱스의 원소를 포함하는 경우와 포함하지않는 경우로 백트래킹 실행
            for(int i=0; i<candidates.length; i++) {
                inclusionArr[incIndex] = candidates[i];
                backTracking(inclusionArr, incIndex, incLastIndex);
            }
        }
    }
    
}
 
cs


배운점..

음.. 뭐 저번에 NQueen문제를 백트래킹으로 구현하며 너무 삽질을 많이해서 이번 문제는 풀만하게 느껴진 것 같다. 그래도 백트래킹 기법에 대해 좀 더 이해할 수 있는 문제였다.


NQueen 문제풀이

- N*N의 체스판에서 모든 행에 대해서 각 행마다 하나의 퀸이 존재하도록 퀸을 배치하는 문제

조건

1. 모든 행에 대해서 각 행마다 하나의 퀸이 존재
2. 각 퀸은 같은 열 또는 대각선 상에 위치할 수 없음

풀이전략

먼저 5시간 정도 삽질한 내용을 적자면 ( 하.. 부끄럽다 .. )

(1). 체스판을 2차원 배열로 표현
(2). 퀸을 체스판(2차월 배열)에 위치시킬 수 있는지 검사( 배열 전체를 검사함 - 같은 행, 같은 열, 대각선상에 존재 )
(3). 위치시킬 수 있다면 체스판의 값을 업데이트 ( 2 - 퀸의 위치, 1 - 퀸을 놓을 수 없는 위치, 0 - 퀸을 놓을 수 있는 위치 )
(4). 다음 행의 노드 전부를 2번에서 구한 노드의 자식노드로 설정
(5). 자식 노드를 순회하며 (2)~(4) 번  순회

# 자식 노드 전부 퀸을 위치시킬 수 없다면 ? 
 - (2)의 수행결과를 롤백해야 함
 - 부모의 퀸을 +1열 옮겨서 다시 테스트해야함 ( 이부분에 대해 스택으로 삽질해보았지만 머리만 아팠다..)

접근 방법이 잘못되었다고 느끼기 시작했다... 
힌트를 얻고자 구글링하다가 친절하게 설명해주신 분을 발견 !!  감사합니다 ㅠㅠ

핵심을 말하자면 2차원 배열로 퀸의 위치를 표현하는 것이 아니라
행의 크기(N) 와 각 행에서의 퀸의 위치(cols)를 1차원 배열로 표현하여 분리해야 한다는 것 !

구현하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * @author troh
 */
public class Stack백트래킹_NQueen {
    private static int N = 4;
    private static int[] cols;
    
    public static void main(String[] args) {
        cols = new int[4];
        backTracking(0);
    }
    
    public static void backTracking(int level) {
        // 현재 레벨이 N이라는 것은 N-1까지 퀸을 배치했다는 것이므로 모든 퀸을 배치 완료했다는 것
        // 답을 출력
        if(level == N) {
            for(int i=0; i<N; i++) {
                System.out.printf("%d ", cols[i]);
            }
            System.out.println("");
        } 
        // 현재 레벨의 어느 위치에 퀸을 두어야 하는지 확인
        else {    
            for(int i=0; i<N; i++) {
                // 현재 레벨의 퀸은 i열에 위치시킴
                cols[level] = i; 
                // 현재 레벨이 유효한지 검사
                if(isPossible(level)) {
                    backTracking(level+1);
                }
            }
        }
    }
    
    public static boolean isPossible(int level) {
        for(int i=0; i<level; i++) {
            // 같은 열에 위치하거나
            // 또는
            // 대각선에 위치하거나 ( 가로차이와 세로차이가 같으면 대각선상에 있다고 판단 )
            if(cols[i] == cols[level] || Math.abs(level-i) == Math.abs(cols[level] - cols[i])) {
                return false;
            }
        }
        return true;
        
    }
}
 
cs


배운점 ..

5시간의 삽질로 인해 스택과 재귀함수에 대해 많이 생각해 본 것 같다. 그리고 알고리즘 구현에 잡다한 로직(2차원 배열의 수행결과 롤백)을 수행해야 한다면 문제를 다른 시각으로 생각해봐야 할 것 같다.


계산기(문자열 수식 계산하기)

계산기의 문자열 계산식을 스택을 사용하여 해결할 수 있다.

  1. Stack을 이용하여 중위표기법[각주:1]의 수식을 후위표기법[각주:2]으로 변경
  2. 후위표기법의 수식을 Stack을 이용하여 계산


1. 중위표기식을 후위표기식으로 변환하기

  1. 입력받은 중위표기식에서 토큰을 읽음
  2. 토큰이 피연산자이면 토큰을 출력
  3. 토큰이 연산자(괄호포함)일 경우
    1. 토큰이 스택의 top에 저장된 연산자보다 우선순위가 높으면 push
    2. 그렇지 않다면 토큰의 우선순위가 더 높을때까지 pop한 후 토큰의 연산자를 push
    3. top에 연산자가 없으면 push
  4. 토큰이 오른쪽 괄호 ')' 일 경우
    1. Stack top에 왼쪽 괄호 '(' 가 올 때까지 pop
    2. pop한 연산자를 출력
  5. 중위표기식에 더 읽을 것이 없다면 중지, 더 읽을 것이 있다면 1부터 반복
  6. Stack 남아있는 연산자를 모두 pop하여 출력


1-1. 중위표기식을 후위표기식으로 변환하는 알고리즘 구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
 
public class Stack_중위표기법변환 {
    // 스택 내에서 연산자 우선순위 맵
    private static Map<Character, Integer> operPriorityMap;
    private static Stack<Character> operStack ;
    static {
        operPriorityMap = new HashMap<>();
        operPriorityMap.put('('0);
        operPriorityMap.put('+'1);
        operPriorityMap.put('-'1);
        operPriorityMap.put('*'2);
        operPriorityMap.put('/'2);
        
        operStack = new Stack<>();
    }
 
    public static void main(String[] args) {
        String input = "(6+5*(2-8)/2)";
        List<Character> output = new ArrayList<>();
        
        char[] inputChars = input.toCharArray();
        
        for(int i=0; i<inputChars.length; i++) {
            char c = inputChars[i];
            
            // 연산자
            if(isOperation(c)) {
                // 연산자 스택이 비었다면 push 
                if(operStack.isEmpty()) {
                    operStack.push(c);
                } // 연산자 우선순위 비교 
                else {
                    // 닫는 괄호일경우 여는 괄호 '(' 가 나올떄까지 pop
                    if(c == ')') {
                        char top = ' ';
                        do {
                            top = operStack.pop();
                            if(top != '(') {
                                output.add(top);
                            }
                        } while(top != '(');
                    } // 여는 괄호는 무조건 push 
                    else if(c == '(') {
                        operStack.push(c);
                    } // 그외 연산자인 경우 
                    else {
                        int tokenPriority = operPriorityMap.get(c);
                        
                        char top = operStack.peek();
                        int topPriority = operPriorityMap.get(top);
                        // 토큰의 우선순위가 더 높다면 스택에 추가
                        if(tokenPriority > topPriority) {
                            operStack.push(c);
                        } // 토큰의 우선순위가 더 높을때까지 pop 한 후 push 
                        else {
                            top = operStack.pop();
                            output.add(top);
                            
                            do {
                                if(operStack.isEmpty()) {
                                    operStack.push(c);
                                } else {
                                    top = operStack.peek();
                                    topPriority = operPriorityMap.get(top);
                                    if(tokenPriority > topPriority) {
                                        operStack.push(c);
                                    } else {
                                        top = operStack.pop();
                                        output.add(top);    
                                    }
                                }
                            } while(tokenPriority <= topPriority);
                        }
                    }
                }
            } // 피연산자
            else {
                output.add(c);
            }
        }
        
        // 6528-*2/+
        for(int i=0; i<output.size(); i++) {
            System.out.printf("%c", output.get(i));
        }
    }
    
    /**
     * 연산자 여부 확인
     * @param c
     * @return
     */
    public static boolean isOperation(char c) {
        if(')' == c) {
            return true;
        }
        
        return operPriorityMap.containsKey(c);
    }
}
 
cs


2. 후위표기법의 수식을 Stack을 이용하여 계산하기

위에서 구한 후의표기법을 이용하여 계산을 수행해보자. 알고리즘은 아래와 같다.

  1. 피연산자를 만나면 Stack에 push
  2. 연산자를 만나면 필요한 만큼의 피연자를 Stack에서 pop하여 계산하고 연산 결과를 다시 Stack에 push함
    피연산자의 숫자는 pop한 순서와 반대임
  3. 수식이 끝나면 마지막으로 Stack을 pop하여 출력


2-1. 후위표기법으로 계산 구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
 
public class Stack_후위표기법계산 {
    private static Stack<Integer> numberStack = new Stack<>();
 
    public static void main(String[] args) {
        String input = "6528-*2/+";
        char[] inputChars = input.toCharArray();
        
        for(int i=0; i<inputChars.length; i++) {
            char c = inputChars[i];
            
            // 피연산자이면 스택에 추가
            if(!isOperation(c)) {
                int num = Integer.parseInt(new Character(c).toString());
                numberStack.push(num);
            } 
            else {
                int num1 = numberStack.pop();
                int num2 = numberStack.pop();
                
                int result = 0 ;
                switch(c) {
                case '+' :
                    result = num2 + num1;
                    break;
                case '-' :
                    result = num2 - num1;
                    break;
                case '*' :
                    result = num2 * num1;
                    break;
                case '/' :
                    result = num2 / num1;
                    break;
                }
                numberStack.push(result);
            }
            
        }
 
        int result = numberStack.pop();
        System.out.println(result); // -9
    }
    
    /**
     * 연산자 여부 확인
     * @param c
     * @return
     */
    public static boolean isOperation(char c) {
        char[] operations = {'('')''*''+''-''/'};
        for(int i=0; i<operations.length; i++) {
            if(c == operations[i]) {
                return true;
            }
        }
        return false;
    }
}
 
cs



백트래킹(Backtracking)

해를 찾는 도중에 막히면(해가 아니면) 되돌아가서 다시 해를 찾아가는 기법이다. 이를 이용하여 최적화(Optimization) 문제와 결정(Desision)문제[각주:3]를 해결할 수 있다.

미로찾기 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
 
public class Stack백트래킹_미로찾기 {
    private static Stack<Pos> posStack = new Stack<>();
    private static Map<Pos, Boolean> visitHistory = new HashMap<>();
    
    // 0은 벽, 1은 통로, 2는 출구
    private static int[][] mazeArr = {
            {1000},
            {1110},
            {0100},
            {0111},
            {0200}
    };
    
    
    // 시계방향으로 이동가능한지 확인을 위함
    private static int[][] directionArr = {
            {10}, // 우 
            {01}, // 하
            {-10}, // 좌
            {0-1// 상
    };
    
    public static void main(String[] args) {
        Pos now = new Pos(00);
        posStack.push(now);
        
        do {
            System.out.println(now);
            visitHistory.put(now, true);
            
             Pos movablePos = move(now);
             if(movablePos != null) {
                  posStack.push(movablePos);    
             } else {
                movablePos = posStack.pop();
             }
            now = movablePos;
        } while(mazeArr[now.getY()][now.getX()] != 2);
    }
    
    /**
     * 이동할 수 있는 위치 반환. 이동할 수 없다면 null 반환
     * @param pos
     * @return
     */
    private static Pos move(Pos pos) {
        int x = pos.getX();
        int y = pos.getY();
        
        // 현재위치에서 시계방향으로 이동할 수 있는 곳을 체크함
        for(int i=0; i<4; i++) {
            int[] direction = directionArr[i];
            int moveX = direction[0];
            int moveY = direction[1];
            
            // 미로 범위안에서 이동가능 체크
            if((x + moveX) >= 0 && (x + moveX) <= 3
                    && (y + moveY) >= 0 && (y + moveY) <= 4) {
                if(mazeArr[y+moveY][x+moveX] == 0) {
                    continue;
                } 
                // 1(통로) 아니면 2(도착지)
                Pos movePos = new Pos(x+moveX, y+moveY);
                
                boolean isVisit = posStack.contains(movePos);
                if(!isVisit) {
                    Boolean hasVisitHisotry = visitHistory.get(movePos);
                    if(hasVisitHisotry == null) {
                        return movePos;
                    }
                }
            }
        }
        return null;
    }
}
 
 
class Pos {
    private int x;
    private int y;
    
    public Pos(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    
    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof Pos)) {
            return false;
        }
        Pos pos = (Pos)obj;
        if(pos.getX() == x && pos.getY() == y) {
            return true;
        }
        return false;
    }
    
    public int hashCode() {
        return Objects.hash(x,y);
    }
    
    public String toString() {
        StringBuffer bf = new StringBuffer();
        bf.append("x: "+x+",");
        bf.append("y: "+y);
        return bf.toString();
    }
}
 
cs


  1. 연사자를 피연산자의 가운데 표기하는 방법 ex) A+B [본문으로]
  2. 연산자를 피연산자 뒤에 표기하는 방법. 컴퓨터로 연산할 때 자주쓰임 ex) AB+ [본문으로]
  3. 문제의 조건을 만족하는 해가 존재하는지의 여부를 'YES' OR 'NO'가 답하는 문제 ex) 미로 찾기, n-Queen, Map Coloring, 부분 집합의 해 [본문으로]

'알고리즘 > SW Expert Academy' 카테고리의 다른 글

백트래킹 - PowerSet  (0) 2019.05.15
백트래킹 - NQueen  (0) 2019.05.15
Stack(2) - DP(동적 계획법), DFS(깊이우선탐색)  (0) 2019.03.23
Stack(1) - 스택과 memorization  (0) 2019.03.19
문자열 (String)  (0) 2019.02.16

DP (동적 계획법)

DP란 Dynamic Programming의 약자이며 최적화 문제를 해결하는 알고리즘 설계기법 중 하나이다. 
  • 입력 크기가 작은 부분 문제들을 모두 해결한 후에 그 해들을 이용하여 보다 큰 크기의 부분 문제를 해결한다
  • 최종적으로 원래 주어진 입력의 문제를 해결한다
즉 점층적으로 문제를 해결해나가는 방식이다. 

앞선 예제인 피보나치 수열을 DP 방식으로 해결해보자. 
  • 문제를 부분 문제로 분할한다
    • F(N) = F(N-2) + F(N-1)
    • F(N-1) = F(N-3) + F(N-2)
    • ...
    • F(3) = F(2) + F(1)
  • 부분 문제로 나누는 일이 끝났으면 작은 부분부터 해를 구한다.
    • F(2) = 1, F(1) = 1 이다
  • 부분 문제의 해를 이용하여 전체의 해를 구한다.
    • F(2)와 F(1)을 이용하여 전체의 해를 구한다.

보통 Memorization는 재귀적 구조에 사용하는 것보다 반복문에서 사용하는 것이 효율적이다. 재귀 함수에서는 스택 오버플로우가 발생할 수 있다.

DP구현 - 피보나치 수열 ( 재귀함수 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FibonacciNumberDP {
    private static Integer[] fArr; 
    public static void main(String[] args) {
        fArr = new Integer[46];
        fArr[0= 0;
        fArr[1= 1;
        fArr[2= 1;
        
        int number = getFibonacciNumber(45);
        System.out.println(number);
    }
    
    public static int getFibonacciNumber(int index) {
        if(fArr[index] != null) {
            return fArr[index];
        }
        
        fArr[index] = getFibonacciNumber(index-2+ getFibonacciNumber(index-1);
        return fArr[index];
    }
}
 
cs



DP구현 - 피보나치 수열 (반복문)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FibonacciNumberDP2 {
    private static Integer[] fArr; 
    public static void main(String[] args) {
        fArr = new Integer[46];
        fArr[0= 0;
        fArr[1= 1;
        fArr[2= 1;
        
        for(int i=3; i<46; i++) {
            fArr[i] = fArr[i-2+ fArr[i-1];
        }
        int result = fArr[45];
        System.out.println(result);
    }
}
 
cs



DFS(깊이우선탐색)

비선형구조인 그래프 구조를 검색하는 두 가지 방법 중 하나이다. 

  • 시작 정점의 한 방향으로 갈 수 있는 경로가 있는 곳까지 깊이 탐색한다
  • 더 이상 갈 곳이 없게 되면, 가장 마지막에 만났던 갈림길 간선이 있는 정점으로 되돌아온다
  • 다른 방향의 정점으로 탐색을 계속 반복하여 모든 정점을 방문, 순회한다
가장 마지막에 만났던 갈림길의 정점으로 되돌아가서 다시 깊이 우선탐색을 반복해야 하므로 후입선출 구조의 Stack을 사용하는게 적절하다.

DFS를 구현해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.util.EmptyStackException;
import java.util.Stack;
 
public class DFS_깊이우선탐색 {
    public static boolean[] visitedArr = new boolean[7];
    
    public static void main(String[] args) {
        // 탐색 정점 표시 a,b,c,d,e,f 방문표시
        for(int i=0; i<visitedArr.length; i++) {
            visitedArr[i] = false;
        }
        // 그래프 연결
        
        Data g = new Data("g"6null);
        Data f = new Data("f"5, g);
        Data e = new Data("e"4, f);
        Data d = new Data("d"3, f);
        Data c = new Data("c"2, e);
        Data b = new Data("b"1, d, e);
        Data a = new Data("a"0, b, c);
        
        // 정점 스택
        Stack<Data> dataStack = new Stack<>();
 
        try {
            // 순회 시작점 a
            Data start = a;
            do {
                start.visit();
                
                // 방문하지않은 정점 찾기
                Data unvisit = start.getNotVisit();
                if(unvisit != null) {
                    dataStack.push(start);
                } 
                System.out.println("[정점]:"+start.getName()+"->");
                
                while(unvisit != null) {
                    System.out.println("\t[방문 정점]:"+unvisit.getName());
                    unvisit.visit();
                    // 방문하지 않은 정점찾기
                    if(unvisit.getNotVisit() != null) {
                        dataStack.push(unvisit);
                        unvisit = unvisit.getNotVisit();
                    } else {
                        unvisit = null;
                    }
                }
                start = dataStack.pop();
            } while(start != null);
        } catch(EmptyStackException e1) {
            // 방문결과확인
            for(int i=0; i<visitedArr.length; i++) {
                System.out.println(visitedArr[i]);
            }
        }
        
    }
}
 
class Data {
    private String name;
    private int index;
    private Data[] links;
    
    public Data(String name, int index, Data... data) {
        this.name = name;
        this.index = index;
        this.links = data;
    }
    
    public String getName() {
        return this.name;
    }
    
    public void visit() {
        DFS_깊이우선탐색.visitedArr[index] = true;
    }
    
    public boolean isVisit() {
        return DFS_깊이우선탐색.visitedArr[index];
    }
    
    public Data getNotVisit() {
        Data unvisit = null;
        
        if(links == null) {
            return null;
        }
        
        for(int i=0; i<links.length; i++) {
            Data link = links[i];
            if(!link.isVisit()) {
                unvisit = link;
                break;
            }
        }
        return unvisit;
    }
}
 
cs


Stack

물건을 쌓아 올리듯 자료를 쌓아 올린 형태의 자료구조를 말하며 선형구조[각주:1]를 가진다 마지막에 삽입한 자료가 가장 먼저 꺼내지는 후입선출(LIFO, Last-In-First-Out) 형태이다. 스택은 삽입(push), 삭제(pop), 공백확인(isEmpty), 반환(peek) 연산을 수행한다


스택의 연산



먼저 push 연산을 알아보자. 빈 스택에서는 top의 인덱스가 -1을 가리킨다. 이때 A를 push하면 스택의 맨 위에 A가 저장되고 top인덱스가 0이 된다. 이후 B를 push 하면 맨 위에 B가 저장되고 top 인덱스가 1이 증가된다. 스택의 크기를 넘어가면 overflow 가 발생한다.


pop 연산은 push의 반대이다. 스택에 A -> B -> C 순서대로 저장했을 때 pop 연산을 수행하면 C -> B -> A 순으로 반환하며 스택에서 삭제된다. 스택이 삭제될 때 top 인덱스는 1씩 감소한다. 


스택의 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
/**
 * push, pop 구현
 * @author troh
 *
 */
public class Stack구현1 {
    public static void main(String[] args) {
        Stack stack = new Stack(3);
        stack.push('A');
        stack.push('B');
        stack.push('C');
        
        System.out.println(stack.pop()); // C
        System.out.println(stack.pop()); // B
        System.out.println(stack.pop()); // A
    }
}
 
class Stack {
    private int top = -1;
    
    private char[] stack;
    
    public Stack(int size) {
        this.stack = new char[size];
    }
    
    public void push(char c) {
        if(top == stack.length) {
            throw new RuntimeException("Stack Overflow");
        }
        stack[++top] = c;
    }
    
    public char pop() {
        if(top == -1) {
            throw new RuntimeException("Stack Underflow");
        }
        return stack[top--];
    }
}
cs



스택의 활용 - 괄호 유효성

스택을 활용하여 괄호의 쌍이 올바르게 사용되었는지 판단하는 프로그램을 작성해보자. 괄호의 올바른 사용조건은 아래와 같다.
  • 여는 괄호와 닫는 괄호의 개수는 같아야한다
  • 여는 괄호가 닫는 괄호보다 먼저 나와야한다
  • 괄호사이에는 포함 관계만 존재해야한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.EmptyStackException;
import java.util.Stack;
 
public class Stack활용_괄호 {
    public static void main(String[] args) {
        String str = "for(int i=0; i++; i<10)";
        boolean result = bracketCheck(str);
        System.out.println(result);
    }
    
    public static boolean bracketCheck(String str) {
        Stack<Character> bracketStack = new Stack<>();
        try {
            for(int i=0; i<str.length(); i++) {
                char c = str.charAt(i);
                if(isOpenBracket(c)) {
                    bracketStack.push(c);
                } else if(isCloseBracket(c)) {
                    bracketStack.pop();
                }
            }
            
            // 괄호의 쌍이 맞는지 확인
            if(!bracketStack.isEmpty()) {
                return false;
            }
            return true;
        } catch (EmptyStackException e) {
            // 여는 괄호보다 닫는 괄호가 먼저 나왔을 때는 유효하지 않는 경우임
            return false;
        }
    }
    
    /**
     * 여는 괄호인지 판단
     * @param c
     * @return
     */
    public static boolean isOpenBracket(char c) {
        return c == '(' ;
    }
    
    /**
     * 닫는 괄호인지 판단
     * @param c
     * @return
     */
    public static boolean isCloseBracket(char c) {
        return c == ')' ;
    }
}
 
cs



Memorization 

컴퓨터 프로그램을 실행하 ㄹ때 이전에 계산한 값을 메모리에 저장해서 매번 다시 계산하지 않도록 하여 전체적인 실행속도를 빠르게 하는 기술이다. 피보나치 수열의 재귀함수[각주:2]를 Memorization을 사용하여 효율적으로 처리할 수 있다. 피보나치 수열을 재귀함수로 구현하면 중복 함수 호출이 많기 때문이다. 

다음은 피보나치 수열의 n번째 수를 구하는 일반적인 방법이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
public class FibonacciNumbers {
    public static void main(String[] args) {
        int num = fibonacciNumber(21);
        System.out.println(num);
    }
    
    public static int fibonacciNumber(int index) {
        if(index <= 2) {
            return 1;
        }
        
        return fibonacciNumber(index-1+ fibonacciNumber(index-2);  
    }
}
 
cs


위의 알고리즘의 동작을 그림으로 살펴보면 많은 중복 호출이 있는것을 알 수 있다. fibonacciNumber 함수를 F로 표현하겠다.



위의 그림을 보면 함수가 중복적으로 많이 호출되는 것을 알 수 있다. 피보나치의 n이 커질수록 중복은 많아질 것이다. 이러한 중복을 해결하는 방법이 Memorization이다. 이를 이용하여 구현한 코드를 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
public class FibonacciNumbers_Memorization {
    private static Integer[] memories ;
    
    public static void main(String[] args) {
        memories = new Integer[46];
        
        long start = System.currentTimeMillis();
        
        int num = fibonacciNumber(45);
        
        long end = System.currentTimeMillis();
        System.out.println(num);
        System.out.println("실행시간: "+ (end-start));
    }
    
    public static int fibonacciNumber(int index) {
        if(index <= 2) {
            return 1;
        }
        if(memories[index] != null) {
            return memories[index];
        } else {
            memories[index] = fibonacciNumber(index-1+ fibonacciNumber(index-2);
            return memories[index];
        }
    }
}
 
cs


Memorization을 이용하여 45번째 피보나치 숫자를 구한 실행시간은 0 밀리세컨으로 결과값이 나온다. 반면 Memorization을 사용하지 않았을 경우에는 약 3초(2919 밀리세컨)의 실행시간이 소요된다. 이러한 성능 차이는 숫자가 커짐에 따라 더욱 커질 것이다. 

  1. 선형구조: 자료 간의 관계가 1대 1의 관계를 가짐. 비선형구조: 자료 간의 관계가 1대 N의 관계를 가짐( 트리 자료구조 ) [본문으로]
  2. 자기 자신을 재호출하는 함수 [본문으로]

c부트스트랩이란 반응형 웹[각주:1]을 좀더 쉽게 개발할수 있도록 도와주는 프론트엔드 프레임워크의 한 종류이다. 


왜 부트스트랩을 써야하는가? 

부트스트랩의 이점은 아래와 같은데 개인적으로 4번의 이유가 매우 중요해보인다.
  1. 사용하기 쉽다
  2. 반응형이다
  3. 모바일 우선 방식이다 ( 모바일을 지원한다는 의미인듯..)
  4. 브라우저가 호환된다


Container

사이트의 요소를 감싸기위해 포함 요소가 필요한데 부트스트랩은 2개의 클래스를 지원한다. 예시 이미지로 보면 둘의 차이점이 이해가 될 것이다.
  1. .container: 고정폭을 반응형으로 제공
  2. .container-fluid: 뷰포트에 따른 전체 너비 제공 ( 모바일이든 웹이든 전체 너비를 제공한다는 듯..)



Grid

부트스트랩은 페이지 전체를 12개의 컬럼으로 구성할 수 있다. 아래의 예시를 보면 12개의 컬럼을 각자 개별적으로 사용할수도, 또는 몇개의 그룹으로 묶어서 사용할 수 있는 것을 확인할 수 있다.

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1



그리드 클래스는 col-**-** 형태이며 처음 ** 에는 아래의 디바이스 크기에 맞는 적설한 값을 마지막 **에는 12개의 컬럼중 몇개로 구성할지를 작성하면 된다. 이러한 그리드 클래스는 행을 구분하는 row 클래스 내에서 사용하면 된다.  


  • xs (for phones - screens less than 768px wide)
  • sm (for tablets - screens equal to or greater than 768px wide)
  • md (for small laptops - screens equal to or greater than 992px wide)
  • lg (for laptops and desktops - screens equal to or greater than 1200px wide)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-lg-12"  style="background-color:gray;"><tiles:insertAttribute name="header" /></div>
    </div>
    <div class="row">
        <div class="col-lg-3"  style="background-color:green;"><tiles:insertAttribute name="menu" /></div>
        <div class="col-lg-9"  style="background-color:blue;"><tiles:insertAttribute name="body"/></div>
    </div>
    <div class="row">
        <div class="col-lg-12"  style="background-color:red;"><tiles:insertAttribute name="bottom" /></div>
    </div>
</div>
</body
cs


  1. 디바이스(크기)에 따라 자동적으로 모습이 바뀌는 웹페이지 [본문으로]

'웹 개발' 카테고리의 다른 글

JSTL (작성중)  (0) 2018.05.04
jsonp  (0) 2017.02.09
OAuth 2.0 ( RFC 6749)  (0) 2017.01.19
JSP 와 Servlet의 한글처리.  (0) 2016.10.06
Apache 와 Tomcat  (0) 2016.07.26

이번 포스팅에서는 엔티티매니저와 영속컨텍스트에 대해서 알아보려한다. 그전에 엔티티와 엔티티매니저가 무엇인지 알아야 하는데 이에 대해서는 이전에  정리한 내용이 있으니 참고하길 바란다.

+ JPA


JPA를 이용하여 개발한 프로그램 코드는 보통 다음과 같은 패턴으로 개발된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
EntityManager emf = Persistence.createEntityManagerFactory("jpastart");
EntityManager em = emf.createManager();
EntityTransaction transaction = em.getTransaction();
 
try {
    transaction.begin();
    Sight sight = em.find(Sight.class1);
    sight.setDetail(new SightDetail('오전9~오후5시''연중무휴''100여대 주차가능'));
    transaction.commit();
catch(Exception e) {
    transaction.rollback();
    throw e;
finally {
    em.close();
emf.close();
}
cs


엔티티매니저를 생성한 이후 트랜잭션을 시작하고 엔티티매니저를 사용하여 엔티티(Sight)를 검색하고 명소정보(SightDetail)를 지정한 이후 트랜잭션을 커밋한다. 얼핏 일반적인 jdbc를 이용한 프로그래밍 패턴과 비슷하지만 JPA는 큰 차이점이 있다.



영속 컨텍스트

앞서 JPA의 가장 큰 특징은 영속성(Persistence)이다. JPA를 사용해 검색한 DB매핑정보(엔티티)는 메모리(영속 컨텍스트)에 저장되고 이러한 엔티티는 영속객체라 부른다. JPA는 매번 데이터베이스에 접근하는 것이 아니라 엔티티매니저를 사용하여 메모리상에 작업을 한후 트랜잭션이 커밋되는 시점에 데이터베이스에 반영되는 구조를 가지고 있다. 


엔티티, 엔티티매니저와 영속 컨텍스트의 관계는 어느정도 이해가 됐을 것 같은데 간단히 정리하면 아래의 그림과 같다.


프로그램에서 엔티티매니저를 이용하여 데이터를 검색요청하면 영속 컨텍스트에 데이터가 없을 경우 DB에서 검색하여 영속 컨텍스트에 엔티티로 저장한다. 이후에 같은 식별자를 가지는 데이터를 요청할 경우에는 DB에서 검색하는 것이 아니라 영속 컨텍스트에서 검색한 결과를 반환한다.



전체적인 흐름은 위와 같고 먼저 엔티티매니저에 대해 자세히 알아보도록 하자.



EntityManager(엔티티매니저)


앞서 엔티티매니저는 영속컨텍스트에 저장되어 있는 엔티티를 관리하기위한 객체라고 설명했었다. 엔티티매니저를 관리하는 주체에 따라 종류가 구분된다

 종류

설명 

애플리케이션 관리 엔티티매니저 

 애플리케이션에서 직접 emf를 생성하고 관리

컨테이너관리 엔티티매니저 

 JBoss EAP, 웹로직, TomEE와 같은 JEE 컨테이너가 EMF를 생성하고 관리


전자는 앞선 코드에서 처럼 프로그램 코드에서 직접 EntityManager를 직접 생성하고 종료하는 방식을 말한다. 반면 컨테이너 관리 엔티티매니저는 프로그램 코드가 아닌 JEE 컨테이너[각주:1]에서 EntityManager의 라이프사이클(생성~종료)을 관리한다. 

 아래의 예제에서 보이다시피 EntityManager에 대한 생성과 종료에 대한 코드가 없다. JEE 컨테이너는 @Transactional 애노테이션이 적용된 메서드를 트랜잭션 범위에서 실행한다. 즉 메소드가 실행될때 트랜잭션이 시작(begin)되고 메서드가 종료될 때 트랜잭션이 완료(commit)되는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
@PersistenceContext EntityManager em;
 
@Transactional
public void withdraw(String email) {
    User user = em.find(User.class, email);
    if(user == null) {
        throw new UserNotFoundException();
    }
    
    em.remove(user);
}
cs



트랜잭션

컨테이너관리 엔티티매니저를 사용했을 때 트랜잭션에 대해 상세한 제어가 필요한 경우가 있다. 애플리케이션에서 관리하는 앤티티매니저의 경우는 개발자가 직접 트랜잭션을 제어할 수 있기 때문에 큰 어려움은 없겠지만 컨테이너가 관리하는 엔티티매니저의 경우는 전파방법에 대해 알아두어야 한다.

그전에 먼저 트랜잭션 종류에 대해 알아보자. JPA는 자원 로컬(Resource Local) 트랜잭션 타입과 JTA(Java Transaction Api) 타입의 두 가지 트랜잭션 타입을 지원한다. 트랜잭션 타입은 persistence.xml 파일의 transaction-type 속성값을 설정하면 된다. 

먼저 자원 로컬 트랜잭션은 JPA가 제공하는 EntityTransaction을 이용하는 방식이다. 아래의 코드는 JPA가 제공하는 자원 로컬 트랜잭션을 사용한 예시이다.
1
2
3
4
5
6
7
8
9
10
11
12
EntityManager em = emf.createEntityManager();
Transaction tx = em.createTransaction();
try {
    tx.begin();
    //...
    tx.commit();
catch(Exception e) {
    tx.rollback();
finally {
    em.close();
}
 
cs


다음은 JTA 트랜잭션 방식인데 JTA 트랜잭션을 사용하면 JPA에서 트랜잭션을 관리하지 않는다. 대신 EntityManager를 JTA 트랜잭션에 참여시켜 트랜잭션을 관리한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UserTransaction utx = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
utx.begin(); // JTA 트랜잭션 시작
 
EntityManager em = emf.createEntityManager();
em.joinTransaction(); // JTA 트랜잭션에 참여
 
try {
    // ...
    utx.commit();
catch(Exception e) {
     utx.rollback();
finally {
    em.close()
}
 
cs


위의 코드에서 5라인의 joinTransaction()을 호출하지 않으면 엔티티의 변경이 있을 경우 실제 DB에 반영되지 않는다. 그 이유는 JPA는 트랜잭션 내에서의 변경만 DB에 반영하는데 JTA트랜잭션 방식을 사용하면 엔티티매니저는 트랜잭션에 관여하지 않고 엔티티매니저가 JTA 트랜잭션에 참여하지 않았으니 실제 DB에 반영되지 않는 것이다. 


엔티티매니저의 영속 컨텍스트 전파


  1. Enterprise급 개발에 필요한 모든 기술을 담고 있는 기술 (트랜잭션, JSP, 서블릿 등..)을 사용할 수 있는 환경 [본문으로]

'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

@Embeddable  (0) 2019.02.19
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

@Embeddable 애노테이션을 지정한 클래스를 밸류 클래스라고 합니다. 밸류 클래스란 int, double 처럼 하나의 값을 나타내는 클래스를 말합니다. 보통 주소(address)라는 값을 저장하기 위해서는 아래와 같이 String 변수에 저장하여 관리할 것입니다. 

1
String address = "서울시 강동구 천호동 113-12번지 개발아파트 110동 1101호";
cs


하지만 주소 역시 값을 분리하여 저장하는 경우도 많습니다. address1과 address2,, zipCode가 합쳐져야 하나의 정확한 의미를 나타내는데 변수를 분리하여 관리하기 때문에 불편한 점도 있을 것입니다. 이러한 경우에 "밸류 클래스"를 사용하여 값이 가지는 의미를 강화시킬 수 있습니다.


1
2
3
String address1 = "서울시 강동구 천호동";
String address2 = "개발아파트 110동 1101";
String zipCode = "113-12";
cs


밸류 클래스

밸류 클래스는 여러개의 값(address1, address2, zipCode)을 가지지만 개념적으로 하나의 값(주소)을 표현합니다. 또한 다른 밸류 객체와 식별하기 위한 식별자를 가지지 않는 것이 특징입니다. 아래는 주소라는 값을 나타내는 Address 클래스입니다. 변수를 address1, address2, zipCode로 각각 관리했을 때보다 좀 더 의미가 뚜렷한 것 같지않으신가요? 

1
2
3
4
5
class Address {
    String address1;
    String address2;
    String zipCode;
}
cs


하지만 밸류 클래스는 보통 값으로 사용되기 때문에 equals 메소드와 hashCode 메소드를 오버라이딩하여 사용해야 합니다. 


@Embeddable 애노테이션과 밸류 매핑

먼저 @Embeddable 애노테이션은 설정한 클래스가 다른 엔티티의 일부로 저장될 수 있음을 설정하는 애노테이션입니다. 즉 @Embeddable 애노테이션을 설정한 밸류 클래스는 다른 엔티티에 포함될 수 있다는 것이죠. 밸류를 포함하는 엔티티에서는 @Embedded로 밸류 클래스임을 설정합니다. 호텔(엔티티)과 주소(밸류)의 설정을 살펴보시면 이해가 될 것 입니다.

1
2
3
4
5
6
7
8
create table jpastart.hotel (
    id varchar(100not null primary key,
    name varchar(50),
    grade varchar(255),
    zipcode varchar(5),
    address1 varchar(255),
    address2 varchar(255)
engine innodb character set utf8;
cs


호텔 엔티티
1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Hotel {
    @Id
    private String id;
    private String name;
    @Enumberated(EnumType.STRING)
    private Grade grade;
 
    @Embedded
    private Address address;
}
cs

주소 밸류클래스

1
2
3
4
5
6
7
8
@Embeddable
public class Address{
    private String zipCode;
    private String address1;
    private String address2;
 
    //...
}
cs



그럼 엔티티매니저를 사용하여 Hotel을 검색하면 어떻게 될까요? address1, address2, zipCode는 자동으로 Hotel 엔티티안의 Address 밸류클래스로 매핑되기 때문에 호텔의 get메소드를 호출하여 사용할 수 있습니다. 


1
2
3
4
5
Hotel hotel = em.find(Hotel.class"H100-01");
Address address = hotel.getAddress();
// address.getAddress1(); 서울시 강동구 천호동
// address.getAddress2(); 개발호텔
// address.getZipCode();  113-12
cs



@Embeddable 접근 타입

@Embeddable 매핑한 대상은 기본적으로 엔티티의 접근 방법( 필드 접근타입 또는 프로퍼티 접근 타입 )을 따릅니다. 엔티티에서 프로퍼티 접근 타입(getter / setter)를 사용한다면 @Embeddable 매핑한 밸류 클래스 역시 프로퍼티 접근 타입을 사용하기 때문에 getter, setter 메소드를 정의해야 합니다.



@AttributeOverrides

관광지라는 엔티티가 있고 이 엔티티는 한글로 된 주소, 영어로 된 주소 2개를 가진다고 해보자. 

1
2
3
4
5
6
7
8
9
10
create table jpastart.sight (
    id varchar(100not null primary key,
    name varchar(50),
    kor_zipcode varchar(5),
    kor_address1 varchar(255),
    kor_address2 varchar(255)
    eng_zipcode varchar(5),
    eng_address1 varchar(255),
    eng_address2 varchar(255)
engine innodb character set utf8;
cs


위의 sight 테이블을 엔티티로 매핑하면 아래와 같은데 Address 밸류 클래스는 address1, address2, zipcode를 필드로 설정하고 있다. Sight 엔티티에서 Address 밸류클래스를 중복 선언하였기 떄문에  필드가 중복됐다는 JPA의 에러 메세지를 볼 수 있을것이다. 

1
2
3
4
5
6
7
8
9
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    private Address korAddress;
    @Embedded
    private Address engAddress;
}
cs



@AttributeOverrides 또는 @AttributeOverride 애노테이션을 사용하면 위와 같은 문제를 해결할 수 있다. 이 두 애노테이션은 @Embedded로 매핑한 값 타입의 매핑 설정을 재정의할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="kor_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="kor_address1"),
        @AttributeOverride(name="address2", column=@Column(name="kor_address2")
    })
    private Address korAddress;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="eng_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="eng_address1"),
        @AttributeOverride(name="address2", column=@Column(name="eng_address2")
    })
    private Address engAddress;
}
cs


위와 같이 설정하면 Address 밸류 클래스 필드값의 컬럼 설정을 재정의할 수 있다. 기존에는 address1, address2, zipcode가 중복되어서 오류가 발생하였지만 @AttributeOverrides, @AttributeOverride 애노테이션을 이용하여 컬럼이 중복되지 않도록 재정의하였다. 


데이터를 객체를 모델링하다보면 밸류 클래스의 중첩이 일어날 수 있는데 JPA에서는 중첩 @Embeddable도 지원된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Entity
public class City {
    // ...
    
    // 도시 연락처정보
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="address.zipcode", column=@Column(name="city_zip")),
        @AttributeOverride(name="address.address1", column=@Column(name="city_addr1")),
        @AttributeOverride(name="address.address2", column=@Column(name="city_addr2"))
    })
    private ContractInfo contractInfo;
}
 
@Embeddable
public class ContractInfo {
    @Column(name="ct_phone")
    private String phone;
 
    @Column(name="ct_email");
    private String email;
 
    @Embedded
    // City 클래스에서 재정의 안했을 경우 ContractInfo 클래스에서도 
    //@AttributeOverrides({
    //    @AttributeOverride(name="zipcode", column=@Column(name="ct_zip")),
    //    @AttributeOverride(name="address1", column=@Column(name="city_addr1")),
    //    @AttributeOverride(name="address2", column=@Column(name="city_addr2"))
    //})
    private Address address;
}
 
 
cs



밸류를 다른 테이블에 저장하기

sight 테이블을 Sight 엔티티와 Address 밸류로 매핑한 것처럼 하나의 테이블을 엔티티와 밸류로 저장할 수도 있지만, 
엔티티와 밸류를 각각의 테이블로도 저장할 수 있다. UML로 표현하면 아래와 같은 구조를 가진다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Embeddable
public class SightDetail {
    @Column(name="hours_op")
    private String hourcOfOperation;
    private String holidays;
    private String facilites;
}
 
@Entity
@SecondaryTable(
    name ="sight_detail",
    pkJoinColumns= @PrimaryKeyJoinColumn(     //sight_detail의 sight_id(기본키=외래키)가 sight테이블의 id(식별자)를 참조함
        name = "sight_id", referencedColumnName = "id")
public class Sight {
    // 생략..
 
    // table = sight_detail은 데이터를 해당 테이블에서 가져오겠다는 의미
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(
            name="hoursOfOperation",
            column=@Column(name="hours_op", table="sight_detail")),
        @AttributeOveride( 
            name="holidays",
            column=@Column(table="sight_detail")),
        @AttributeOverride(
            name="facilities",
            column=@Column(table="sight_detail"))
    })
    private SightDetail detail;
}
cs




'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

+ Recent posts