[Spring Boot] MVC 패턴 작성법과 예시(MyBatis)

hameee·2023년 12월 15일
0

Spring Boot

목록 보기
8/20
post-thumbnail

📍 전체 흐름

이미지 출처

🚩 Controller, BO, Repository/Mapper 호출

  • Controller는 여러 개의 BO를 호출할 수 있다.
  • BO는 여러 개의 BO를 호출할 수 있다.
  • BO는 본인과 매핑된 Repository/Mapper만 호출할 수 있다.
  • Controller <- BO <- Mapper 방향으로 호출될 수는 있지만, 역방향은 불가하다.
  • BO1에서 BO2를 호출했다면, BO2에서 BO1을 호출할 수 없다.(상호 autowired 안됨, 한 방향으로만 가능)

🚩 MyBatis 주의할 점

  • 오버로딩을 지원하지 않는다.
  • insert, update, delete 시, 성공적으로 완료된 행의 수를 반환한다.

📍 파일 구조

src
└── main
    ├── java
    │   └── 베이스 패키지
    │       └── ...
    │           ├── bo
    │           │   └── 도매인명BO.java
    │           ├── domain
    │           │   └── 도매인명.java
    │           ├── mapper
    │           │   └── 도매인명Mapper.java
    │           └── Controller.java
    ├── resources
    │   └── mappers
    │       └── 도매인명Mapper.xml
    └── webapp
        └── WEB-INF
            └── jsp
                └── ...
                    ├── 페이지명1.jsp
                    ├── 페이지명2.jsp
                    └── ...

📍 Controller

1) 주요 어노테이션

  • 클래스
    • @Controller(JSP 경로 리턴->HTML)/@RestController(객체 리턴->JSON, String 리턴->HTML)
    • 메소드의 공통 요청 URL이 존재하는 경우: @RequestMapping("공통 요청 URL")
  • 필드
    • BO 클래스를 받는 필드: @Autowired
  • 메소드
    • GET 요청만 받는 메소드: @RequestMapping(path = "요청 URL", method = RequestMethod.GET)/@GetMapping("요청 URL")
    • POST 요청만 받는 메소드: @RequestMapping(path = "요청 URL", method = RequestMethod.POST)/@PostMapping
    • 모든 요청(GET/POST/UPDATE/DELETE)을 받는 메소드: @RequestMapping("요청 URL"):
  • 파라미터
    • 요청 파라미터 받기
      • 단일 파라미터를 바인딩하는 경우
        • 필수 파라미터: @RequestParam("요청 파라미터명")
        • 선택 파라미터: @RequestParam(value = "요청 파라미터명", required = false, defaultValue = "기본값")
      • 여러 파라미터를 하나의 객체로 바인딩하는 경우
        • @ModelAttribute
    • 요청 경로에서 파라미터 추출(@RequestMapping("/{변수명}"))
      • 경로의 변수명의 값을 받는 파라미터: @PathVariable("변수명")

2) 주의 사항

  • insert 시, auto_increase 속성을 가진 필드는 자동 생성되므로 삽입하지 않는다.
  • 매칭 관계: (요청 URL) ?⭐️key⭐️=1 <-> (Controller 내부 메소드의 파라미터) @RequestParam("⭐️key⭐️") int id123
  • Model은 org.springframework.ui.Model에서 import 한다.
  • Model의 값을 JSP에서 사용 시, ${obj.key} 혹은 ${key}로 가져온다.
  • redirect 시, return "redirect:재요청 보낼 주소"로 작성한다.
  • AJAX로 요청받는 메서드일 경우, 응답으로 View 경로가 아닌 성공 시 반환할 데이터를 AJAX로 다시 보내야 하므로 해당 메서드에 @ResponseBody 작성 후 String을 반환한다. (해당 포스팅 참고)

3) 권장 사항

📍 Service(= BO)

1) 주요 어노테이션

  • 인터페이스
    • @Service
  • 필드
    • Mapper 인터페이스를 받는 필드: @Autowired

2) 주의 사항

3) 권장 사항

  • 메소드명: get/add/update/delete + 도매인명/도매인명List + By필드명(조건)

📍 Repository(= DAO, Mapper)

1. 도매인명Mapper.java

1) 주요 어노테이션

  • 클래스
    • @Repository
  • 파라미터
    • 파라미터가 2개 이상인 경우(Map 형태로 제공): @Param("파라미터명")

2) 주의 사항

  • interface로 생성한다.
  • 매칭 관계: (Mapper.java 클래스 내부 메소드의 파라미터) @Param("⭐️param-name⭐️") int id123 <-> (Mapper.xml) #{⭐️param-name⭐️}

3) 권장 사항

  • 메소드명: select/insert/update/delete + 도매인명/도매인명List + By필드명(조건)

2. 도매인명Mapper.xml

1) 주요 속성

  • mapper 태그
    • namespace: .java 인터페이스
  • insert/select/update/delete 태그
    • 공통
      • id: 메소드 이름
      • parameterType: 자료형(파라미터 1개) / map(파라미터 2개 이상)
    • insert
      • useGeneratedKeys: 데이터베이스에서 생성된 키를 사용할지 여부(true/false), 기본값은 false
      • keyProperty: useGeneratedKeys에서 생성된 키를 할당할 프로퍼티 이름(ex. id)
    • select
      • resultType: 한 행의 자료형

2) 주의 사항 & 권장 사항

  • 파일
    • xml 파일을 먼저 생성하면 서버가 켜지지 않을 수 있다. Controller와 JSP 파일 연결 확인 후 xml 파일을 생성한다.
    • DB 테이블 1개 당 xml 파일 1개를 생성한다.
    • DTD(Document Type Definition)를 파일 상단에 작성한다.
      ex) <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  • 태그
    • 공통
      • 속성 작성 시, 내가 생성한 자료형(인터페이스/클래스)이라면 경로 작성한 후 Ctrl + 클릭으로 연결 상태를 확인한다.
    • select
      • resultType 속성을 작성한다.
      • 쿼리문에서 COUNT(*)로 받아오고 resultType="boolean"일 경우, 0이면 false, 1이면 true를 반환한다.
  • 쿼리문
    • 공통
      • #{}로 도매인명Mapper.java 인터페이스 내부 메소드의 파라미터(@Param)를 가져온다.(#{}은 작은따옴표가 붙어서 인식되므로, 작은따옴표가 오류의 원인일 경우 값을 그대로 출력하는 ${}를 사용하기도 한다.)
      • 대문자로 작성한다.
      • * 사용을 지양한다.
      • 부등호 사용 시, CDATA(권장) 혹은 HTML 특수 기호를 사용한다.
      • 제어문 사용이 가능하다. 조건문 내에서 파라미터로 온 변수 사용 시, 일반 변수처럼 사용할 수 있다.(#{} 혹은 ${} 사용 안함)
        ex. <if test="imagePath != null">, `imagePath` = #{imagePath}</if>
    • INSERT
      • auto_increase 속성이 있는 필드: 자동 생성되므로 쿼리문에 작성하지 않는다.
      • createdAt, updatedAt: default current_timestamp 속성이 있어 기본값으로 자동 삽입되더라도, 관례적으로 쿼리문을 작성한다.
      • 값이 null로 들어온 필드: null인 필드라도 반드시 쿼리문을 작성한다.
    • UPDATE
      • updatedAt: NOW()로 업데이트 일시를 변경하는 쿼리문을 작성한다.

📍 Domain(= Entity, Model)

1) 주의 사항

  • 일반 클래스이다.
  • createdAt, updatedAt의 자료형인 Date는 java.util.Date에서 import 한다.
  • null이 가능한 필드는 참조 자료형으로 지정한다.
  • 필드의 자료형을 변경하는 경우, 반드시 getter, setter도 함께 변경한다.

2) 권장 사항

📍 예시

1. 파일 구조 & 흐름

src
└── main
    ├── java
    │   └── com.example
    │       └── lesson04
    │           ├── bo
    │           │   └── StudentBO.java
    │           ├── domain
    │           │   └── Student.java
    │           ├── mapper
    │           │   └── StudentMapper.java
    │           └── Lesson04Ex02Controller.java
    ├── resources
    │   └── mappers
    │       └── StudentMapper.xml
    └── webapp
        └── WEB-INF
            └── jsp
                └── lesson04
                    ├── addStudent.jsp
                    └── afterAddStudent.jsp

2. 코드

🚩 가독성을 위해 import 문은 제외했습니다.

1) Controller

// 🟢 Lesson04Ex02Controller.java

package com.example.lesson04;

@RequestMapping("/lesson04/ex02")
@Controller
public class Lesson04Ex02Controller {

	@Autowired
	private StudentBO studentBO;
	
	// 학생 추가 화면
	@GetMapping("/add-student-view")
	public String addStudentView() {
		return "lesson04/addStudent";
	}
	
	// 방금 가입된 학생 화면(학생 추가 완료 후 화면)
	@PostMapping("/add-student")
	public String addStudent(
			@ModelAttribute Student student,
			Model model) {
		
		// DB insert
		studentBO.addStudent(student);
		
		// DB에서 방금 가입된 사용자 select
		int id = student.getId();
		Student latestStudent = studentBO.getStudentById(id);
		
		// Model 객체에 담는다.(JSP에서 사용하도록)
		model.addAttribute("student", latestStudent);
		
		// JSP 경로 리턴
		return "lesson04/afterAddStudent";
	}
	
}

2) Service(= BO)

// 🟢 StudentBO.java

package com.example.lesson04.bo;

@Service
public class StudentBO {

	@Autowired
	private StudentMapper studentMapper;
	
	public void addStudent(Student student) {
		studentMapper.insertStudent(student);
	}
	
	public Student getStudentById(int id) {
		return studentMapper.selectStudentById(id);
	}
	
}

3) Repository(= DAO, Mapper)

  • 도매인명Mapper.java
// 🟣 StudentMapper.java

package com.example.lesson04.mapper;

@Repository
public interface StudentMapper {

	public void insertStudent(Student student);
	
	public Student selectStudentById(int id);
	
}
  • 도매인명Mapper.xml
<!-- 🟤 StudentMapper.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
 <mapper namespace="com.example.lesson04.mapper.StudentMapper">
 	<insert id="insertStudent" parameterType="com.example.lesson04.domain.Student" useGeneratedKeys="true" keyProperty="id">
 		INSERT INTO `new_student`
 		(
 			`name`
			, `phoneNumber`
			, `email`
			, `dreamJob`
			, `createdAt`
			, `updatedAt`
 		)
 		VALUES
 		(
 			#{name}
			, #{phoneNumber}
			, #{email}
			, #{dreamJob}
			, NOW()
			, NOW()
 		)
 	</insert>
 	
 	<select id="selectStudentById" parameterType="int" resultType="com.example.lesson04.domain.Student">
 		SELECT
 			`id`
 			, `name`
			, `phoneNumber`
			, `email`
			, `dreamJob`
			, `createdAt`
			, `updatedAt`
		FROM
			`new_student`
		WHERE
			`id` = #{id}
 	</select>
 </mapper>

4) Domain(= Entity, Model)

// 🟢 Student.java

package com.example.lesson04.domain;

public class Student {
	private int id;
	private String name;
	private String phoneNumber;
	private String email;
	private String dreamJob;
	private Date createdAt;
	private Date updatedAt;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPhoneNumber() {
		return phoneNumber;
	}
	public void setPhoneNumber(String phoneNumber) {
		this.phoneNumber = phoneNumber;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getDreamJob() {
		return dreamJob;
	}
	public void setDreamJob(String dreamJob) {
		this.dreamJob = dreamJob;
	}
	public Date getCreatedAt() {
		return createdAt;
	}
	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}
	public Date getUpdatedAt() {
		return updatedAt;
	}
	public void setUpdatedAt(Date updatedAt) {
		this.updatedAt = updatedAt;
	}
}

5) JSP

<%-- 🟡 addStudent.jsp --%>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>학생 추가</title>
<!-- BootStrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
</head>
<body>
	<div class="container">
		<h1>회원가입</h1>
		
		<form method="post" action="/lesson04/ex02/add-student">
			<div class="form-group">
				<label for="name">이름</label>
				<input type="text" id="name" name="name" class="form-control col-4">
			</div>
			<div class="form-group">
				<label for="phoneNumber">핸드폰 번호</label>
				<input type="text" id="phoneNumber" name="phoneNumber" class="form-control col-4">
			</div>
			<div class="form-group">
				<label for="email">이메일</label>
				<input type="text" id="email" name="email" class="form-control col-4">
			</div>
			<div class="form-group">
				<label for="dreamJob">장래희망</label>
				<input type="text" id="dreamJob" name="dreamJob" class="form-control col-4">
			</div>

			<input type="submit" value="회원가입" class="btn btn-success">
		</form>
	</div>
</body>
</html>
<%-- 🟡 afterAddStudent.jsp --%>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>방금 가입된 사용자 정보</title>
<!-- BootStrap -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>
</head>
<body>
	<div class="container">
		<h1>방금 가입된 사용자 정보</h1>
		<table class="table table-striped">
			<tr>
				<th>id</th>
				<td>${student.id}</td>
			</tr>
			<tr>
				<th>이름</th>
				<td>${student.name}</td>
			</tr>
			<tr>
				<th>핸드폰 번호</th>
				<td>${student.phoneNumber}</td>
			</tr>
			<tr>
				<th>이메일</th>
				<td>${student.email}</td>
			</tr>
			<tr>
				<th>장래희망</th>
				<td>${student.dreamJob}</td>
			</tr>
			<tr>
				<th>생성일</th>
				<td>${student.createdAt}</td>
			</tr>
			<tr>
				<th>수정일</th>
				<td>${student.updatedAt}</td>
			</tr>
		</table>
	</div>
</body>
</html>

0개의 댓글