본문 바로가기
Back-end/Spring

[Spring] chapter11 애플리케이션 만들기(비즈니스 로직 처리)

by na1-4an 2023. 8. 7.

아래 글은 스프링 프레임 워크 첫걸음 책을 기반하여 작성한 글입니다.

11-1 비즈니스 로직의 처리 내용 확인과 작성

(1) 작성할 내용 확인

No 레이어 컴포턴트 이름 비고
1 애플리케이션 레이어 View - 화면 표시
2 애플리케이션 레이어 Controller Quizcontroller 제어 역할 담당
3 애플리케이션 레이어 Form QuizForm 화면의 게임 폼을 표현
4 도메인 레이어 Service QuizService 인터페이스로 생성
5 도메인 레이어 ServiceImpl QuizServiceImpl Service를 구현
6 도메인 레이어 도메인 객체 Quiz 엔티티 역할
7 도메인 레이어 Repository QuizRepository 인터페이스로 생성
8 인프라스트럭처 레이어 RepositoryImpl - O/R Mapper로 자동 생성
9 인프라스트럭처 레이어 O/R Mapper - 스프링 데이터 JDBC를 사용

위의 표는 chapter09에서 작성한 이 프로젝트에서 필요한 컴포넌트 목록이다.

이번 장에서는 4-5 컴포넌트를 만들어보겠다.

참고로 10장에서는 6-9 컴포넌트를 만들었다.

위의 이미지에서 점선에 해당하는 부분!!

또한 아래의 표는 우리가 만드는 프로그램의 기능인데, 10장에서 우리는 1-4 기능을 모두 만들었다.

5번 기능을 이번 장에서 만들어 보겠다.

(어렵지 않다!  이미 한 가지 퀴즈만 취득하는 함수를 구현했기 때문에 여기서 랜덤으로 가져오는 것만 추가하면 됨!!)

No 기능 설명
1 등록 기능 퀴즈를 등록한다.
2 갱신 기능 등록된 퀴즈를 갱신한다.
3 삭제 기능 등록된 퀴즈를 삭제한다.
4 목록 표시 기능 등록된 퀴즈의 목록을 표시한다.
5 게임 기능 등록한 퀴즈를 무작위로 표시하고 답한다.

 

(2) 비즈니스 로직 처리 만들기

01 Service 생성

아래와 같이 service 패키지를 만들어 그 안에 QuizService를 인터페이스로 만들어 아래처럼 내용을 채운다.

package com.example.quiz.service;

import com.example.quiz.entity.Quiz;

import java.util.Optional;

/** 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 인터페이스에서는 추상 메서드를 작성한다.

 

02 ServiceImpl 생성

앞에서 작성한 인터페이스를 구현할 ServiceImpl을 생성하겠다. 같은 패키지 안에 생성하겠다.

package com.example.quiz.service;

import com.example.quiz.entity.Quiz;
import com.example.quiz.repository.QuizRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@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);
    }

}

@Service 어노테이션을 클래스에 부여해서 인스턴스 생성 대상으로 지정한다.

@ Autowired로 QuizRepository를 주입한다.

 

03 RepositoryImpl에 추가

CrudRepository에 없는 메서드는 해당 메서드에

@Query 어노테이션을 부여해 어노테이션의 인수에 SQL을 추가한는 것으로 정의할 수 있다.

그래서 QuizRepository 인터페이스에 아래와 같이 추가한다.

/** Quiz 테이블: Repository*/
public interface QuizRepository extends CrudRepository<Quiz, Integer>{
    @Query("SELECT id FROM quiz ORDER BY RANDOM() limit 1")
    Integer getRandomId();
}

11-2 트랜잭션 관리 알아보기

(1) 트랜잭션이란?

: 복수의 처리를 하나의 그룹으로 모은 것! 랜잭션의 결과는 '성공' or '실패'만 가질 수 있다!

처리 중 실패했을 경우 트랜잭션은 실행 전의 상태로 돌아간다 ☞ "롤백(rollback)"

처리를 모두 성공하면 처리가 확정된다 "커밋(Commit)"

※ 부분적인 성공이나 부분적인 실패라는 것은 없다!

 

(2) 트랜잭션 경계란?

트랜잭션에서는 시작되고 끝나는 위치를 반드시 지정해야하고, 시작되고 끝날 때까지의 범위를 ''트랜잭션 경계"라고 한다!

결론부터 얘기하자면, 트랙잭션 경계는 Service에서 설정한다.

 

(3) 트랜잭션 관리 방법

트랜잭션 관리는 스프링 프레임워크에서 제공하는 @Transactional 어노테이션을 사용한다.

클래스나 메서드에 @Transactional을 부여하면 트랜잭션이 관리되어 트랜잭션의 시작, 커밋, 롤백이 자동 실행된다.

  • 클래스에 부여: 그 클래스의 모든 메서드에 트랜잭션 제어를 설정할 수 있다.
  • 메서드에 부여: 메서드가 호출되는 타이밍에 트랜잭션이 시작되어, 대상 메서드가 정상 종료하면 '커밋', 예외로 종료하면 '롤백'된다.

이 책에서는 클래스에 @Transactional 어노테이션을 부여하는 것을 추천한다.

 

(4) Servicemol 추가

QuizServiceImpl 클래스에 @Transactional을 부여하겠다.

@Service
@Transactional
public class QuizServiceImpl implements QuizService{

11-3 비즈니스 로직 처리하기

(1) quiz 테이블의 초기화

pgAdmin 4를 실행해 Tables에서 quiz를 선택하고 메뉴바에서 Tools에서 Query Tool을 선택한다.

Query Editor 화면에 아래처럼 SQL을 입력한다.

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

 

(2) QuizApplication 수정

아래와 같이 코드를 수정해준다.

package com.example.quiz;

import com.example.quiz.entity.Quiz;
import com.example.quiz.repository.QuizRepository;
import com.example.quiz.service.QuizService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

@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("오답입니다!");
		}
	}
}

 

(3) 등록/참조 처리

setup(), showlist(), showOne()

 

(4) 갱신/삭제 처리

updateQuiz(), deleteQuiz()

 

(5) 퀴즈 처리

doQuiz()

 

(6) QuizApplication 수정

다음 진행을 위해 아래처럼 코드를 수정하겠다.

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