Web/JPA 2019. 2. 2. 22:17

JPA - 상속 관계 매핑, @Inheritance, @DiscriminatorColumn



사실 관계형 데이터베이스에는 객체지향 언어에서 다루는 상속이라는 개념이 없다. 대신 슈퍼타입 서브타입관계라는 모델링 기법이 객체의 상속 개념과 가장 유사하다. ORM에서 이야기하는 상속 관계 매핑은 객체의 상속구조와 데이터베이스의 슈퍼타입 서브타입 관계를 매핑하는 것이다.


슈퍼타입 서브타입 논리 모델을 실제 물리 모델인 테이블로 구현할 때는 3가지 방법을 선택할 수 있다.





  

 각각의 테이블로 변환

 4개 각각을 모두 테이블로 만들고 조회할 때 조인을 사용한다.(조인전략)

 통합 테이블로 변환

 단 하나의 테이블을 사용해서 상속관계를 통합한다.(단일 테이블 전략)

 서브타입 테이블로 변환

 서브 타입마다 하나의 테이블을 만든다(Entity-per-table 전략)







조인전략


조인전략은 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본키 + 외래키로 사용하는 전략이다. 조회할 때 조인을 자주 사용한다. 그리고 이 전략을 사용할 때 주의할 점이 객체는 타입으로 구분가능하지만 테이블은 타입의 개념이 없다. 즉, 부모테이블에서 자식테이블의 타입을 구분할 칼럼이 존재해야한다.



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
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
//자식테이블을 구분할 구분자 컬럼이름을 지어준다.
@DiscriminatorColumn(name = "DTYPE")
@Getter
@Setter
public abstract class Item {
    @Id
    @Column(name = "ITEM_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "ITEM_SEQ_GENERATOR")
    @TableGenerator(
            name="ITEM_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="ITEM_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=1
    )
    private Long id;
 
    private String name;
 
    private int price;
}
 
@Entity
@DiscriminatorValue("Album")
/*
 * 상속매핑을 할때 디폴트로 자식테이블은 부모테이블의 ID칼럼명을 그대로 사용하는데,
 * 만약 자식테이블의 기본 키 칼럼명을 재정의 하고 싶다면 밑의 어노테이션으로 하면된다.
 */
@PrimaryKeyJoinColumn(name = "ALBUM_ID")
@Getter
@Setter
public class Album extends Item{
    private String artist;
}
 
@Entity
//default = entity class name
@DiscriminatorValue("MOVIE")
@Getter
@Setter
public class Movie extends Item{
    private String director;
    private String actor;
}
cs



위와 같이 조인전략을 이용하여 상속관계를 정의해주었다. 대부분의 어노테이션은 주석으로 충분히 설명이 될 것같아서 생략한다.


구분 

설명 

장점 

-테이블이 정규화된다.

-외래 키 참조 무결성 제약조건을 활용할 수 있다.

-저장공간을 효율적으로 사용한다 

 단점

-조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.

-조회 쿼리가 복잡하다.

-데이터를 등록할 INSERT SQL을 두번 실행한다.(부모,자식테이블)

 특징

-JPA 표준 명세는 구분 컬럼을 사용하도록 하지만 하이버네이트를 포함한 몇몇 구현체는 구분컬럼(@DisciminatorColumn) 없이도 동작한다.






단일 테이블 전략



테이블을 하나만 사용하는 전략이다. 구분칼럼으로 어떤 자식 데이터가 저장되었는지 구분한다. 조회할때 조인이 필요없으므로 일반적으로 3전략중 가장 빠르다.

하지만 이 전략을 사용할 때 주의점은 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다는 점이다. 예를 들어서 Book엔티티를 저장하면 나머지 자식 엔티티의 필드는 필요없기에 null로 들어가기 때문이다.


소스는 위의 소스에서 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)로 지정해주면 된다.


구분 

설명 

장점 

-조인이 필요없으므로 일반적으로 조회 속도가 빠르다

-조회 쿼리가 단순하다.

단점 

-자식 엔티티가 매핑한 칼럼은 모두 null을 허용해야한다.

-단일 테이블에 모든 것을 저장하므로 자식엔티티가 많아지거나 컬럼수가 각 엔티티별로 많다면  테이블이 엄청 커져서 오히려 조회 성능이 나빠질 수 있다.

특징 

-구분 칼럼을 꼭 사용해야한다. 따라서 @DiscriminatorColumndms 꼭 필수 어노테이션이다. 




Table-per-Concrete-Class 전략



자식 엔티티마다 테이블을 만드는 전략이다.

소스는 위와같이 strategy = InheritanceType.TABLE_PER_CLASS로 바꿔주면된다. 이 전략은 데이터베이스 설계자와 ORM전문가 둘 다 일반적으로 추천하지 않는 전략이라고 한다.


구분 

설명 

장점 

-서브타입을 구분해서 처리할 때 효과적이다.

-not null 제약조건을 사용할 수 있다. 

단점 

-여러 자식 테이블을 함께 조회할 때 성능이 느리다.(SQL의 UNION사용)

-자식 테이블을 통합해서 쿼리하기 어렵다. 

특징 

-구분 컬럼을 사용하지 않는다. 



posted by 여성게
:
Web/JPA 2019. 1. 24. 13:02

JPA- 연관관계 외래키의 주인과 주인의 참조자 관계





JPA에서 일대일,일대다,다대일,다대다 관계에서는 항상 연관관계의 주인이 존재한다. 연관관계의 주인이라고 함은 데이터베이스 테이블에서 외래키의 주인을 뜻한다. 보통 일대다,다대일(다대다 포함, 다대다는 일대다,다대일관계로 매핑을 시킴) 관계에서는 보통 다쪽에 외래키가 존재한다. 일단 예제를 보면서 설명하겠다.






연관관계 예시



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
package com.spring.jpa.entitiy;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
 
@Entity
@Table(name = "TEAM_MEMBER")
public class TeamMember {
    
    @Id
    @Column(name = "MEMBER_ID")
    private Long id;
    
    @Column(name = "MEMBER_NAME")
    private String memberName;
    
    //연관관계 매핑, 한팀에 여러 회원이 소속될 수 있음으로 다대일 관계이다.
    @ManyToOne
    //외래키로 사용될 컬럼의 이름이다.(디폴트는 해당 객체의 필드명+조인테이블의 칼럼명이다.)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getMemberName() {
        return memberName;
    }
 
    public void setMemberName(String memberName) {
        this.memberName = memberName;
    }
 
    public Team getTeam() {
        return team;
    }
    
    //연관관계에서는 객체관점에서도 똑같이 값을 세팅해주는 것이 좋다.
    //team.getMembers().add(this)는 데이터베이스에는 절대 영향을 미치지는 않지만
    //객체관점에서는 같이 set을 해주는 것이 맞다.
    public void setTeam(Team team) {
        this.team = team;
        
        if(!team.getMembers().contains(this)) {
            team.getMembers().add(this);
        }
    }
 
    @Override
    public String toString() {
        return "TeamMember [id=" + id + ", memberName=" + memberName + ", team=" + team.getName() + "]";
    }
    
}
 
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.spring.jpa.entitiy;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name = "TB_TEAM")
public class Team {
    
    @Id
    @Column(name = "TEAM_ID")
    private Long id;
    
    
    @Column(name = "TEAM_NAME")
    private String name;
    
    //컬렉션의 제네릭타입이 존재할때
    //양방향 관계를 정의할때, mappedBy = "team" 여기서 team은 TeamMember테이블의
    //Team 엔티티의 필드 네임이다.(테이블 칼럼명이 아니다)
    @OneToMany(mappedBy = "team")
    private List<TeamMember> members = new ArrayList<TeamMember>();
    
    //컬렉션의 제네릭타입이 존재하지 않을때
    /*@OneToMany(targetEntity=TeamMember.class)
    private List members;*/
 
    public Long getId() {
        return id;
    }
 
 
    public void setId(Long id) {
        this.id = id;
    }
 
 
    public String getName() {
        return name;
    }
 
 
    public void setName(String name) {
        this.name = name;
    }
 
 
    public List<TeamMember> getMembers() {
        return members;
    }
 
 
    public void setMembers(List<TeamMember> members) {
        this.members = members;
    }
 
 
    @Override
    public String toString() {
        return "Team [id=" + id + ", name=" + name + ", members=" + Arrays.toString(members.toArray()) + "]";
    }
    
    
}
 
cs


데이터베이스에서는 양방향 참조가 가능하다.(외래키를 이용) 하지만 JPA에서는 사실 양방향이라는 것은 존재하지 않는다. 즉, 각각 다른 단방향으로 마치 양방인것처럼 꾸미는 것이다. 여기서 중요한 것은 연관관계의 주인인 TeamMember에서는 JPA로 조회를 할 경우 연관관계에 있는 Team 필드가 데이터로 채워진다. 그말은 즉, 객체 그래프탐색이 가능하다는 것이다. 하지만 mappedBy ="team"으로 어노테이션 속성이 달린 연관관계의 주인이 아닌 엔티티는 JPA로 조회를 해도 연관관계에 있는 엔티티리스트를 가져오지 않는다. 그렇기 때문에 연관관계의 주인인 엔티티의 setter에서 위와같은 로직이 포함되어야 한다. 그래야 연관관계의 주인이 아닌 엔티티에서도 객체 그래프 탐색이 가능하다. 왜냐하면 영속성 컨텍스트에서는 원래 엔티티의 복사본을 가지고 있기 때문에 원래 엔티티의 복사본과 1차캐시에서 가져간 엔티티를 비교하여 변경이 발생한다면 1차 캐시에 변경된 내용을 반영하여 주인이 아닌 엔티티에서도 해당 연관관계의 엔티티 데이터가 반영이 된다. 하지만 여기서 중요한 것은 setter로 엔티티를 변경하던가 위의 setter에 로직이 추가되어 List에 add가 되던 1차캐시의 복사본과 비교를 통해 변경을 반영하려면 반드시 트랜잭션 내에 존재해야한다는 것이다.

posted by 여성게
:
Web/JPA 2019. 1. 24. 11:58

JPA - 다대다 연관관계(@ManyToMany),N:N





설명에 앞서 사실 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 곤계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다,다대일 관계로 풀어내는 연결 테이블을 사용한다. 왜냐하면 다대다 관계를 1:1 테이블 매핑은 한다고 생각해보자. 회원과 상품의 관계인데, 한 회원이 여러개의 상품을 구입할 수 있고, 한 상품(ID)이 여러 회원에 의해 구입될 수 있다. 그렇다면 서로 몇개까지 살 수 있냐라는 제한이 없으면 외래키가 유동적으로 늘어난다. 그렇다면 엄청 많은 외래키를 굳이 미리 생성할 필요도 없다. 즉, 이렇게 몇개인지 알수 없는 다대다 관계를 중간에 연결 테이블 하나를 두고 일대다, 다대일 관계로 매핑을 시켜주는 것이다. 연결테이블은 단순히 하나의 로우에 회원의 기본키,상품의 기본키를 가지고 있으면 되므로 관계의 수가 늘어나면 단순히 로우의 수만 증가시켜주면 되기 때문이다.







(현재소스와는 관계없는 그림)



다대다(@ManyToMany) 매핑 방법 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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package com.spring.jpa.entitiy;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
 
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
 
import com.spring.jpa.common.RoleType;
 
 
/*
 * 유니크키 설정 및 nullable,length 등의 속성은 모두 auto DDL을 사용했을 때만 유효한 설정이다.
 * 즉, 테이블을 직접 생성한다면 적용되지 않는다. 하지만 테이블과 객체간의 관계표현에 있어 해당 설정들을 해놓으면
 * 엔티티 클래스만 봐도 테이블의 구조가 파악되기에 가독성을 위해서라도 설정을 해놓는 것이 좋다.
 */
/*
 * sequence table
 * CREATE TABLE MY_SEQUENCE(
 *    sequence_name varchar2(255) PRIMARY KEY,
 *    next_val number(22,0)
 * )
 */
@Entity
@Table(name = "MEMBER"
       ,uniqueConstraints = {
           @UniqueConstraint(
                   name = "NAME_AGE_UNIQUE",
                   columnNames = {"NAME","AGE"//uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
           )
})
public class Member {
    
    @Id
    @Column(name = "MEMBER_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBER_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBER_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    /*
     * not null
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",nullable=false,length=10)
    private String username;
    
    private Integer age;
    
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",nullable=false,length=20)
    private RoleType roleType;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Access(AccessType.FIELD)
    private Date createdDate = new Date();
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
    
    @Lob
    private String description;
    
    @ManyToMany
    //다대다를 일대다-다대일 관계로 연결해줄 테이블명
    @JoinTable(name = "MEMBER_PRODUCT_CONN",
               joinColumns = @JoinColumn(name = "MEMBER_ID"),//멤버랑 연결시켜줄 연결테이블의 컬럼명(현재방향)
               inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))//상품과 연결시켜줄 연결테이블의 칼럼명(반대방향)
    private List<Product> products = new ArrayList<Product>();
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public RoleType getRoleType() {
        return roleType;
    }
 
    public void setRoleType(RoleType roleType) {
        this.roleType = roleType;
    }
 
    public Date getCreatedDate() {
        return createdDate;
    }
 
    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }
 
    public Date getLastModifiedDate() {
        return lastModifiedDate;
    }
 
    public void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    public List<Product> getProducts() {
        return products;
    }
 
    public void setProducts(List<Product> products) {
        this.products = products;
    }
 
    @Override
    public String toString() {
        return "Member [id=" + id + ", username=" + username + ", age=" + age + ", roleType=" + roleType
                + ", createdDate=" + createdDate + ", lastModifiedDate=" + lastModifiedDate + ", description="
                + description + ", products=" + Arrays.toString(products.toArray()) + "]";
    }
 
    
    
}
 
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
53
54
55
56
57
58
59
60
61
62
63
package com.spring.jpa.entitiy;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
 
@Entity
@Table(name = "TB_PRODUCT")
public class Product {
    
    @Id
    @Column(name = "PRODUCT_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "PRODUCT_SEQ_GENERATOR")
    @TableGenerator(
            name="PRODUCT_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="PRODUCT_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    @Column(name = "PRODUCT_NAME")
    private String name;
    
    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<Member>();
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Member> getMembers() {
        return members;
    }
 
    public void setMembers(List<Member> members) {
        this.members = members;
    }
    
    
}
 
cs



@ManyToMany
    //다대다를 일대다-다대일 관계로 연결해줄 테이블명
    @JoinTable(name = "MEMBER_PRODUCT_CONN",
               joinColumns = @JoinColumn(name = "MEMBER_ID"),//멤버랑 연결시켜줄 연결테이블의 컬럼명(현재방향)
               inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))//상품과 연결시켜줄 연결테이블의 칼럼명(반대방향)
    private List<Product> products = new ArrayList<Product>();



매핑에 대한 상세 설명을 하자면 우선 연관관계를 맺을 필드에 @ManyToMany 어노테이션을 달아준다. 이것은 현재 참조하고 있는 컬렉션과 다대다 관계임을 명시해준다. 하지만 여기서 의문이 드는것이 "다대다 관계가 안된다며?"이다. 이것은 다음 속성에 나온다. @JoinTable로 중간에 연결테이블에 대한 속성을 정의해준다. name속성은 연결테이블의 이름, joinColums는 연관관계를 맺어줄 연결테이블의 컬럼을 정의해준다. @JoinColums는 현재 회원테이블에 대한 외래키이고, inverseJoinColums는 반대쪽 상품 테이블에 대한 외래키이다. 그리고 반드시 연관관계에는 연관관계의 주인이 있어야 하므로, 회원테이블에 연관관계의 주인임을 명시해주었다.(상품에 mappedBy속성이 있음으로) 즉, @ManyToMany도 결국에 데이터베이스에는 일대다,다대일 관계로 매핑되며 중간에 연결테이블이 생성된다.





이렇게 편하게 다대다 관계를 맺어줄 수 있다. 하지만 이 연관관계는 한가지 한계점이 존재한다. 실무에서는 연결테이블에 단순 외래키만 존재하길 원하지 않는다. 회원이 몇개의 상품을 주문했는지의 수량, 언제 주문했는지 날짜등의 데이터를 연결테이블에 있길 원할 수도 있기 때문인데, 이것은 @ManyToMany로 매핑할 수 없다. 이 한계점을 개선한 다대다 매핑을 다음에 설명한다.





다대다(@ManyToMany -> @OneToMany,@ManyToOne & 복합기본키 ) 매핑 방법 2




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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package com.spring.jpa.manytomanyexpend;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
 
import com.spring.jpa.common.RoleType;
import com.spring.jpa.entitiy.Product;
 
@Entity
@Table(name = "MEMBER_2"
       /*,uniqueConstraints = {
           @UniqueConstraint(
                   name = "NAME_AGE_UNIQUE",
                   columnNames = {"NAME","AGE"} //uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
           )
}*/)
public class Member_2 {
    
    @Id
    @Column(name = "MEMBER_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER2_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBER2_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBER2_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    /*
     * not null
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",/*nullable=false,*/length=10)
    private String username;
    
    private Integer age;
    
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",/*nullable=false,*/length=20)
    private RoleType roleType;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Access(AccessType.FIELD)
    private Date createdDate = new Date();
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
    
    @Lob
    private String description;
    
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts ;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public RoleType getRoleType() {
        return roleType;
    }
 
    public void setRoleType(RoleType roleType) {
        this.roleType = roleType;
    }
 
    public Date getCreatedDate() {
        return createdDate;
    }
 
    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }
 
    public Date getLastModifiedDate() {
        return lastModifiedDate;
    }
 
    public void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    public List<MemberProduct> getMemberProducts() {
        return memberProducts;
    }
 
    public void setMemberProducts(List<MemberProduct> memberProducts) {
        this.memberProducts = memberProducts;
    }
 
    
    
}
 
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
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.spring.jpa.manytomanyexpend;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.TableGenerator;
 
//회원엔티티와 상품엔티티의 다대다 연결을 위한 연결엔티티이다.
@Entity
//복합 기본키 매핑을 위한 식별자 클래스
@IdClass(MemberProductId.class)
public class MemberProduct {
    
    @Id
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID"//외래키의 주인이며, MEMBER_ID로 해당테이블에 외래키가 생성된다.
    private Member_2 member;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID"//위와 동일
    private Product2 product;
    
    private int orderAmount;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public Member_2 getMember() {
        return member;
    }
 
    public void setMember(Member_2 member) {
        this.member = member;
    }
 
    public Product2 getProduct() {
        return product;
    }
 
    public void setProduct(Product2 product) {
        this.product = product;
    }
 
    public int getOrderAmount() {
        return orderAmount;
    }
 
    public void setOrderAmount(int orderAmount) {
        this.orderAmount = orderAmount;
    }
    
    
}
 
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
53
54
55
56
57
package com.spring.jpa.manytomanyexpend;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
 
import com.spring.jpa.entitiy.Member;
 
@Entity
@Table(name = "TB_PRODUCT2")
public class Product2 {
    
    @Id
    @Column(name = "PRODUCT_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "PRODUCT2_SEQ_GENERATOR")
    @TableGenerator(
            name="PRODUCT2_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="PRODUCT2_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    @Column(name = "PRODUCT_NAME")
    private String name;
    
    /*@ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<Member>();*/
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
    
    
}
 
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
53
54
55
56
package com.spring.jpa.manytomanyexpend;
 
import java.io.Serializable;
 
public class MemberProductId implements Serializable{
    
    private Long member;
    private Long product;
    
    
    public Long getMember() {
        return member;
    }
    public void setMember(Long member) {
        this.member = member;
    }
    public Long getProduct() {
        return product;
    }
    public void setProduct(Long product) {
        this.product = product;
    }
    
    //복합키 식별자 클래스는 반드시 밑의 메소드를 오버라이드해주어야한다.
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((member == null) ? 0 : member.hashCode());
        result = prime * result + ((product == null) ? 0 : product.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MemberProductId other = (MemberProductId) obj;
        if (member == null) {
            if (other.member != null)
                return false;
        } else if (!member.equals(other.member))
            return false;
        if (product == null) {
            if (other.product != null)
                return false;
        } else if (!product.equals(other.product))
            return false;
        return true;
    }
    
}
 
cs



이번 다대다 매핑은 명시적으로 어노테이션도 @OneToMany,@ManyToOne으로 연관관계를 맺었다. 이전에는 내부적으로 연결테이블을 생성했지만 지금은 명시적으로 연결 엔티티를 생성해준다. 그리고 연결엔티티에서 @ManyToOne 어노테이션을 갖는다. 이 말은 즉슨, 연결테이블이 연관관계의 주인이 되는 것이다.(보통 데이터베이스에서 다대일,일대다 관계에서 다 쪽에 외래키를 갖는다.) 하지만 여기서 조금 특이한 것이 있다면 @IdClass이다. 이것은 회원과 상품의 기본키를 연결테이블에서 외래키로 사용함과 동시에 두키를 복합키로 하여 기본키를 지정하기 때문에 식별자 클래스가 추가된 것이다.(회원의 외래키와 상품의 외래키를 복합키로 하여 기본키로 지정) 식별자 클래스는 별개 없다. 단순히 각 테이블(회원,상품)의 기본키의 필드타입으로 하여서 두개의 식별자로 사용될 필드를 선언하고 Getter,Setter메소드를 만들어 준 후에 IDE의 기능을 이용하여 hashCode()와 equals()를 자동 구현해주면된다. 그리고 마지막으로 @Embeddable 클래스는 반드시 기본생성자를 필수로 생성해주고, Serializable을 implements 해주면 된다. 하지만 복합키도 좋지만 이렇게 되면 해야할 일이 늘어난다. 식별자 클래스를 만들어주는 등의.... 그래서 다음 매핑방법에는 복합키를 사용하지 않고 연결엔티티에 별도로 기본키를 할당해주어서 식별자 클래스등을 만드는 불편함을 줄이겠다.






다대다(@ManyToMany -> @OneToMany,@ManyToOne ) 매핑 방법 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package com.spring.jpa.manytomanyexpend;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
 
import com.spring.jpa.common.RoleType;
import com.spring.jpa.entitiy.Product;
 
@Entity
@Table(name = "MEMBER_2"
       /*,uniqueConstraints = {
           @UniqueConstraint(
                   name = "NAME_AGE_UNIQUE",
                   columnNames = {"NAME","AGE"} //uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
           )
}*/)
public class Member_2 {
    
    @Id
    @Column(name = "MEMBER_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER2_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBER2_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBER2_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    /*
     * not null
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",/*nullable=false,*/length=10)
    private String username;
    
    private Integer age;
    
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",/*nullable=false,*/length=20)
    private RoleType roleType;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Access(AccessType.FIELD)
    private Date createdDate = new Date();
    
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
    
    @Lob
    private String description;
    
    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<MemberProduct>();
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public RoleType getRoleType() {
        return roleType;
    }
 
    public void setRoleType(RoleType roleType) {
        this.roleType = roleType;
    }
 
    public Date getCreatedDate() {
        return createdDate;
    }
 
    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }
 
    public Date getLastModifiedDate() {
        return lastModifiedDate;
    }
 
    public void setLastModifiedDate(Date lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    public List<MemberProduct> getMemberProducts() {
        return memberProducts;
    }
 
    public void setMemberProducts(List<MemberProduct> memberProducts) {
        this.memberProducts = memberProducts;
    }
 
    
    
}
 
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.spring.jpa.manytomanyexpend;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
 
import com.spring.jpa.entitiy.Member;
 
@Entity
@Table(name = "TB_PRODUCT2")
public class Product2 {
    
    @Id
    @Column(name = "PRODUCT_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "PRODUCT2_SEQ_GENERATOR")
    @TableGenerator(
            name="PRODUCT2_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="PRODUCT2_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    @Column(name = "PRODUCT_NAME")
    private String name;
    
    @OneToMany(mappedBy = "product")
    private List<MemberProduct> members = new ArrayList<MemberProduct>();
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<MemberProduct> getMembers() {
        return members;
    }
 
    public void setMembers(List<MemberProduct> members) {
        this.members = members;
    }
    
    
}
 
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
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
package com.spring.jpa.manytomanyexpend;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.TableGenerator;
 
//회원엔티티와 상품엔티티의 다대다 연결을 위한 연결엔티티이다.
@Entity
//복합 기본키 매핑을 위한 식별자 클래스
/*@IdClass(MemberProductId.class)*/
public class MemberProduct {
    
    @Id
    @Column(name = "MEMBERPRODUCT_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBERPRODUCT_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBERPRODUCT_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBERPRODUCT_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
    
    /*@Id*/
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID"//외래키의 주인이며, MEMBER_ID로 해당테이블에 외래키가 생성된다.
    private Member_2 member;
    
    /*@Id*/
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product2 product;
    
    private int orderAmount;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public Member_2 getMember() {
        return member;
    }
 
    public void setMember(Member_2 member) {
        this.member = member;
    }
 
    public Product2 getProduct() {
        return product;
    }
 
    public void setProduct(Product2 product) {
        this.product = product;
    }
 
    public int getOrderAmount() {
        return orderAmount;
    }
 
    public void setOrderAmount(int orderAmount) {
        this.orderAmount = orderAmount;
    }
    
    
}
 
cs



크게 바뀐 것은 없다. 식별자 클래스가 없어지고, 연결엔티티에 외래키를 기본키로 사용하는 @Id가 없어지는 등 조금의 수정이 이루어졌을 뿐이다. 단순히 시퀀스를 기본키로 사용하므로서 복합키 매핑등의 조금은 복잡한 과정이 빠져 더 쉽게 연관관계 매핑을 할 수 있다. 위의 코드와 비교해보면 차이점을 금방 알 수 있다.

posted by 여성게
:
Web/JPA 2019. 1. 17. 22:51

JPA 기본 키 매핑 전략,@Id


JPA에서 기본 키 매핑 전략에는 크게 4가지가 있다.


1)직접 할당 : 기본 키를 애플리케이션에서 직접 엔티티클래스의 @Id 필드에 set해준다.

2)자동 생성 : 대리 키 사용 방식

- IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.(ex MySQL - AUTO INCREMENT...)

- SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.(ex Oracle sequence...)

- TABLE : 키 생성 테이블을 사용한다.(ex 시퀀스용 테이블을 생성해서 테이블의 기본키를 저장하고 관리한다.)


자동 생성 전략이 이렇게 다양한 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문이다. 위 중에서 IDENTITY와 SEQUENCE는 데이터베이스 벤더에 의존적이다. 하지만 TABLE 전략은 키 생성용 테이블을 하나 만들어두고 마치 시퀀스처럼 사용하는 방법이기에 벤더에 의존하지 않는다.(하지만 각각 장단점이 존재함)


*키 생성 전략을 사용하려면 persistence.xml 혹은 application.properties(이것은 구글링 해봐야 할 듯)에 hibernate.id.new_generator_mappings = true 설정을 해주어야한다.






IDENTITY 전략


IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. 주로 MySQL,PostgreSQL,SQL Server,DB2에서 사용한다


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
/*
 * 유니크키 설정 및 nullable,length 등의 속성은 모두 auto DDL을 사용했을 때만 유효한 설정이다.
 * 즉, 테이블을 직접 생성한다면 적용되지 않는다. 하지만 테이블과 객체간의 관계표현에 있어 해당 설정들을 해놓으면
 * 엔티티 클래스만 봐도 테이블의 구조가 파악되기에 가독성을 위해서라도 설정을 해놓는 것이 좋다.
 */
@Entity
@Table(name = "MEMBER"
        ,uniqueConstraints = {
        @UniqueConstraint(
                name = "NAME_AGE_UNIQUE",
                columnNames = {"NAME","AGE"//uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
        )
})
@Getter
@Setter
@ToString
public class Member {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
 
    /*
     * not null
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",nullable=false,length=10)
    private String username;
 
    private Integer age;
 
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",nullable=false,length=20)
    private RoleType roleType;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
 
    @Lob
    private String description;
}
 
cs


IDENTITY 전략의 단점이라고 있는 특징이 있다. 그것은 데이터베이스에 값을 저장하고 나서야 기본 값을 구할 있다. 여기서 기본  

값을 구하는 것이 무슨 상관이냐? 라고 말할 있지만 영속성 컨텍스트에 1차캐시(엔티티의 영속상태화) 하기 위해서는 구분자로 기본키(@Id) 필드를 이용한다. , 영속성

컨텍스트에 캐싱을 하기위한 primary key 값을 가져오기 위하여 테이블을 추가로 조회하게 된다. 그래서 다른 전략과는 다른 행동중 하나가 persist()호출을

하자마자 지연쓰기를 하는 것이 아니라, primary key값을 가져오기 위하여 바로 flush 호출하게 된다.







SEQUENCE 전략




데이터베이스의 시퀀스 오브젝트를 이용하여 유일한 값을 순서대로 생성한다. 전략은 주로 Oracle,PostgreSQL,DB2,H2 등의 데이터베이스에서 사용할 있다.


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
/*
 * 유니크키 설정 및 nullable,length 등의 속성은 모두 auto DDL을 사용했을 때만 유효한 설정이다.
 * 즉, 테이블을 직접 생성한다면 적용되지 않는다. 하지만 테이블과 객체간의 관계표현에 있어 해당 설정들을 해놓으면
 * 엔티티 클래스만 봐도 테이블의 구조가 파악되기에 가독성을 위해서라도 설정을 해놓는 것이 좋다.
 */
/*
 * sequence table
 * CREATE TABLE MY_SEQUENCE(
 *    sequence_name varchar2(255) PRIMARY KEY,
 *    next_val number(22,0)
 * )
 */
@Entity
@SequenceGenerator(
        name="BOARD_SEQ_GENERATOR",
        sequenceName="BOARD_SEQ",
        initialValue=1,allocationsSize=1
)
@Table(name = "MEMBER"
        ,uniqueConstraints = {
        @UniqueConstraint(
                name = "NAME_AGE_UNIQUE",
                columnNames = {"NAME","AGE"//uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
        )
})
@Getter
@Setter
@ToString
public class Member {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy=GenerationType.SEQUENCE
            ,generator="BOARD_SEQ_GENERATOR"
    )
    private Long id;
 
    /*
     * not null
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",nullable=false,length=10)
    private String username;
 
    private Integer age;
 
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",nullable=false,length=20)
    private RoleType roleType;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
 
    @Lob
    private String description;
}
cs




IDENTITY와는 다른 설정이 있다면 시퀀스생성기 어노테이션이다. 여기서 name속성은 실제 @Id필드에서 참조할 이름이라고 생각하면 되고, sequenceName 

실제 데이터베이스에 생성되는 시퀀스 오브젝트 이름이다. 그리고 시퀀스 초기값과 allocationSize라는 속성이 있다. 여기서 allocationSize 실제 데이터베이스에서

가져오는 시퀀스의 한번 호출에 증가하는 값의 크기이다. 이것은 성능 최적화와 관련된 속성이므로 마지막에 따로 설명한다.

SEQUENCE 전략은 em.persist() 호출할 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회한다.(실제 엔티티에 할당할 primary key) 그리고 조회한 식별자를

엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장한다. 이후 커밋이 일어나게 되면 실제 데이터베이스에 INSERT되게 된다.








TABLE 전략




TABLE 전략은 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 이것은 벤더에 의존적이지 않은 전략이다.

전략을 사용할 auto DDL설정을 했다면 상관없지만 나중에 프로덕환경에서의 데이터베이스 설계에서 시퀀스 테이블에 생성이 선행되어야한다.


CREATE TABLE APP_SEQUENCE(

sequence_name varchar2(255) primary key,

next_val number(22,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
53
54
55
56
57
58
59
/*
 * 유니크키 설정 및 nullable,length 등의 속성은 모두 auto DDL을 사용했을 때만 유효한 설정이다.
 * 즉, 테이블을 직접 생성한다면 적용되지 않는다. 하지만 테이블과 객체간의 관계표현에 있어 해당 설정들을 해놓으면
 * 엔티티 클래스만 봐도 테이블의 구조가 파악되기에 가독성을 위해서라도 설정을 해놓는 것이 좋다.
 */
/*
 * sequence table
 * CREATE TABLE MY_SEQUENCE(
 *    sequence_name varchar2(255) PRIMARY KEY,
 *    next_val number(22,0)
 * )
 */
@Entity
@Table(name = "MEMBER"
        ,uniqueConstraints = {
        @UniqueConstraint(
                name = "NAME_AGE_UNIQUE",
                columnNames = {"NAME","AGE"//uniqueConstraints는 auto DDL 속성을 사용할때만 유효한 설정이다.
        )
})
public class Member {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBER_SEQ_GENERATOR",
            table="MY_SEQUENCE"//시퀀스 생성용 테이블 이름
            pkColumnName="sequence_name"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBER_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=50
    )
    private Long id;
 
    /*
     * varchar2(10) -> 기본값 255;
     */
    @Column(name = "NAME",nullable=false,length=10)
    private String username;
 
    private Integer age;
 
    /*
     * EnumType의 기본값 설정은 정수이다.
     */
    @Enumerated(EnumType.STRING)
    @Column(name="ROLE_TYPE",nullable=false,length=20)
    private RoleType roleType;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;
 
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;
 
    @Lob
    private String description;
}
 
 
cs




MEMBER_SEQ_GENERATOR라는 테이블 생성기를 등록하였다. 여기의 설정들은 모두 주석으로 설명을 걸어놓았다






allocationSize 이용한 성능 최적화?





영속성 컨텍스트에 엔티티를 저장하기 위해 식별자를 구하는 과정을 설명하면,


1)식별자를 구하려고 데이터베이스 시퀀스를 조회한다.

->select board_seq.nextval from dual


그렇다면 만약 increment by 1이라는 설정이라면? 엔티티를 하나하나 저장할 때마다 데이터베이스에 엑세스하여 시퀀스 값을 가져와야한다.(allocationSize=1일때)


여기에서 allocationSize 이용하여 성능 최적화를 있다. allocationSize값을 적절히 크기를 키워 설정한 값만큼 번에 시퀀스 값을 증가시키고 나서

그만큼 메모리에서 기억해 시퀀스 자체를 메모리에서 할당하는 것이다. 예를 들어 allocationSize 값이 50이면 시퀀스를 한번 가져올때 마다 한번에 50 증가된

값을 받아온다. 그말은 처음 시퀀스 (50) 받아오면 1~50까지는 메모리에서 엔티티에 식별자를 할당한다. 그리고 51 되면 시퀀스 (100) 한번더 가져와 

51~100까지를 메모리에서 다시 할당해준다. 이말은 쉽게 말하자면 allocationSize=1 때보다 시퀀스 값을 가져오기 위해 데이터베이스에 엑세스하는 횟수를 49번을

줄인 것이다. insert 성능이 크게 중요하지 않은 애플리케이션은 상관없지만 반대는 성능최적화 전략을 사용해보는 것도 좋은 방안일 듯하다. 하지만 제대로

사용하지 못하면 아무리 좋은 것도 독이 되니 설계해서 쓰는 것이 좋을 같다.








기타





사실은 전략중에 AUTO전략이라는 것도 있다. 이것은 데이터베이스 벤더에 따라 자동으로 위의 3가지방법중 하나를 선택해 준다. 장점은 데이터베이스 벤더가 바뀌어도

코드에 수정이 없다는 것이다. 하지만 중요한 것은 사상이다. JPA 엔티티 클래스만 봐도 테이블이 예상이 되어야하는데 AUTO 가독성이 많이 떨어지기에

크게 추천하지 않고, 위의 3가지 방법중 하나를 선택하여 명시적으로 설정해주는 것이 좋을 같다.(개인적인 생각)

posted by 여성게
:
Web/JPA 2019. 1. 15. 22:04

JPA 영속성 관리 - persistence context(영속성컨텍스트)


JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있다.


엔티티 매니저 팩토리와 엔티티 매니저


EntityManager는 엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련된 모든 일을 처리한다. 이름 그대로 엔티티를 관리하는 관리자다. 개발자 입장에서 엔티티 매니저는 엔티티를 저장하는 가상의 데이터베이스로 생각하면 될 듯 싶다.


그러면 엔티티 매니저 팩토리란 무엇인가? 바로 이 엔티티 매니저를 생성해주는 공장역할을 하는 객체이다. 보통 엔티티 매니저 팩토리는 생성비용이 크게 때문에 하나만 생성해서 모든 스레드가 공유할 수 있도록 설계되었고, 엔티티 매니저는 사용할 때마다 새로 생성하면 된다. 즉, 엔티티 매니저는 데이터베이스 커넥션과 관련이 있는 객체이기에 스레드간 공유가 되서는 안되는 객체이다. 이 엔티티 매니저는 데이터베이스와 커넥션을 맺지만 꼭 필요할 때 커넥션을 맺는다.(트랜잭션이 시작되었을 경우) 


Ex) EntityManagerFactory emf = Persistence.createEntityManagerFactory("persist"); -> "persist"는 persistence.xml에 생성한 persistence unit 명이다.

      EntityManager em = ema.createEntityManager();





영속성 컨텍스트란?


영속성 컨텍스트란 JPA에서 가장 중요한 용어 중 하나이다. 이것을 한글로 표현하면 "엔티티를 영구 저장하는 환경"이라고 표현해도 좋을 것 같다. 즉, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.


em.persist(entity)는 실제로 데이터베이스에 회원 엔티티를 저장하는 것이 아니고, 엔티티 매니저를 사용해 엔티티를 영속성 컨텍스트에 저장하는 것이다.



엔티티의 생명주기


비영속 : 영속성 컨텍스트와 전혀 관계가 없는 상태이다.

영속: 영속성 컨테스트에 저장된 상태

준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제: 삭제된 상태



비영속 상태를 예를 들면 엔티티 객체를 생성한 상태이다.


Member m = new Member(); -> 지금 이 상태는 순수한 객체 상태이며, 아직 저장하지 않은 것이라서 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 것이다.


영속 상태를 예를 들면 em.persist(entity) 혹은 em.find(entity) 등을 호출한 상태이며, 이 상태는 영속성 컨테스트가 관리하는 엔티티가 되었다 라는 뜻이다. 


준영속 상태는 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태를 말한다. 예를 들면, em.detach(),em.close(),em.clear() 등 분리,닫기,초기화 등의 메소드를 호출하게 되면 영속된 엔티티들은 모두 준영속 상태가 된다.


삭제 상태는 말그대로 영속성 컨텍스트(+데이터베이스)에서 상태가 되는 것이다. em.remove(entity)




영속성 컨텍스트의 특징


1)영속성 컨텍스트와 식별자 값 - 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본키와 매핑한 값)으로 구분한다. 즉, 영속상태에 있으려면 반드시 식별자 값이 존재해야한다. 만약 식별자 값이 없다면 예외가 발생하게 된다.


2)JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는 데, 이것을 Flush라고 한다.


영속성 컨텍스트가 엔티티를 관리하면서의 장점

-1차 캐시 : persist메소드를 호출하면 commit이 호출되기 전까지 1차 캐시에 저장된다. 즉, find 메소드를 호출하면 데이터베이스에서 직접 엑세스해서 가져오는 것이 아니라, 메모리의 캐시에서 엔티티를 가져오게 되므로 빠른 속도와 적은 비용으로 조회가 가능하다.


-동일성 보장 : 아무리 같은 엔티티 키값으로 여러번 조회를 해도 1차 캐시에서 하나의 객체만 리턴해주기 때문에 항상 동일성을 보장한다.(같은 인스턴스를 가르킨다.)


-트랜잭션을 지원하는 쓰기 지연 : JPA는 commit 호출전까지 데이터베이스에 쓰기를 하지 않는다. 그말은 쿼리를 쿼리 저장소에 차곡차곡 모아두다가 commit이 되는 시점에 모아둔 모든 쿼리를 데이터베이스에 반영하는 지연작업을 한다.


-변경 감지:ex) Member m = em.find(Member.class,"1");

                       m.setAge(27);


JPA는 이렇게 set메소드로 데이터만 변경했을 뿐이지만 commit 시점에 데이터베이스의 데이터가 update된다. 왜냐? 영속성 컨텍스트에는 항상 엔티티의 복사본인 스냅샵을 가지고 있기 때문에 commit 시점에 스냅샵과 현재 엔티티의 값을 비교해 다른 점이 있다면 update쿼리를 쿼리저장소에 보내서 commit될때 데이터베이스에 반영이 된다. 하지만 가정이 있다. 반드시 영속상태의 엔티티만 적용이 된다는 것이다.

업데이트 쿼리는 모든 필드를 대상으로 업데이트를 한다.(수정 쿼리가 항상 같기에 애플리케이션 로딩시점에 수정 쿼리를 미리 생성해두고 재사용 할 수 있고, 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한번 파싱된 쿼리를 재사용한다.) 물론 변경된 대상으로만 update쿼리를 보내는 방법도 있는데 이것은 hibernate 확장기능을 이용해야한다(DynamicUpdate,DynamicInsert..)

-지연 로딩



posted by 여성게
:
Web/JPA 2018. 10. 14. 19:24

JPA는 쿼리가 너무 정적이라는 단점이 존재합니다. 이러한 단점을 극복하기 위하여 JAP+QueryDSL을 통해 type-safe하고, 자바의 메소드를 통한 동적인(유연한) 쿼리 작성이 가능해집니다.


이전 글에서 JPA사용법에 대하여 간단히 작성했지만 이번 글에서도 JPA부터 설명하겠습니다.



pom.xml 설정


JPA 및 querydsl 등 각종 dependency 설정입니다.



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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
<!--querydsl version 명시-->
        <querydsl.version>4.1.4</querydsl.version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
<!--querydsl dependency-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.0</version>
        </dependency>
<!--JPA dependency-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> 
            <version>${org.springframework-version}</version> </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
<!--maven plugin 추가-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <!-- 사용할 자바버전 -->
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
<!--Q클래스들이 생성될 위치입니다.-->
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 

cs




Entity class 작성


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
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
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
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


maven plugin에서 JpaAnnotationProccesor가 @Entity를 보고 관련 Q클래스를 생성해줍니다.



maven build




run 해주면 target>generate-sources/java에 해당 Q클래스들이 생성됩니다. 만약 생성이 되었지만 Q클래스들을 인식하지 못하였을 경우에는 build path에 해당 폴더를 add folder해줍니다. 



Repository 작성



1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import java.util.List;
 
import com.spring.jpa.model.UserEntity;
 
public interface JpaUserRepositoryCustom {
    List<UserEntity> findAllLike(String keyword);
}
 
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
package com.spring.jpa.repository;
 
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
 
import com.querydsl.jpa.JPQLQuery;
import com.spring.jpa.model.QUserEntity;
import com.spring.jpa.model.UserEntity;
 
@Repository
public class JpaUserRepositoryImpl extends QuerydslRepositorySupport implements JpaUserRepositoryCustom{
 
    public JpaUserRepositoryImpl() {
        super(UserEntity.class);
        // TODO Auto-generated constructor stub
    }
 
    @Override
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
 
    @Override
    public List<UserEntity> findAllLike(String keyword) {
        // TODO Auto-generated method stub
        QUserEntity qUserEntity=QUserEntity.userEntity;
        JPQLQuery<UserEntity> query=from(qUserEntity);
        List<UserEntity> userEntityList=query.where(qUserEntity.userName.like(keyword)).fetch();
        return userEntityList;
    }
 
}
 
cs


커스텀레포지터리 구현체를 만들어줍니다. 주의해야할점은 생성자에서 super(domain.class)입니다. 도메인클래스 인자는 해당 구현체가 필요한 것이 아니라, QuerydslRepositorySupport이 필요한 인자입니다. 꼭 넣어주어야합니다 !!!!




그리고 커스텀인터페이스에서 선언한 메소드를 오버라이드하여, querydsl을 이용한 type-safe한 동적인 쿼리를 작성해줍니다. 여기서 querydsl은 일반 Jpa 레포지토리의 기능을 업그레이드 해주는 역할이라고 보시면 됩니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long>,JpaUserRepositoryCustom{
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


일반 Jpa 레포지토리에 커스텀인터페이스 상속을 해줍니다. 이렇게 커스텀인터페이스를 상속해주면 커스텀인터페이스 구현체에서 구현한 querydsl 메소드를 일반 jpa레포지토리에서 사용이 가능해집니다. 즉, 일반 jpa레포지터리의 기능을 그대로 이용하면서 업그레이드된 querydsl쿼리를 같이 이용할 수 있게 되는 것입니다.



Controller 작성


대충짰습니다....controller에서 repository를 다이렉트로 주입하고... 따라하시지 마시고 테스트용도이니 만약 사용하실때는 꼭 controller->service->repository 구조로 이용해주세요 !!!!


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
package com.spring.jpa.controller;
 
import java.util.Arrays;
import java.util.List;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.repository.JpaUserRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    @Autowired
    JpaUserRepository jpaUserRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) throws JsonProcessingException {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        
        XmlMapper xmlMapper=new XmlMapper();
        String xmlString=xmlMapper.writeValueAsString(user);
        
        log.info(xmlString);
        
        List<UserEntity> userList=jpaUserRepository.findAllLike("여성게");
        log.info("userEntity list={}",userList.get(0).getUserName());
        
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs



확실히.. 편하긴 하지만 여기까지 오기에 복잡한 설정은 아니지만 귀찮은 설정들이 많았내요... 조금 적응되면 이 기술도 자유롭게 사용가능하겠죠? mybatis를 선호하였던 저였지만, jpa+querydsl을 잠시나마 사용해보니 확실히 기술을 익히고 사용한다면 mybatis보다 훨씬 유연한 데이터베이스 관련 기술 일것 같습니다.



혹시 여기까지 오시다가 cannot change web module version 2.5 to ....어쩌고 맞나?.. 이런 에러가 뜬다면 자기가 사용중인 web module 버전과 maven에 명시되어 있는 버전과 비교를 하시고 꼭 maven jdk 버전이랑도 같이 맞춰주시면 해당 에러는 사라집니다... 설정을 다 다시 마추고 maven update하면 에러는 싹 사라집니다. 

posted by 여성게
:
Web/JPA 2018. 10. 13. 21:27

spring boot 환경이 아니라, 일반 springframework 환경에서 진행하였습니다.

모든 소스는 mac OS 환경에서 진행되었습니다.


이번에는 ORM을 이용한 데이터베이스 조작입니다. ORM이란 자바객체와 관계형데이터베이스의 테이블 간의 매핑을 아주 쉽게 해주는 기술입니다. 주로 mybatis만 이용하는 저였기에 이 기술을 접하고 아주 신세계...하지만 둘다 장단점이 있는 기술인 것같습니다. 아직까지는 우리나라는 mybatis를 더욱 많이 쓰고 있는 것 같기도 한데....맞나요..


project 우클릭 -> properties -> project facets -> JPA 선택.

resources 밑에 영속성 관련 설정을 할 수 있는 persistence.xml이 생성되어 있을 것입니다. 요 설정 파일에 영속성 엔티티 클래스 및 기타 설정파일을 따로 설정하실수 있습니다.


pom.xml



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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${org.springframework-version}</version>
        </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
 
cs



application.properties, servlet-context.xml


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
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <mvc:annotation-driven />
 
    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />
 
    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    
    <context:component-scan base-package="com.spring.jpa.controller" />
    
    
    
</beans:beans>
 
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <context:component-scan base-package="com.spring.jpa">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
    </bean>
    
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@127.0.0.1:59162:xe"/>
        <property name="username" value="springjpa"/>
        <property name="password" value="springjpa"/>
    </bean>
    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    
    <tx:annotation-driven/>        
    
    <jpa:repositories base-package="com.spring.jpa.repository"/><!-- transactionManager라는 이름이 아니면 명시적으로 등록해주어야한다. -->
</beans>
 
cs


만약 트랜잭션매니저 빈의 id을 transactionManager라고 지정하지 않았다면, jpa:repositories 태그에 명시적으로 트랜잭션 id값 설정을 넣어줘야한다.


UserEntity.java , NationEntity.java


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
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
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
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


기타 어노테이션들은 구글링으로...



persistence.xml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="springjpa">
        <class>com.spring.jpa.model.UserEntity</class>
        <class>com.spring.jpa.model.NationEntity</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
            
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="false"/>
            <property name="hibernate.id.new_generator_mappings" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>
 
cs



JpaUserRepository.java , JpaNationRepository.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long> {
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
import com.spring.jpa.model.NationEntity;
 
public interface JpaNationRepository extends JpaRepository<NationEntity, Long> {
    NationEntity findByNationName(String nationName);
}
 
cs


JpaReposotory 인터페이스는 기본적으로 CRUDRepository,PagingAndSortingRepository,QueryByExampleExecutor,Repository 인터페이스들을 상속한 인터페이스입니다. 즉, JpaRepository 인터페이스 하나면 상속받으면 대부분 기본기능들은 모두 사용가능합니다. 하지만 조금은 복잡한 쿼리를 일반 쿼리로 작성하고 싶다면, 위의 예제처럼 native한 쿼리를 작성하여 메소드에 매핑해 줄 수 있습니다.


UserController.java


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
package com.spring.jpa.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs









기타 Service클래스 등은 첨부하지 않았습니다.


굉장히 간단한 jpa 예제입니다. 추후 QueryDsl과 jpa를 이용한 예제를 업로드할 예정입니다.

부족한점은 많이 코멘트 남겨주시면 감사하겠습니다...

posted by 여성게
: