[스프링 부트 어드민 페이지] 3. JPA CRUD

2021. 5. 16. 09:56개발공부/[패스트캠퍼스] 스프링 부트 어드민 페이지 만들기


Github 코드

 

JPA를 통한 CRUD

JPARepository 클래스를 상속하면 쿼리를 대체하는 내장 메서드를 사용할 수 있습니다. 대표적으로 findById(), save(), update()...등이 있는데 반환되는 데이터를 객체로 받을 때 Optional<T>으로 받습니다. Optional은 null일수도 있는 객체를 받아줄 때 하용하며 제너릭 타입을 명시합니다. not null일 경우 boolean 타입을 반환해주는 isPresent() 메서드가 자주 사용됩니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Test
    @Transactional
    public void read() {
        Optional<User> user = userRepository.findById(4L);
 
        /** ifPresent()
         * If a value is present, invoke the specified consumer with the value, otherwise do nothing.
         * Params: consumer – block to be executed if a value is present
         * Throws: NullPointerException – if value is present and consumer is null
         * */
 
        user.ifPresent(selectUser->{
            selectUser.getOrderDetails().stream().forEach(detail->{
                Item item = detail.getItem();
                System.out.println(detail.getItem());
            });
        });
    }
cs

 

Update

Update 쿼리를 실행시킬 때 save() 메서드를 사용하는데 이 메서드는 식별키인 id가 데이터 행으로 있는지 확인한 다음 update을 해줍니다. 특히 JPA는 변경하려는 value 뿐만 아니라 전체 value들을 다시 할당해줍니다. JPA는 편리한 대신 불필요한 처리도 된다는 점을 알 수 있습니다. 

1
2
3
Hibernate: select user0_.id as id1_0_0_, user0_.account as account2_0_0_, user0_.created_at as created_3_0_0_, user0_.created_by as created_4_0_0_, user0_.email as email5_0_0_, user0_.phone_number as phone_nu6_0_0_, user0_.updated_at as updated_7_0_0_, user0_.updated_by as updated_8_0_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_0_, user0_.account as account2_0_0_, user0_.created_at as created_3_0_0_, user0_.created_by as created_4_0_0_, user0_.email as email5_0_0_, user0_.phone_number as phone_nu6_0_0_, user0_.updated_at as updated_7_0_0_, user0_.updated_by as updated_8_0_0_ from user user0_ where user0_.id=?
Hibernate: update user set account=?, created_at=?, created_by=?, email=?, phone_number=?, updated_at=?, updated_by=where id=?
cs

 

Tip : @Test @Transactional을 붙여주면 실제 DB 변경이 이루어지지 않습니다. (rollback)


JPA 연관관계 설정

관계 Annotation
일대일 @OneToOne
일대다 @OneToMany
다대일 @ManyToOne
다대다 @ManyToMany

 

user - order_detail - item

user과 item을 일대다로 연결시킬 수 없기 때문에 order_detail이란 테이블을 추가해줍니다. 그래서 DB설계는 MySQL Database Reverse Engineer과 Forward Engineer로 생성해줍니다. Engineer을 사용해 ERD(Entity Relationship Diagram) 먼저 설계하고 자동으로 테이블이 생성됩니다.

DB ERD(테이블 연관관계)


JPA 연관관계 설정 배우기

차례는 아래와 같습니다. DB 설계 → Entity 설정 → Repository 생성 → Repository Test 실행. 

아래 코드는 테이블별로 연관관계를 스프링에 작성해준 것입니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//OrderDetail
//N:1
    @ManyToOne
    private User user; //hibernate에서 알아서 user_id를 찾아감
 
//User
// 1:N
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<OrderDetail> orderDetails;
 
/** 
FetchType. LAZY – Fetch it when you need it. 
The FetchType. LAZY tells Hibernate to only fetch the related entities 
from the database when you use the relationship. 
This is a good idea in general because there's no reason to select 
entities you don't need for your uses case.
*/
cs

 

HIbernate Fetch Explanation - link

Hibernate will NOT load this User’s Profile unless you explicitly ask it to. So this means that if you try to call user.getUserProfile(), you’ll get a NULL. Lazy는 명령을 통해서만 relationships 데이터를 가져오기 때문에 불필요한 DB 접근을 막을 수 있다는 장점이 있고 성능개선에 도움이 될 수 있습니다.

Lazy는 getter 방식으로 호출하지 않는 이상 id를 기준으로 item 테이블에서만 해당되는 데이터만 뽑습니다. 하지만 Eager 방식은 연관관계에 있는 테이블을 모두 Join해 id를 검색합니다. 이는 즉시로딩으로 불리우며 Lazy 방식은 지연로딩으로 불립니다. 따라서 즉시로딩은 연관관계가 없는 DB 스키마일 때(@ManyToOne) 추천하는 방식입니다.

@OneToMany는 Collection으로 OrderDetail 객체를 리턴해줍니다. 

1
2
3
4
5
        user.ifPresent(selectUser2->{
            selectUser2.getOrderDetails().stream().forEach(detail2->{
                Item item2 = detail2.getItem();
            });
        });
cs

 

Lombok

Lombok @Data를 쓸 때는 자동으로 toString 메서드가 생성되는데 연관관계 클래스 어노테이션을 ToString(exclude) 시켜줘야 합니다. 그렇지 않으면 상호 호출이 반복되어 stackoverflow 에러가 뜹니다.

1
2
@ToString(exclude = {"user","item"})
 
cs

출처 : 패스트캠퍼스 Java 웹 개발 마스터 올인원 패키지