'web/JPA'에 해당되는 글 12건

web/JPA

스프링 부트에서 사용하는 JPA 기능 정리

스프링 프레임워크에서 제공하는 JPA는 별도의 구현 클래스 없이 인터페이스만을 사용할 수 있도록 제공한다. 제공되는 인터페이스 JpaRepository는 실행시점에 자동으로 인터페이스 내용을 연결하는 엔티티에 맞게 자동으로 구현해준다. 만약 스프링 JPA 인터페이스에서 제공하지 않는 기능을 사용하고 싶을 때는 메서드명을 특정한 규칙대로 만들어서 사용하면 인터페이스가 알아서 그 이름에 맞는 JPQL을 만들어서 실행해준다.


스프링 JPA 인터페이스는 Mysql같은 RDBMS 뿐만 아니라 Mongodb, Redis와 같은 NoSQL에도 동일한 인터페이스를 사용해서 기능을 사용할 수 있도록 제공해준다. 공통으로 사용할 수 있기에 아주 편리하다.


우선 스프링 부트에 JPA를 사용하기 위해서 Gradle에 라이브러리를 넣자.

1
compile ('org.springframework.boot:spring-boot-starter-data-jpa')
cs

 

기본적인 구조

JpaRepository를 상속받아 구현하고자 하는 인터페이스를 만들고 제네릭에 구현하려는 엔티티와 엔티티의 식별자 타입을 지정하여 인터페이스를 선언한다.

1
2
3
public interface StudentRepository extends JpaRepository<Student, Long> {
}
 
cs


주요 메서드 몇개만 정리해보자.

save() : 저장하거나 업데이트한다.

delete(entity) : em.remove() 호출하여 엔티티를 제거한다.

findOne(ID) : em.find() 호출하여 엔티티를 찾는다.

findAll() : 엔티티 모두를 조회한다. 


JpaRepository 인터페이스로 부족한 기능을 구현한 PagingAndSortingRepository CrudRepository 사용해서 보완할 있다.


JPA에서 메서드 사용하는 방법

JPA에서 엔티티에 맞는 메서드를 사용하는 방법은 크게 3가지이다.

- 메서드 이름으로 쿼리 생성

- 메서드 이름으로 JPA NamedQuery 호출

- @Query 어노테이션을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의


1. 메서드 이름으로 쿼리 생성

메서드 이름으로 쿼리를 생성할 있는데 정식 Document 이용하면 자세히 나와있다

https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#jpa.query-methods


2. 메서드 이름으로 JPA NamedQuery 호출

JPA NamedQuery 쿼리에 이름을 부여해서 사용하는 방법은 다음과 같이 엔티티에 선언해주면 된다.

1
2
3
4
5
@NamedQuery(
  name="student.findByName",
  query="select s from student s where s.name = :name")
public class Student {

}
cs


위와 같이 선언하고 실제 사용할 때는 entityManager에 아래와 같이 createQuery를 사용해서 쿼리를 호출하면 된다.

1
2
3
4
5
6
7
@PersistenceContext
private EntityManager entityManager;
 
public List<Student> findByUser(String name) {
  List<Student> students = entityManager.createQuery("student.findByName", Student.class).setParameter("name""wedul").getResultList();
}
 
cs


위와 같이 EntityManager를 사용할 수 있지만 스프링 JPA를 사용하여 간단하게 메소드 이름만으로 호출이 가능하다. 이렇게 호출하면 레포지토리에서 Student.쿼리메소드 형태로 쿼리를 찾는다. 만약 실행할 쿼리가 없으면 메서드 이름으로 쿼리를 자동으로 바꿔 동작한다.

1
2
3
4
5
public interface StudentRepository extends JpaRepository<Student, Long> {
  
  List<Student> findByUserName(@Param("name") String name);
  
}
cs


3. @Query 어노테이션을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의

2번에서는 @Entity 클래스에서 정의한 쿼리를 레포지토리에서 메소드 형태로 접근하여 사용하였다.  이번에는 레포지토리에서 직접적으로 쿼리를 만들어서 조회하는 방식을 확인해보자.


@Query("select s from Student s where s.name = ?1")

Student findByName(String name);


인터페이스에 정의하는 메소드에 @Query 어노테이션을 붙혀서 정의하고 사용하면 된다. 바인딩 값은 1부터 시작한다. 스프링 데이터 JPA에서는 ?1 ?2 같은 위치기반 파라미터와 :name 같은 이름 기반 방식을 모두 사용가능하다.


페이징과 정렬

스프링 데이터 JPA에서 쿼리 메서드에 페이징과 정렬 기능을 사용할 있다. 파라미터로 Pageable 인터페이스를 사용할 경우에는 Page 또는 List 반환 받을 있다.

1
2
3
4
public interface StudentRepository extends JpaRepository<Student, Long> {
 
  Page<Student> findByNameStartingWith(String name, Pageable pageable);
}
cs


실제 사용할 때는 Pageable 인터페이스이기 때문에 구현체인 PageRequest 객체를 사용해서 사용한다.

// 파라미터 순서대로 페이지 번호, 사이즈, 정렬 기준 등으로 사용한다.

1
PageRequest pageRequest = new PageRequest(0, 10, new Sort(Sort.Direction.DESC, "name"));
cs


반환되는 값인 Page에서 제공하는 다양한 메소드를 사용해서 편하게 페이징과 소트 기능을 사용할 있다. 

컨트롤러에서 사용자 요청에게 전달되는 페이징 정보를 받기 위해서는 다음과 같이 Pageable 인터페이스를 받으면 되고 받을 속성값은 page, size, sort 사용해서 받는다. (/student?page=0&size=20&sort=name,desc&sort=address.city)


1
2
3
4
5
6
7
@GetMapping("/student")
public String list(Pageable pageable, Model model) {
  Page<Student> page = studentRepository.findByNameStartingWith("dbsafer", pageable);
 
  return "dfdfdf";
}
 
cs


참고 싸이트

https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html


출처. : 자바 ORM 표준 JPA 프로그래밍


web/JPA

@MappedSuperclass를 이용한 부모 매핑정보 사용하기

기존에는 부모의 엔티티에 접근해서 정보를 가져와서 사용했다. 하지만 그러면 너무 비용이 크기 때문에 이를 위해서 부모클래스의 매핑정보만 가지고와서 사용할 수 있는 방법이 있다.

@MappedSuperClass 어노테이션을 사용하면 부모 엔티티 접근 없이 부모 클래스의 매핑정보를 사용할 수 있다.


예를들어 선생님과 학생을 담당하는 클래스에서 id name 공통 속성이다. 이런 고유 속성을 부여해주는 부모클래스를 만들고 이를 상속받은 Student Teacher 클래스를 만들어보자.


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
/**
 * springboottest
 *
 * @author wedul
 * @since 03/11/2018
 **/
@MappedSuperclass
public abstract class CommonEntity {
 
  @Id @GeneratedValue
  private Long id;
 
  private String name;
}
 
 
/**
 * springboottest
 *
 * @author wedul
 * @since 03/11/2018
 **/
@Entity
public class Student extends CommonEntity {
 
  // ID 상속
  // NAME 상속
  private String locker;
 
}
 
 
/**
 * springboottest
 *
 * @author wedul
 * @since 03/11/2018
 **/
@Entity
public class Teacher extends CommonEntity {
 
  // ID 상속
  // NAME 상속
  private String address;
 
}
 
cs


쉽게말해서 저번에 공부 했을때는 무조건 자식테이블이 부모테이블의 테이블과 조인을 해야하기 때문에 번거로웠으나 단순하게 자신의 매핑정보와 부모의 매핑정보를 사용해서 새로운 동작을 있게 해준다. 


부모의 내용을 별도로 재정의해서 사용하고 싶은 경우에는 @AttributeOverride 사용하여 재정의 한다. 여러개를 한번에 정의하기 위해서는 @AttributeOverrides 사용한다.


1
2
3
4
5
6
7
8
9
10
11
@Entity
@AttributeOverride(name = "id", column = @Column(name = "TEACHER_ID"))
public class Teacher extends CommonEntity {
 
 
@Entity
@AttributeOverrides({
        @AttributeOverride(name="id", column = @Column(name = "STUDENT_ID")),
        @AttributeOverride(name="name", column = @Column(name = "STUDENT_NAME"))
})
public class Student extends CommonEntity {
cs


@MappedSuperClass 테이블과 매핑 되지 않고 자식 클래스에 엔티티의 매핑정보를 상속하기 위해 사용 되기 때문에 정식 엔티티가 아니다. 그래서 find() JPQL등의 메소드를 사용할 없다.


이를 이용하면 createTime, updateTime 등에 대한 공통 영역 관리를 수월하게 있다.


  1. Favicon of https://wedul.site BlogIcon 위들 wedul 편집답글

    @Entity는 @Entity 또는 @MappedSuperclass로 지정한 클래스만 상속받을 수 있다.

web/JPA

JPA 상속관계 매핑 전략

객체 지향으로 데이터베이스 중심 매핑을 변경하기 위해서 가장 애매한게 상속이다. 이런 상속관계속에서 테이블로 구현할 3가지 방법을 선택할 있다.


1) 각각의 테이블로 변환 : 각각을 모두 테이블로 만들고 조회할 조인을 사용.

2) 통합 테이블로 변환 : 테이블을 하나만 사용해서 통합 

3) 서브타입 테이블로 변환 : 서브 타입마다 하나의 테이블을 만드는 방식.



순서대로 하나씩 정리해보자.




각각의 테이블로 변환 (조인전략)

- 부모와 각각의 자식 엔티티를 모두 각자의 테이블로 만들고 부모의 기본키와 자식의 외래키를 사용하여 조인하여 사용한다.

-  자식 엔티티의 타입을 구별하기 위한 DTYPE 컬럼을 구분컬럼으로 추가하여 사용한다. (없어도 무관)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
  
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
  
  private String name;
  private int price;
  
}
 
cs

-> 부모 추상 클래스 Culture 선언된 애노테이션에 대한 설명은 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
@Inheritance(strategy = InheritanceType.JOINED) : 조인전략 사용을 의미
@DiscriminatorColumn(name = "DTYPE") : 부모 클래스에 구분 컬럼으로 써 자식 테이블을 구분할 때 사용할 키로 사용된다. 기본값은 DTYPE이며 바꿔서 사용가능 (해당 기능을 사용하지 않아도 무관하다.)
 
 
@Entity
@DiscriminatorValue("M")
public class Movie {
 
  private String artiest;
  private String genre;
 
}
cs


기본적으로 사용하는 자식클래스 형식이다. 기본적으로 부모 테이블의 ID 컬럼명을 승계받아 사용 하지만 만약 변경하고 싶은 경우 클래스 위에 @PrimaryKeyJoinColumn(name = "MOVIE_ID")처럼 정의해서 사용할 있다.


조인전략의 장점은 저장공간을 효율적으로 관리하거나 테이블의 정규성이 지켜진다는 점이 있지만 조회할 조인이 많아지고 조회 쿼리시 귀찮아지며 수정이 발생하면 부모와 자식 테이블 두번을 해주어야한다.

 


 통합 테이블로 변환 (단일 테이블 전략)

부모의 속성과 자식의 속성을 하나의 테이블로 사용하는 것이다. 그리고 구분 컬럼 DTYPE 추가하여 해당 테이블이 어떤 자식 엔티티를 기반으로 만들어진 테이블인지 구분한다. (DTYPE 어떤자식인지 구분하기 위해 사용한다. 만약 속성이 같은 자식인 경우 구분이 안되기 때문이다.) 해당 전략의 단점으로는 자식 엔티티에 추가된 필드는 필수가 아니기 때문에 null 입력 되어도 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
 
cs

-> Inheritance(strategy = InheritanceType.SINGLE_TABLE) 조인 타입을 단일 테이블 전략으로 지정한다. 


1
2
3
4
5
6
7
@Entity
@DiscriminatorValue("M")
public class Movie extends Culture {
 
  private String genre;
 
}
cs


 하나의 테이블로 지정되기 때문에 조인전략과 다르게 자식클래스를 구분할 있는 DTYPE 무조건 추가되어야 한다. 방식은 하나로 묶여있어 쿼리가 단순하다는 장점이 있지만 자식 엔티티의 필드의 null 무조건 허용해줘야 한다는 것과 자식마다 성격이 모두 달라도 테이블에 필요없는 필드까지 추가되어 있어야 하는 문제점이 있다.



- 서브타입 테이블로 변환 (구현 클래스마다 테이블 전략)

 자식 엔티티 마다 별도의 모든 테이블을 만들어 주는 방식이다. 부모와 자식을 키로 묶어서 사용하는 조인 방식과 다르게 자식 마다 부모의 속성과 자식의 속성을 모두 포함한 엔티티를 개별적으로 모두 만드는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Culture {
 
  @Id @GeneratedValue
  @Column(name = "CULTURE_ID")
  private Long id;
 
  private String name;
  private int price;
 
}
cs


- 단일테이블 전략과 다르게 모든 자식 테이블이 별도로 만들어지기 때문에 필요없는 필드가 추가되지 않아서 not null조건을 넣어줄 있다. 하지만 모두 만들어야하고 자식테이블끼리 합칠 성능의 문제가 발생할 있다. 가장 비효율적인 방법으로 일반적으로 조인이나 단일 테이블 전략중에서 사용한다.


- 참고 JAVA ORM 표준 JPA 프로그래밍


web/JPA

JPA 관계 유형별 엔티티 설정 방법

JPA에서 관계 유형별로 엔티티를 설정하는 방법을 정리해보자.



1. 다대일 (단방향)

-> 다쪽에 @ManyToOne 으로 설정


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
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
 
  @Id
  @Column(name = "CLASSES_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
}
 
cs


2. 다대일 양방향

-> 다쪽에 @ManyToOne, 일쪽에 @OneToMany 지정해서 사용

-> 다쪽인 Student에서 외래키를 가지고 있으므로 직접 조작할 있고 반대쪽은 조회만 가능

-> 이전시간에 정리한거 처럼 setClasses(), addStudent()등에 메서드에 빈곳이 없도록 해야한다.

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
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
 
  @Id
  @Column(name = "CLASSES_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @OneToMany(mappedBy = "classes")
  private List<Student> students;
 
}
cs


3. 일대다 (단방향)

-> 일대다에서 외래키가 다쪽에 존재

-> 다대일 단방향에서 일쪽에 @JoinColumn(name = "CLASSES_ID") 추가된 형태

-> 일대다보다 있으면 다대일을 사용하자.

1
2
3
4
@JoinColumn(name = "CLASSES_ID")
@OneToMany(mappedBy = "classes")
private List<Student> students;
 
cs


4. 일대다 (양방향)

-> 일대다 매핑은 존재하지 않고 다대일 양방향으로 사용해야한다.



5. 일대일

-> 양쪽이 서로 하나의 관계만 가지는 .

-> 양쪽 테이블 모두 외래키를 가질 있다.

-> @OneToOne으로 매핑하고 주인이 엔티티에 @JoinColumn 사용하고, 나머지 엔티티에는  mappedBy 옵션을 사용한다.

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
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker locker;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Locker {
 
  @Id
  @Column(name = "LOCKER_ID")
  private String id;
 
  @Column(name = "location")
  private String location;
 
  @OneToOne(mappedBy = "locker")
  private Student student;
}
cs


6. 다대다

-> 데이터베이스에서 사용하지 않는 다대다를 사용하지 말고 중간에 중점을 두어 다대일로 구별하여 처리한다.

-> 예를 들어 학생과 책의 다대다 관계를 지칭한다 했을 중간에서 학생과 책의 관계를 담당하는 엔티티 StudentBook 있다고 가정해보자.

-> Student에는 관계 테이블에서 사용될 키가 보함된   @OneToMany(mappedBy = "student") 설정한다.

-> 관계 엔티티 에는 키를 관리하는 별도의 객체를 만들어서 @IdClass(StudentBookId.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
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  // 다대일
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
  // 일대일
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker locker;
 
  // 다대다(중간에 관계테이블 있는경우)
  @OneToMany(mappedBy = "student")
  private List<Book> books;
 
  public void setClasses(Classes classes) {
    // 먼저 지워준다.
    classes.getStudents().remove(this);
 
    // 그리고 반을 바꾸고 학생추가
    this.classes = classes;
    classes.getStudents().add(this);
  }
 
}
 
 
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
 
  @Id
  @Column(name = "PRODUCT_ID")
  private String id;
 
  private String name;
 
}
 
@Entity
@IdClass(StudentBookId.class)
public class StudentBook {
 
  @Id
  @ManyToOne
  @JoinColumn(name = "STUDENT_ID")
  private Student student;
 
  @Id
  @ManyToOne
  @JoinColumn(name = "BOOK_ID")
  private Book book;
 
}
 
@EqualsAndHashCode
public class StudentBookId implements Serializable {
 
  private String student;
  private String book;
 
}
 
cs


web/JPA

연관관계 매핑 (다대일 - 양방향)

바로 앞에서 다대일 관계에서 단반향으로써 학생이 반을 접근하는 방식으로 진행했으나 이번에는 반에서 학생들을 접근하는 방식을 사용해보자.


그렇게 되면 학생 -> 반에서 반 -> 학생이 추가되어 결국 반 <-> 학생 이런 양방향 연관관계가 형성된다.

 

하나의 반에는 여러 학생이 포함되어 있다. 그렇기 때문에 반 클래스에 List<Student> 객체를 추가한다.


1

2

  @OneToMany(mappedBy = "classes")

  private List<Student> students;

cs

 


@OneToMany(mappedBy = "classes")

- 일대다 매핑을 정보를 추가하고 학생쪽에서 사용되는 반 필드명을 mappedBy에 값으로 추가해준다.


조회

반에 포함되어 있는 학생들을 조회한다.

1

2

3

4

5

6

7

8

9

10

@Override

@Transactional

public void selectClasses() {

  Classes classes1_1 = entityManager.find(Classes.class"1-1");

  List<Student> students = classes1_1.getStudents();

 

  for (Student student : students) {

    print(student);

  }

}

Colored by Color Scripter

cs

 


연관관계 주인 지정


테이블은 외래키 하나로 테이블의 연관관계를 관리 있다. 예를 들면 이름이 외래키라고 했을 학생 테이블에서 외래키 이름을 추가할 수도 있고, 테이블에서 반 이름을 관리할 있다. 하지만 엔티티에서는 외래키를 관리(추가, 수정, 삭제) 있는 것은 개의 엔티티의 연관관계의 주인이 되는 엔티티만이 가능하다. 나머지 다른 엔티티는 조회만 가능하다.

 

예를 들어 저번 시간에 공부 했었던 Student 엔티티 클래스는 Classes 외래키의 주인으로써 외래키를 추가, 수정, 삭제 있다. Classes 엔티티 클래스는 외래키의 주인이 아니므로 조회만 가능하다.

 

@ManyToOne 설정이 있는 곳이 무조건 주인이다. 그리고 양방향 설정된 엔티티에서 조회가 가능하도록 하기 위해서 다른 엔티티에 @OneToMany(mappedby ="classes") 지정해 주면 주인 설정이 끝난다.

 

그럼 진짜 주인이 아닌 엔티티 Classes에서는 외래키 관리가 안되는지 확인해보자.

 

1

2

3

4

5

6

7

@Override

@Transactional

public void saveClasses() {

  Student wedul = entityManager.find(Student.class"1-1-01");

  Classes classes2_1 = new Classes("2-2""2학년2", Arrays.asList(wedul));

  entityManager.persist(classes2_1);

}

Colored by Color Scripter

cs

-> 처음 생각대로라면 id : "2-2", name : "2학년2" 반이 classes테이블에 추가되고 학생 테이블에 wedul 학생의 반이 2-2 같지만 그렇지 않다. 왜냐하면 Classes 엔티티는 주인이 아니기 때문이다. 그래서 테이블에 값만 추가된다.



주의사항

 만약 단방향 그러니까 Student 엔티티에만 @ManyToOne 해줄 경우 Classes 엔티티를 통해 반에 등록된 학생을 조회 하려 값을 받게 된다. 왜냐하면 연관관계가 맺어지지 않았기 때문이다. 그래서 무조건 이럴 경우 양방향 연관관계를 맺어 주는것이 좋다.(@OneToMany)

 

아래 코드를 보면 wedul학생에 classes2_1 반을 추가해줬지만 classes2_1에서 학생을 조회하면 wedul 학생이 없다.

1

2

3

4

5

6

Student wedul = entityManager.find(Student.class, "1-1-01");

Classes classes2_1 = new Classes("2-2", "2학년2", Collections.emptyList());

wedul.setClasses(classes2_1);

    

// 반영 되어 있지 않아서 wedul 출력되지 않음

classes2_1.getStudents();

Colored by Color Scripter


그래서 이런 문제로 버그가 발생할 있기 때문에 좋은 방법으로 setClasses() 메소드를 다음과 같이 변경해주면 좋다.

1

2

3

4

public void setClasses(Classes classes) {

  this.classes = classes;

  classes.getStudents().add(this);

}

Colored by Color Scripter

그렇지만 이렇게만 해주고 나면 또다른 버그가 발생할 있다. 다음과 같은 상황을 가정해보자.

1

2

3

4

5

6

7

8

9

Student wedul = entityManager.find(Student.class, "1-1-01");

Classes classes2_1 = new Classes("2-2", "2학년2", Collections.emptyList());

wedul.setClasses(classes2_1);

 

classes2_1.getStudents();

 

// 학생의 반을 다른 반으로 변경할 경우 기존의 반에 들어있는 getStudents List안에서 학생을 지워줘야한다.

Classes classes3_1 = new Classes("3-1", "3학년1", Collections.emptyList());

wedul.setClasses(classes3_1);

Colored by Color Scripter

상황에서는 위에 변경해주었던 방식대로 진행하면 기존에 반이었던 classes2_1에도 wedul이 있고 classes3_1에도 wedul 있는 문제가 발생한다. 그래서 다음과 같이 바꿔주면 해결된다. 

1

2

3

4

5

6

7

8

  public void setClasses(Classes classes) {

    // 먼저 지워준다.

    classes.getStudents().remove(this);

    

    // 그리고 반을 바꾸고 학생추가

    this.classes = classes;

    classes.getStudents().add(this);

  }

Colored by Color Scripter


단방향 매핑만으로도 테이블과 객체의 연관관계 매핑이 되었지만 양방향 매핑을 통해서 더욱 편리하게 객체의 탐색이 가능하게 있다. 하지만 위에 보았듯이 양방향 매핑에서는 주의해서 관리 해줘야 포인트가 많다.


 [ 1 ]  [ 2 ]  [ 3 ] 

푸터바

알림

이 블로그는 구글에서 제공한 크롬에 최적화 되어있고, 네이버에서 제공한 나눔글꼴이 적용되어 있습니다.

카운터

  • Today : 36
  • Yesterday : 651
  • Total : 55,511