반응형

연관 관계 편의 메소드를 사용하기 전에

이 부분을 이해하시려면 우선 영속성 컨텍스트(Persistence Context)에 대한 이해가 필요합니다. 관련 글은 다른 블로그에 자세히 설명된 게 있어서 링크로 첨부하니 확인해보세요!

 

JPA / 영속성 컨텍스트 / 1차 캐시 / 쓰기 지연

"T아카데미 / JPA 프로그래밍 기본기 다지기 - 김영한 " 강좌를 듣고 정리한 글입니다. 영속성 컨텍스트를 영속성 컨텍스로 관리하면 어떤 이점을 갖을까 1. 1차 캐시 / 엔티티 동일성 보장 2. 쓰기

www.ecsimsw.com

 

연관 관계 편의 메소드

JPA에서 양방향 연관 관계를 설정했을 때 연관 관계의 주인의 반대쪽 필드는 mappedby를 설정하여 읽기 전용으로 쓰게 됩니다. 그런데 일대다 중 다 쪽에만 연관 관계를 설정하면 객체 지향적이지 않을뿐더러 DB에서 데이터를 조회하기 전까지는 일대다 중 다 쪽에만 값이 세팅되어 일 쪽 Entity에서는 연관 관계를 모르기 때문에 일 쪽의 값을 출력해보면 데이터가 나오지 않습니다.

 

예제 코드

아래 코드는 teamA를 생성하고 memberA에 teamA를 넣어 연관 관계를 설정하는 코드입니다. 이 코드는 문제가 없어보일 수 있지만 실행하면 마지막 줄은 출력되지 않습니다.

 

memberA에는 teamA가 들어갔지만 반대인 teamA는 memberA와의 연관 관계가 설정되지 않았기 때문에 teamA에는 member와 관련된 아무런 값도 설정되지 않았기 때문입니다.

 

따라서 teamA가 memberA를 알려면 em.flush() 후 em.clear()를 통해 영속성 컨텍스트를 깨끗하게 비워 DB에서 값을 찾을 수 있도록 해주면 되는데 이보다 객체 지향적이면서, 1차 캐시의 영속성 컨텍스트와 실제 DB 데이터와의 데이터 차이 문제를 해결하기 위해 일대다 양쪽에 데이터를 넣어주는 게 필수적입니다. 아래 예제를 통해 확인해봅시다!

 

* 반드시 영속성 컨텍스트에 대한 내용을 숙지하고 보세요!

 

Team teamA = new Team();
team.setName("teamA");
em.persist(teamA);

Member memberA = new Member();
memberA.setName("memberA");

// memberA에 연관 관계 설정
memberA.setTeam(teamA);
em.persist(memberA);

System.out.println("memberA.getTeam : " + memberA.getTeam());
System.out.println("teamA.getMembers() : " + teamA.getMembers(0));

 

teamA에도 연관 관계 설정

teamA.getMember().add(memberA); 한 줄만 추가해주면 됩니다.

 

Team teamA = new Team();
team.setName("teamA");
em.persist(teamA);

Member memberA = new Member();
memberA.setName("memberA");

// memberA에 연관 관계 설정
memberA.setTeam(teamA);
em.persist(memberA);

// teamA에 연관 관계 설정
teamA.getMembers().add(memberA);

System.out.println("memberA.getTeam : " + memberA.getTeam());
System.out.println("teamA.getMembers() : " + teamA.getMembers(0));

 

Member Entity에 연관 관계 편의 메소드 생성

위 방법으로 해결할 수 있지만 같은 코드 반복을 방지하기 위해 Member Entity에서 연관 관계 편의 메소드를 생성합니다. 이렇게 연관 관계 편의 메소드를 사용하면 코드 중복을 방지하고 양방향 연관 관계 매핑에서 한쪽에만 연관 관계가 설정되는 것을 방지할 수 있습니다.

 

@Entity
@Getter
public Member {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private Team team;
    
    public changeTeam(Team team) {
        
        // Member에 이미 Team이 설정되어 있을 경우
        if(this.team != null) {
         
            // team에서 해당 Entity를 제거
            this.team.getMembers().remove(this);
        }
        
        // 해당 member Entity에 파라미터로 들어온 team 연관 관계 설정
        this.team = team;
        
        // 파라미터로 들어온 team Entity에 member 연관 관계 설정
        team.getMembers().add(this);
    }
}
반응형
반응형

일반적으로 REST API를 만들게 되면 @RestController를 이용하여 JSON 형식으로 값을 반환하게 됩니다. 일반적으로 데이터를 반환할 때 상태값을 넘겨주는데 이 때 사용하는 게 Spring Framework에서 제공하는 ResponseEntity입니다.

 

ResponseEntity는 HttpEntity를 상속받습니다.

ResponseEntity의 HttpEntity 상속

위 이미지처럼 ResponseEntity는 HTTP 요청에 의한 HttpStatus, HttpHeader와 HttpBody를 포함하는 HttpEntity 클래스를 상속받습니다.

 

반응형
반응형

우선, JPA의 관계 매핑을 알아보기 전에 객체의 연관 관계와 테이블의 연관 관계의 차이를 알아봅시다.

 

테이블 : 외래키로 조인하여 연관된 테이블을 찾습니다.

객체 : 객체를 참조하여 연관된 객체를 찾습니다.

 

테이블은 조인을 통해 양방향 연관 관계 설정이 가능하지만 객체에는 양방향 연관 관계 설정이 없으므로 다른 두 객체에 각각 단방향 연관 관계를 설정해야 합니다.

 

게시판 예제를 통해 확인해봅시다.

 

Article

@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    
    private String content;

    ...
}

Member

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String memberId;

    private String memberPass;
    
    ...
}

 

게시판 객체와 회원 객체가 있을 때 게시판 객체에서는 회원 정보를 얻고 반대로 회원 객체에서는 회원이 작성한 모든 글을 가지고 오고 싶습니다.

 

@ManyToOne과 OneToMany

두 객체를 연결하기 위해 Article 객체 필드에 Member 객체를 만들었고, Member 객체 필드에는 List 타입의 Articles라는 필드를 만든 후 각각 @ManyToOne과 @OneToMany를 붙여줬습니다.

 

@ManyToOne : 다대일, 한 명의 회원이 여러 게시글을 작성할 수 있으므로 게시글(Article) 기준으로 @ManyToOne을 선언합니다.

@OneToMany : 일대다, 회원 한 명이 게시글을 여러 개 작성할 수 있으므로 회원(Member) 기준으로 @OneToMany를 선언합니다.

 

@Entity
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    
    private String content;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    ...
}


@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String memberId;

    private String memberPass;
    
    @OneToMany(mappedby = "member")
    private List<Article> articles = new ArrayList<Article>();
    
    ...
}

 

@ManyToOne(fetch = FetchType.LAZY) : fetch로 로딩 전략을 설정합니다. FetchType.LAZY는 지연 로딩을 의미합니다.

@OneToMany(mappedby = "member") : 일대다 관계를 연결할 상대 객체의 @JoinColumn이 선언된 필드값을 적습니다.

 

mappedby란?

JPA로 연관 관계를 설정할 때 가장 혼동하기 쉬운 부분인데 mappedby는 연관 관계 객체의 반대 객체에 선언합니다.

 

연관 관계 주인 : 외래키가 있는 객체

* 예시에서는 외래키가 Article에 있으므로 Article이 연관 관계의 주인

 

즉, 위 예시에서는 Article과 Member 중에서 Article이 외래키 member_id 라는 컬럼에 해당하는 member를 갖고 있으므로 Article이 연관 관계의 주인입니다.

 

따라서 연관 관계 주인 Article과 연관 관계로 설정된 Member 객체의 articles 필드 위 @OneToMany 안에 (mappedby를 넣고 연관 관계 주인 객체의 연결 필드명 "member"를 넣어주시면 됩니다.

 

처음 해보시면 되게 혼란스러우실 수 있는데 우리나라 JPA 최고 권위자이신 김영한 선생님의 강의 영상 첨부하니 확인해보세요!

 

반응형

'Java Web > JPA' 카테고리의 다른 글

[JPA] persistence.xml 작성 예시  (0) 2022.03.02
[JPA] 연관 관계 편의 메소드  (0) 2022.02.28
[JPA] Auditing 정리  (0) 2022.02.14
[JPA] findAll 정렬  (0) 2021.07.23
[JPA] JPA란?  (0) 2021.05.13
반응형

JPA Auditing

Java 표준 ORM 기술 JPA를 사용할 때 데이터 생성일자, 수정일자 등 어떤 테이블이든 공통으로 갖고 있는 컬럼을 관리하기 위해 제공하는 기능입니다. 이 기능을 사용하면 테이블과 매핑하여 데이터 입력 시 자동으로 시간을 관리할 수 있습니다.

 

BaseTimeEntity 생성

@Getter
@MappedSuperClass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
    
    @CreatedDate
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    private LocalDateTime updatedDate;

}

 

@MappedSuperClass : JPA에서 관리하는 Entity 클래스가 해당 추상 클래스를 상속하면 createdDate와 updateDate를 컬럼으로 인식하게 해 줍니다.

 

@EntityListeners(AuditingEntityListener.class) : 해당 클래스가 Auditing 기능을 포함하게 됩니다.

 

@CreatedDate : 데이터가 입력될 때 자동으로 시간을 저장해줍니다.

 

@LastModifiedDate : 데이터의 값이 변경될 때 자동으로 시간을 저장해줍니다.

 

Article

@Getter
@NoArgsConstructor
@Entity
public class Member extends BaseTimeEntity {

    ... 내용 생략

}

 

위처럼 @Enitity로 지정된 클래스가 BaseTimeEntity를 상속받으면 사용 가능합니다. 

 

BoardApplication

@EnableJpaAudithing
@SpringBootApplication
public class BoardApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
}

 

마지막으로 해당 프로젝트의 Application 클래스에 @EnableJpaAuditing 어노테이션을 추가함으로써 어플리케이션 내에서 Auditing 기능을 활성화할 수 있습니다.

 

UserRepositoryTest

@SpringBootTest
public class MemberRepositoryTest {

    @Autowired
    private UserRepository UserRepository;

    @Test
    public void insert() {
        String userId = "test01";

        // 실행 시간보다 전으로 임의 시간 지정
        LocalDateTime currentTime = LocalDateTime.of(2022, 2, 14, 0, 0, 0);

        User user = User.builder()
                .userId(userId)
                .userPass("test01")
                .userName("test01")
                .userEmail("test01@gmail.com")
                .build();

        userRepository.save(user);

        // 11행에서 선언한 currentTime보다 테이블에서 새로 입력된 값의 시간이 뒤인지 확인
        assertThat(userRepository.findAll().get(0).getCreatedDate()).isAfter(currentTime);

    }
}

 

Test 클래스를 하나 만들어서 board 테이블에 실행한 시간을 잘 저장했는지 테스트해봅니다.

 

MySQL Workbench에서 확인한 결과 작성일자와 수정일자이 현재 시간으로 저장된 것을 볼 수 있습니다.

반응형

'Java Web > JPA' 카테고리의 다른 글

[JPA] persistence.xml 작성 예시  (0) 2022.03.02
[JPA] 연관 관계 편의 메소드  (0) 2022.02.28
[JPA] 양방향 관계 매핑 @OneToMany, @ManyToOne 그리고 mappedby  (1) 2022.02.16
[JPA] findAll 정렬  (0) 2021.07.23
[JPA] JPA란?  (0) 2021.05.13

+ Recent posts