Spring Framework (4) - todo 실습(Create, Delete 구현) Day 51

jaehoon ahn·2025년 12월 1일

Spring boot

목록 보기
4/20
post-thumbnail

DBCP(DataBase Connection Pool)

  • Connection Pool: 연결 정보가 들어가있는 풀(대기상태 → 유휴상태, 언제든지 사용가능한 상태)
  • 요청이 오면 해당 커넥션을 대여해주고 다 쓰면 대기함
  • ⇒ 성능적으로 뛰어남
  • ⇒ HikariCP를 사용함

실습

@Autowired

// TodoServiceImpl.java
@Service // 비즈니스 로직(데이터 가공, 트랜잭션 처리 등)의 역할을 명시 + Bean 등록
public class TodoServiceImpl implements TodoService {
}
.
.

@Autowired // 타입이 같거나 상속관계를 DI 함
private TodoService service;

Mybatis

TodoMapper.java

package edu.kh.todo.model.mapper;

import org.apache.ibatis.annotations.Mapper;

// Mybatis를 사용하면 Mapper가 DAO를 대신해서 사용됌

/**
 * @Mapper - Mybatis에서 제공하는 어노테이션 - Mybatis에서 SQL과 Java 메서드를 연결해주는
 *         인터페이스(Mapper)의 구현체를 스프링의 Bean으로 등록할 수 있게 해주는 어노테이션
 * 
 *         이 어노테이션을 붙이면 Spring이 Mapper 인터페이스를 인식하고, 자동으로 구현체를 생성해줌 -> 이 구현체가
 *         Bean으로 등록됨!
 * 
 *         - ⭐⭐⭐ 해당 어노테이션이 작성된 인터페이스는 namespace에 해당 인터페이스가 작성된 xxx-mapper.xml
 *         파일과 연결되어 SQL 호출/수행/결과 반환이 가능해짐 -> 현재는 todo-mapper.xml에 namespace에 작성을
 *         해서 연결이 되어 있음
 * 
 * 
 */

// 전체 흐름은 현재 DAO가 있다는 가정하에 다음과 같다
// client -> service -> DAO -> Mapper -> xml 
// 돌아오는 것은 반대로 돌아오면 된다.
@Mapper
public interface TodoMapper {

	String testTitle();

}

todo-mapper.xml


.
.
	<!-- 태그로 SQL 구문 지정하고 작성 -->
	<!-- 여기서부터는 Mybatis -->
	<!-- id는 todoMapper에 있는 메서드 명과 똑같이 해줘야 한다. -->
	<select id="testTitle">
		SELECT TODO_TITLE
		FROM TB_TODO
		WHERE TODO_NO = 1
	</select>
	<!-- <select> 태그 속성 - id 속성 : 태그를 식별하는 값 (연결된 Mapper의 메서드명과 동일하게 작성하면 된다) -->
.
.
<!-- resultType=""은 반환받아오는 타입을 지정해주는 것 -> 반환 타입은 모두 소문자로 적기 -->
<!-- parameterType : 전달받은 값(전달 인자)의 타입을 명시하는 
속성 -> DTO, Map인 경우 필드명/key 
		를 입력해서 값/value을 하나씩 꺼내 사용할 수 있음 -->

⇒ 즉, TodoMapper.java에 있는 testTitle() 메서드를 id에 작성해주면 해당 메서드가 실행이 된다.

<!-- parameterType : 전달받은 값(전달 인자)의 타입을 명시하는 
속성 -> DTO, Map인 경우 필드명/key 
		를 입력해서 값/value을 하나씩 꺼내 사용할 수 있음 -->
		
		<!--
  	mapper.xml 에서 java data(파라미터)를 SQL에 삽입하는 방법!
  	
  	1) #{변수명 | 필드명} : SQL에 삽입 시 양쪽에 '' 붙여서 삽입
 
   ex) MEMBER_EMAIL = #{memberEmail}
   						-> 'member01@naver.com'
   				
   =============================================================
  
   2) ${변수명 | 필드명} : SQL에 삽입 시 양쪽에 '' 붙이지 않고 
   삽입				
  
   ${} 언제 사용할까?
   1) 삽입할 값이 숫자인 경우
   2) SQL문 자체가 변해야 하는 경우
  
   ex) SELECT MEMBER_NAME, ${key} FROM "MEMBER"
   -> ${key}가 ENROLL_DATE 인 경우
   -> SELECT MEMBER_NAME, ENROLL_DATE FROM "MEMBER"
  
   -> ${key}가 MEMBER_ADDRESS 인 경우
   -> SELECT MEMBER_NAME, MEMBER_ADDRESS FROM "MEMBER"
   				
 
   -->

@Transactional

TodoServiceImpl.java

package edu.kh.todo.controller;

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

import edu.kh.todo.model.dao.TodoDAO;
import edu.kh.todo.model.service.TodoService;

/**
 * @Transactional - 트랜잭션 처리를 수행하라고 지시하는 어노테이션 -> 커밋, 롤백이 필요한 상황이 있을 때 하라고 지시하는
 *                어노테이션 - 정상 코드 수행시 commit이 되고, 그 이외에는 rollback이 됨 - 기본값 :
 *                Service 내부 코드 수행 중에 RuntimeException이 발생하면 rollback
 */

@Transactional(rollbackFor = Exception.class) // 커밋, 롤백을 자동으로 진행
/**
 * rollbackFor = Exception.class => 어떠한 예외가 발생했을 때, 롤백을 해줄지 지정해주는 속성인데
 * Exception.class는 모든 예외 발생시 rollback 하겠다는 의미
 */
@Service // 비즈니스 로직(데이터 가공, 트랜잭션 처리 등)의 역할을 명시 + Bean 등록
public class TodoServiceImpl implements TodoService {
	@Autowired // TodoDAO와 같은 타입/상속관계 Bean 의존성 주입(DI)
	private TodoDAO dao;

	@Override
	public String testTitle() {
		return dao.testTitle();
	}
}
  • Service에서는 트랜잭션(커밋/롤백)이 일어나는데, 해당 트랜잭션을 어떠한 예외가 없으면 알아서 일어나게끔 하는 어노테이션
  • Exception.class는 모든 예외를 잡겠다는 의미
    • 만약 예외가 발생하면 rollback, 아니라면 commit

전체 흐름

  • Controller -> Service -> Mapper
    • 해당 데이터 흐름 반대로 흘러옴

RedirectAttributes

@GetMapping("detail") // /todo/detail로 Get 방식 요청 매핑
	public String todoDetail(@RequestParam("todoNo") int todoNo, Model model, RedirectAttributes ra) {
		Todo todo = service.todoDetail(todoNo);

		String path = null;

		if (todo != null) {
			// 조회 결과가 있을 경우 detail.html로 forward
			path = "todo/detail";
			model.addAttribute("todo", todo);
		} else {
			// 조회 결과가 없을 경우 main 페이지로 redirect
			path = "redirect:/";
			ra.addFlashAttribute("message", "해당 할 일이 존재하지 않습니다.");
		}

		return path;
	}
  • RedirectAttributes : 리다이렉트 시 값을 1회성으로 전달하는 객체
  • RedirectAttributes.addFlashAttribute("key", value) 형식으로 세팅
    -> request scope -> session scope로 잠시 변환
  • 응답 전 : request scope
    • redirect : session scope로 이동 -> 사용
    • 응답이 끝난 뒤 : request scope로 복귀함

service에서 Connection을 안만들어도 되는 이유

@Override
	public int addTodo(String todoTitle, String todoContent) {
		Todo todo = new Todo();
		todo.setTodoTitle(todoTitle);
		todo.setTodoContent(todoContent);
		// Todo.builder().todoTitle(todoTitle).todoContent(todoContent).build();

		int result = mapper.addTodo(todo);

	

		// 마이바티스에서 SQL에 전달할 수 있는 파라미터 개수는 오직 1개
		// -> TodoMapper에 생성될 추상메서드의 매개변수도 1개여야 한다는 것

		// 근데 2개 전달인자 가능
		// int result = mapper.addTodo(todoTitle, todoContent);
		return result;
	}
  • Connection을 안만들어도 되는 이유
  • DBCP를 사용하게 되어서, Connection pool에 있는 커넥션을 5개를 만들어서 대기하게끔 만듬
  • 이 관리를 Spring에서 해줌

data-* 속성

  • data-* 속성
    • 이후에는 개발자가 마음대로 작성해도 됌
      • 데이터에 대한 확장성을 고려하여 설계된 속성
      • js에서 요소.dataset 을 이용해서 해당 값을 얻어갈 수 있음
<button id="deleteBtn" th:data-todo-no="${todo.todoNo}">삭제</button>

⇒ 따라서, todo-no -> todoNo로 변환이 됌

const deleteBtn = document.querySelector("#deleteBtn");

deleteBtn.addEventListener("click", (e) => {
  if (confirm("정말 삭제하시겠습니까?")) {
    // 삭제 요청
    location.href = `/todo/delete?todoNo=${e.target.dataset.todoNo}`;
    // location.href는 Get 방식
  }
});

⇒ 위 코드처럼 dataset.key값으로 접근할 수 있다.

0개의 댓글