11장 애플리케이션 만들기 (비즈니스 로직 처리)

Peter·2023년 7월 31일
0
post-thumbnail

11-1-1 작성할 부분 확인

11-1-2 비즈니스 로직 처리 만들기

이번에 작성하는 것은 Service와 ServiceImpl이다. Service는 인터페이스이고, ServiceImpl은 Service의 구현 클래스다.

package com.example.quiz.service;

import java.util.Optional;

import com.example.quiz.entity.Quiz;

/** Quiz 서비스: Service */
public interface QuizService {

    /** 등록된 모든 퀴즈 정보를 가져옵니다 */
    Iterable<Quiz> selectAll();

    /** id를 키로 사용해 퀴즈 정보를 한 건 가져옵니다 */
    Optional<Quiz> selectOneById(Integer id);

    /** 퀴즈 정보를 랜덤으로 한 건 가져옵니다 */
    Optional<Quiz> selectOneRandomQuiz();

    /** 퀴즈의 정답/오답 여부를 판단합니다 */
    Boolean checkQuiz(Integer id, Boolean myAnswer);

    /** 퀴즈를 등록합니다 */
    void insertQuiz(Quiz quiz);

    /** 퀴즈를 변경합니다 */
    void updateQuiz(Quiz quiz);

    /** 퀴즈를 삭제합니다 */
    void deleteQuizById(Integer id);

}

QuizService 인터페이스에서는 추상 메서드를 작성한다.

앞에서 작성한 인터페이스를 구현할 ServiceImpl을 작성한다.

package com.example.quiz.service;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.quiz.entity.Quiz;
import com.example.quiz.repository.QuizRepository;

@Service
public class QuizServiceImpl implements QuizService {
    /** Repository: 인젝션 */
    @Autowired
    QuizRepository repository;

    @Override
    public Iterable<Quiz> selectAll() {
        return repository.findAll();
    }

    @Override
    public Optional<Quiz> selectOneById(Integer id) {
        return repository.findById(id);
    }

    @Override
    public Optional<Quiz> selectOneRandomQuiz() {
        // 랜덤으로 id 값을 가져오기
        Integer randId = repository.getRandomId();

        // 퀴즈가 없는 경우
        if (randId == null) {
            // 빈 Optional 인스턴스를 반환
            return Optional.empty();
        }
        return repository.findById(randId);
    }

    @Override
    public Boolean checkQuiz(Integer id, Boolean myAnswer) {
        // 퀴즈 정답/오답 판단용 변수
        Boolean check = false;

        // 대상 퀴즈를 가져오기
        Optional<Quiz> optQuiz = repository.findById(id);

        // 퀴즈를 가져왔는지 확인
        if (optQuiz.isPresent()) {
            Quiz quiz = optQuiz.get();
            // 퀴즈 정답 확인
            if(quiz.getAnswer().equals(myAnswer)) {
                check = true;
            }
        }
        return check;
    }

    @Override
    public void insertQuiz(Quiz quiz) {
        repository.save(quiz);
    }

    @Override
    public void updateQuiz(Quiz quiz) {
        repository.save(quiz);
    }

    @Override
    public void deleteQuizById(Integer id) {
        repository.deleteById(id);
    }
} 

RepositoryImpl에 추가

CrudRepository에 없는 메서드는 해당 메서드에 @Query 어노테이션을 부여해서 어노테이션의 인수에 SQL을 추가하는 것으로 정의할 수 있다.

QuizRepository 인터페이스에 getRandomId 메서드를 추가한다

@Query("SELECT id FROM quiz ORDER BY RANDOM() limit 1")
Integer getRandomId();

11-2-1 트랜잭션이란?

트랜잭션: 복수의 처리를 하나의 그룹으로 모은 것. 트랜잭션은 결과로 성공 아니면 실패만 가질 수 있다.
처리 중 실패했을 경우 트랜잭션은 실행 전의 상태로 돌아간다. 이것을 롤백(Rollback)이라고 한다.
처리가 모두 성공하면 처리가 확정된다. 이것을 커밋(Commit)이라고 한다. 부분적인 성공이나 부분적인 실패라는 것은 없다.

11-2-2 트랜잭션 경계란?

트랜잭션에서는 시작되고 끝나는 위치를 반드시 지정해야 하고, 시작되고 끝날 때까지의 범위를 '트랜잭션 경계'라고 한다. 결론부터 말하자면 트랜잭션 경계는 Service에서 설정한다.
MVC 모델에서 서비스 처리는 모델이다. Service는 모델의 일부로 비즈니스 로직 처리의 입구로 생각할 수 있다. 이런 이유로 트랜잭션 경계는 Service에 지정한다.

11-2-3 트랜잭션 관리 방법

트랜잭션 관리는 스프링 프레임워크에서 제공하는 @Transactional 어노테이션을 사용한다.
클래스나 메서드에 @Transactional 어노테이션을 부여하면 트랜잭션이 관리되어 트랜잭션의 시작, 커밋, 롤백이 자동으로 실행된다.
롤백의 발생 조건은 비검사(Unchecked) 예외(RuntimeException 혹은 그 서브 클래스)가 발생했을 때이다. 검사(Checked) 예외(Exception 혹은 그 서브클래스에서 RuntimeException 외)가 발생했을 경우 롤백이 안 되고 커밋이 된다.

클래스에 @Transactional 어노테이션 부여하기

클래스에 @Transactional 어노테이션을 부여하면 그 클래스의 모든 메서드에 트랜잭션 제어를 설정할 수 있다.

메서드에 @Transactional 어노테이션 부여하기
메서드에 @Transactional 어노테이션을 부여하면 메서드가 호출되는 타이밍(정확하게는 메서드 시작 전)에 트랜잭션이 시작되어 대상 메서드가 정상 종료한 경우에는 '커밋', 예외로 종료한 경우에는 '롤백'된다.

ServiceImpl 추가

QuizServiceImpl 클래스에 @Transactional 어노테이션을 부여하면 그 클래스의 모든 메서드에 트랜잭션 제어를 할 수 있다.

다음과 같이 수정한다.

@Service
@Transactional
public class QuizServiceImpl implements QuizService {

11-3-1 quiz 테이블의 초기화

DELETE FROM quiz;
SELECT setval('quiz_id_seq', 1, false);

이 두 명령어는 quiz 테이블의 모든 데이터를 삭제하고, 새로운 데이터가 추가될 때 부여될 기본 키 값의 시작점을 1로 재설정하는 역할을 한다.

11-3-2 QuizApplication 수정

다음과 같이 QuizApplication을 수정한다

package com.example.quiz;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.example.quiz.entity.Quiz;
import com.example.quiz.service.QuizService;

@SpringBootApplication
public class QuizApplication {
    /** 구동 메서드 */
    public static void main(String[] args) {
        SpringApplication.run(QuizApplication.class, args).getBean( QuizApplication.class).execute();
    }

    /** 주입(인젝션) */
    @Autowired
    QuizService service;

    /** 실행 메서드 */
    private void execute() {
        // 등록 처리
        //setup();
        // 전체 항목 취득
        //showList();
        // 1건 취득
        //showOne();
        // 변경 처리
        //updateQuiz();
        // 삭제 처리
        //deleteQuiz();
        // 퀴즈 실행
        //doQuiz();
    }

    /** === 퀴즈 5건을 등록합니다 === */
    private void setup() {
        System.out.println("--- 등록 처리 개시 ---");
        // 엔티티 생성
        Quiz quiz1 = new Quiz(null, "자바는 객체지향 언어입니다."
                , true, "등록 담당");
        Quiz quiz2 = new Quiz(null, "스프링 데이터는 데이터 액세스에 관련된 "
                + "기능을 제공합니다.", true, "등록 담당");
        Quiz quiz3 = new Quiz(null, "프로그램이 많이 등록되어 있는 서버를"
                + "라이브러리라고 합니다.", false, "등록 담당");
        Quiz quiz4 = new Quiz(null, "@Component는 인스턴스 생성 어노테이션"
                + "입니다.", true, "등록 담당");
        Quiz quiz5 = new Quiz(null, "스프링 MVC에서 구현하고 있는 디자인 패턴에서 "
                + "모든 요청을 하나의 컨트롤러에서 받는 것을"
                + "싱글 컨트롤러 패턴이라고 합니다.", false, "등록 담당");
        // 리스트에 엔티티를 저장
        List<Quiz> quizList = new ArrayList<>();
        // 첫 인수에 저장될 객체를, 두 번째 인수부터는 저장할 엔티티를 넘겨줌
        Collections.addAll(quizList, quiz1, quiz2, quiz3, quiz4, quiz5);
        // 등록 실행
        for (Quiz quiz : quizList) {
            // 등록 실행
            service.insertQuiz(quiz);
        }
        System.out.println("--- 등록 처리 완료 ---");
    }

    /** === 모든 데이터 취득 === */
    private void showList() {
        System.out.println("--- 모든 데이터 취득 개시 ---");
        // 리포지토리를 이용해 모든 데이터를 취득해서 결과를 반환
        Iterable<Quiz> quizzes= service.selectAll();
        for (Quiz quiz : quizzes) {
            System.out.println(quiz);
        }
        System.out.println("--- 모든 데이터 취득 완료 ---");
    }

    private void showOne() {
        System.out.println("--- 1건 취득 개시 ---");
        // 리포지토리를 사용해서 1건의 데이터를 취득해서 결과를 반환(반환값은 Optional)
        Optional<Quiz> quizOpt = service.selectOneById(1);
        // 반환값이 있는지 확인
        if (quizOpt.isPresent()) {
            System.out.println(quizOpt.get());
        } else {
            System.out.println("해당 데이터는 존재하지 않습니다.");
        }
        System.out.println("--- 1건 취득 완료 ---");
    }

    /** === 변경 처리 === */
    private void updateQuiz() {
        System.out.println("--- 변경 처리 개시 ---");
        // 변경할 엔티티를 생성
        Quiz quiz1 = new Quiz(1, "스프링은 프레임워크입니까? ", true, "변경 담당");
        // 변경 처리
        service.updateQuiz(quiz1);
        // 변경 결과 확인
        System.out.println("변경된 데이터는 " + quiz1 + "입니다.");
        System.out.println("--- 변경 처리 완료 ---");
    }

    /** === 삭제 처리 === */
    private void deleteQuiz() {
        System.out.println("--- 삭제 처리 개시 ---");
        // 삭제 실행
        service.deleteQuizById(2);
        System.out.println("--- 삭제 처리 완료 ---");
    }

    /** === 랜덤으로 퀴즈를 취득해서 퀴즈의 정답/오답을 평가 === */
    private void doQuiz() {
        System.out.println("--- 퀴즈 1건 취득 개시 ---");
        // 리포지토리를 이용해서 1건의 데이터를 받기(반환값은 Optional)
        Optional<Quiz> quizOpt = service.selectOneRandomQuiz();
        // 반환값이 있는지 확인
        if (quizOpt.isPresent()) {
            System.out.println(quizOpt.get());
        } else {
            System.out.println("해당 데이터는 존재하지 않습니다.");
        }
        System.out.println("--- 퀴즈 1건 취득 완료 ---");
        // 답 평가
        Boolean myAnswer = false;
        Integer id = quizOpt.get().getId();
        if (service.checkQuiz(id, myAnswer)) {
            System.out.println("정답입니다!!!");
        } else {
            System.out.println("오답입니다.");
        }
    }

}

11-3-3 등록/참조 처리

/** 실행 메서드 */
private void execute() {
    // 등록 처리
    setup();
    // 전체 항목 취득
    showList();
    // 1건 취득
    showOne();
    // 변경 처리
    //updateQuiz();
    // 삭제 처리
    //deleteQuiz();
    // 퀴즈 실행
    //doQuiz();
}

setup(), showList(), showOne()의 주석처리 제거

11-3-4 갱신/삭제 처리

/** 실행 메서드 */
private void execute() {
    // 등록 처리
    //setup();  <-- 주석 처리
    // 전체 항목 취득
    //showList();  <-- 주석 처리
    // 1건 취득
    //showOne();  <-- 주석 처리
    // 변경 처리
    updateQuiz();
    // 삭제 처리
    deleteQuiz();
    // 퀴즈 실행
    //doQuiz();
}

setup(), showList(), showOne()을 주석처리하고, updateQuiz()와 deleteQuiz() 메서드의 주석 처리를 제거한다.

11-3-5 퀴즈 처리

QuizApplication 클래스의 내용은 아래와 같다. doQuiz() 메서드 외에 모두 주석 처리한다.

/** 실행 메서드 */
private void execute() {
    // 등록 처리
    //setup();  <-- 주석 처리
    // 전체 항목 취득
    //showList();  <-- 주석 처리
    // 1건 취득
    //showOne();  <-- 주석 처리
    // 변경 처리
    //updateQuiz(); <-- 주석 처리
    // 삭제 처리
    //deleteQuiz(); <-- 주석 처리
    // 퀴즈 실행
    doQuiz();
}

QuizApplication 수정

모든 메서드의 동작을 확인했으므로 QuizApplication을 원래대로 돌려놓는다.

package com.example.quiz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class QuizApplication {
    /** 구동 메소드 */
    public static void main(String[] args) {
        SpringApplication.run(QuizApplication.class, args);
    }
}
profile
개발자 지망생. 일단 하고보자

1개의 댓글

comment-user-thumbnail
2023년 7월 31일

좋은 글 감사합니다.

답글 달기