[JAVA]/JPA

5. JPA Advanced [Part.02]

Code_Otaku 2022. 7. 28. 17:46

 

안녕! 안녕!

이번 시간에도 JPA를 한층 Deep Dark.. 하게 다뤄보도록 하자.

저번 시간에는 CRUD 중에서 CREATE / READ 부분만 얘기 해보았다.

그렇다면 UPDATE / DELETE 역시 마무리를 지어야 하지 않겠는가?

 

그런 의미에서 스코프를 조금 더 앞 단으로 당겨줬다.

여태까지는 DB와 Repo 사이를 왔다갔다 하면서 뒷 단만 쪼물딱, 쪼물딱 해봤지.

이번에는 Repo 바로 앞 단인 Service 파트를 만들어 볼 것이다.

참고로 DB에 가까울수록 뒷 단, Client에 가까울수록 앞 단이라고 한다.

 

오케이! 다 좋다 이거야..

CRUD를 마저 완성해주기 위해 앞 단으로 스코프를 이동해 준다고 했지?

그런데 왜 그런 수고로움을 자처하는 것이지?

그냥 App.java 파일 하나에 다 몰아넣으면 안되나?

 

예아, 안될 거 뭐 있노?

CRUD를 전부 실행파일 안에 때려박아도 문제 없이 동작은 할 것이다.

하지만 결과적으로 App.java 파일에는 다음 코드만 남겨놓을 것이다.

 

public class Week02Application {

    public static void main(String[] args) {
        SpringApplication.run(Week02Application.class, args);
    }
}

그러니까.. 지금까지 시험 삼아 만들어 놓은 CRUD는 전부 소거하고!

오직 스프링 서버를 실행하는 기능만 남겨놓는 것이지.

 

아니.. 그러니까, 도대체 왜???

가끔은 그렇다면 그런 줄로 알고 넘어가라.

앱을 개발할 때는 무조건 Client / Server / DB 이렇게 세 계층으로 분할해서 만들어야 한다.

일종의 암묵적인 룰인 것이다.

특히, 할 일이 많은 내부 서버는 다시 Controller / Service / Repository로 나누는 것이 일반적이다.

CRUD 기능을 구현해주되.. 패키지 별로 역활까지 확실하게 분담하는 것이지.

 

그리고 이번 시간에는 App.java 파일에서 UPDATE / DELETE 기능을 소거한 다음에..

Service 패키지 안에 그 녀석들을 알박기 하려는 것이다.

 

감이 좋으신 분들은 이쯤에서 여렷품이 눈치챈 것이 있을 것이다.

UPDATE / DELETE 기능을 수행할 서비스 패키지를 따로 만들어 준다고 했으니..

어딘가에서 CREATE / READ 기능을 수행할 패키지 역시 존재하겠네?

가령 Controller라든지.. 

 

예아, 그 말이 맞다.

이러한 기법을 MVC (Model 2) 패턴이라고 한다.

하지만 우리는 웹 개발 종합반이 아니라..

뒷 단의 JPA만 똑! 떼어놓고 공부하고 있기 때문에 너무 딥 하게 들어가지는 않을 것이다.

 

다만 전체적인 맥락은 파악하길 바란다.

이 JPA 함수가 이 패키지에서는 어떤 기능을 수행하는가?

거기에 초첨을 맞춘다면 학습의 취지에서 벗어나는 일은 없을 것이다.

 

굉장히 헷갈리는 내용이기 때문에 서론을 좀 길게 잡았다.

이제 본격적으로 Service 패키지를 살펴보도록 하자.

미안하고, 고맙다! 훠훠훠..

 


1. Service (UPDATE / DELETE)

 

@Service // 스프링에게 이 클래스는 서비스임을 명시
public class CourseService {

    // final: 서비스에게 꼭 필요한 녀석임을 명시
    private final CourseRepository courseRepository;

    // 생성자를 통해, Service 클래스를 만들 때 꼭 Repository를 넣어주도록
    // 스프링에게 알려줌
    public CourseService(CourseRepository courseRepository) {
        this.courseRepository = courseRepository;
    }

    @Transactional // SQL 쿼리가 일어나야 함을 스프링에게 알려줌
    public Long update(Long id, Course course) {
        Course course1 = courseRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
        );
        course1.update(course);
        return course1.getId();
    }
}

 

MVC 패턴에 어울리도록 Service 패키지를 따로 만들어 주었지.

그리고 UPDATE / DELETE 기능만 수행하는 *.java 클래스를 브랜치로 생성한 것이다.

요 놈을 느긋하게 Line by Line으로 분석 해보자.

코드리뷰 시간은 아니지만 JPA를 기능적으로 이해하는 데 도움이 될 것이다.

 

@Service // 스프링에게 이 클래스는 서비스임을 명시

public class CourseService {...}

골뱅이 (@) 거시기는 스프링부트에 없어서는 안되는 가장 강력한 기능이다.

Annotation 이라고 하지.

자세한 건 구글링을 하던가..

필자가 따로 정리해놓은 Spring Boot 입문과정을 참고하길 바란다.

어쨌든간에 해당 클래스를 서비스 탭으로 운영할 것임을 스프링 서버에게 알려주는 셈이다.

 

무슨 서비스???

Controller에서 데이터의 수정 / 삭제를 요청한다면

그 기능만 전문적으로 수행하는 서비스 탭인 것이지.

 

// final: 서비스에게 꼭 필요한 녀석임을 명시
private final CourseRepository courseRepository;

final은 주석에 달아놓은 그대로이다.

결과적으로는 서비스 탭 내에서 SQL을 수행하기 위함이지..

 

public interface CourseRepository extends JpaRepository<Course, Long> {...}

이 녀석을 가져다가 쓰려는 것이다.

아직까지는 어려운 거 없지?

 

// 생성자를 통해, Service 클래스를 만들 때 꼭 Repository를 넣어주도록 스프링에게 알려줌
public CourseService(CourseRepository courseRepository) {
    this.courseRepository = courseRepository;
}

자바에서 흔히 볼 수 있는 생성자이다.

이번 클래스의 허리를 담당하는 녀석이라고 볼 수 있지.

필자는 도통 이 녀석을 만들어주는 이유를 몰라서 통채로 주석처리 해봤다.

그랬더니..

 

이렇게 에러가 뜨더라.

repo를 가져다가 쓰기 위해서는 별도의 생성자가 필요함을 알 수 있다.

 

@Transactional // SQL 쿼리가 일어나야 함을 스프링에게 알려줌
public Long update(Long id, Course course) {
    Course course1 = courseRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
    );
    course1.update(course);
    return course1.getId();
}

여기서 눈여겨 보아야 할 부분은 어노테이션이다.

@Transactional

자바에서 JPA 함수를 쓰는 이유는 SQL 쿼리를 DB에 전달해주기 위함이다.

그 기능을 수행해주기 위해 별도로 어노테이션을 선언해준 것이다.

 

findById(id)

사실상 SQL문을 대체해주는 JPA 함수라고 할 수 있지.

그 이하는 예외처리를 지정해준 것이다.

id by id로 조회를 하였을 때, 실제 객체가 존재하지 않는다면 저런 예외 문구가 뜰 것이다.

 

SELECT * FROM COURSE WHERE ID LIKE ?;

역시나 JPA 함수에 상응하는 SQL문을 같이 올려줘야 이해가 빠를 것이다.

 

public void update(Course course) {
    this.title = course.title;
    this.tutor = course.tutor;
}

Long타입 id를 Course.java 클래스에서 참조하고 있기 때문에 별도로 Getter를 만들어 준 것이다.

자바 기본 문법을 알고 있다면 어려울 거 없지?

뭐.. 스프링부트를 쓰는 분들이라면 이미 자바 문법 정도는 어느 정도 숙달이 됐다고 본다.

그래도 필요하신 분들을 위해 조만간 자바 기초 문법 역시 업로드 하도록 하겠다.

 

자! 그럼 실제로 UPDATE 기능이 제대로 동작하고 있는지 테스트 정도는 해봐야 겠지?

 

courseRepository.save(new Course("프론트엔드의 꽃, 리액트", "코드헨타이"));
            
            List<Course> courseList = courseRepository.findAll();
            for (int i = 0; i < courseList.size(); i++) {
                Course course = courseList.get(i);
                System.out.println(course.getId());
                System.out.println(course.getTitle());
                System.out.println(course.getTutor());
            }

            Course new_course = new Course("웹개발의 봄, Spring", "코드헨타이");
            courseService.update(1L, new_course);
            courseList = courseRepository.findAll();
            for (int i = 0; i < courseList.size(); i++) {
                Course course = courseList.get(i);
                System.out.println(course.getId());
                System.out.println(course.getTitle());
                System.out.println(course.getTutor());
            }
        };

소스코드 자체는 그리 주의깊게 볼 필요가 없다.

전체적인 맥락만 파악하도록 하자.

CRUD 중에서 UPDATE에 해당하는 녀석이다.

해당 변수 중에 title의 값만 '프론트 엔드의 꽃, 리액트' 에서 '웹 개발의 봄, 스프링' 으로 변경해주려는 것이다.

 

Hibernate: call next value for hibernate_sequence
Hibernate: insert into course (created_at, modified_at, title, tutor, id) values (?, ?, ?, ?, ?)
데이터 인쇄
Hibernate: select course0_.id as id1_0_, course0_.created_at as created_2_0_, course0_.modified_at as modified3_0_, course0_.title as title4_0_, course0_.tutor as tutor5_0_ from course course0_
1
프론트엔드의 꽃, 리액트
코드헨타이
Hibernate: select course0_.id as id1_0_0_, course0_.created_at as created_2_0_0_, course0_.modified_at as modified3_0_0_, course0_.title as title4_0_0_, course0_.tutor as tutor5_0_0_ from course course0_ where course0_.id=?
Hibernate: update course set created_at=?, modified_at=?, title=?, tutor=? where id=?
Hibernate: select course0_.id as id1_0_, course0_.created_at as created_2_0_, course0_.modified_at as modified3_0_, course0_.title as title4_0_, course0_.tutor as tutor5_0_ from course course0_
1
웹개발의 봄, Spring
코드헨타이

콘솔창을 그대로 긁어왔을 뿐이다.

SQL 문법을 알고있는 분들이라면 어떠한 구조로 작동하고 있는지 한 눈에 들어올 것이다.

특히 update course 부분을 눈 여겨서 보면 될 것이다.

 

아! 삭제를 까먹고 안올릴 뻔했다.

 

courseRepository.deleteAll();

이거면 끝이다.

모든 데이터를 삭제해주겠다는 명령어인데..

여러분이 직접 그 절차를 실행해주는 일은 없을 것이다.

뭘 믿고 여러분에게 그러한 권한을 맡기겠는가?

그냥 이런 기능이 있다는 것 정도만 알아두고 넘어가라.

 


여기까지 해서 JPA를 수박 겉 핥기 식으로 살펴보았다.

추후 전공서적을 구해서 그걸 가지고 공부할 때, 적잖은 도움이 될 것이라 본다.

한 가지 유의해야 할 점은..

모든 SQL문을 JPA로 대체할 수는 없다는 것이다.

기본적으로 CRUD에 해당하는 기능은 완벽하게 수행하지만..

서로 다른 테이블을 하나처럼 이어 붙여주는 JOIN은 무엇으로 구분할 것인가?

누누히 강조하는 거지만 SQL 역시 시간을 할애해서 공부해주길 바란다.

 

'[JAVA] > JPA' 카테고리의 다른 글

4. JPA Advanced [Part.01]  (0) 2022.07.27
3. Why JPA?  (0) 2022.07.25
2. JPA Basic  (0) 2022.07.23
1. JPA General  (0) 2022.07.21
0. Intro  (0) 2022.07.21