xxxService
와 같은 이름 구성, 메서드 이름 역시 고객들이 사용하는 용어 그대로 사용MyBatis, mybatis-spring
을 이용해서 구성💫 POJO
POJO는 Java 언어 사양에 의해 강제된 것 이외의 제한에 구속되지 않는 Java 개체입니다.
특별한 제한이 없는 평범한 물건입니다. POJO 파일에는 특별한 클래스 경로가 필요하지 않습니다. Java 프로그램의 가독성과 재사용성을 높입니다.
POJO는 유지 관리가 쉽기 때문에 현재 널리 사용됩니다. 그들은 읽고 쓰기 쉽습니다. POJO 클래스에는 속성 및 메서드에 대한 명명 규칙이 없습니다. Java 프레임워크 에 연결되어 있지 않습니다 . 모든 Java 프로그램에서 사용할 수 있습니다.
POJO 클래스
는 가독성과 재사용성을 높이기 위해 객체를 정의하는 데 사용되기 때문에 Bean과 유사합니다. 그들 사이의 유일한 차이점은 Bean 파일에는 약간의 제한이 있지만 POJO 파일에는 특별한 제한이 없다는 것입니다.
POJO의 속성
출처 : https://stackoverflow.com/questions/3527264/how-to-create-a-pojo
출처 : https://www.javatpoint.com/pojo-in-java
xxxService
라는 방식을 사용, 인터페이스를 구현한 클래스는 xxxServiceImpl이라는 이름을 사용MyBatis의 Mapper의 인터페이스
활용설정
클래스들의 보관 패키지💫 AOP(Aspect Oriented Programming)는 이름에서 알 수 있듯이 프로그래밍에서 aspect를 사용합니다. 코드를 모듈화 라고도 하는 다른 모듈로 나누는 것으로 정의할 수 있습니다 . 여기서 측면은 모듈화의 핵심 단위입니다.
Aspect는 기능에 대한 코드 코어를 어지럽히지 않고 비즈니스 로직의 중심이 아닌 트랜잭션, 로깅과 같은 횡단 관심사의 구현을 가능하게 합니다. 기존 코드에 조언인 추가 동작을 추가하여 이를 수행합니다.
Spring과 Aspect-Oriented Programming이 작동하는 방식
메서드를 호출하면 교차 절단 문제가 자동으로 구현될 것이라고 생각할 수 있지만 그렇지 않습니다. 메서드를 호출하는 것만으로는 어드바이스(해야 할 작업)가 호출되지 않습니다.
Spring은 프록시 기반 메커니즘
을 사용합니다 . 즉, 원래 객체를 감싸고 메소드 호출과 관련된 어드바이스를 취할 프록시 객체를 생성합니다. 프록시 객체는 프록시 팩토리 빈을 통해 수동으로 생성하거나 XML 파일의 자동 프록시 구성을 통해 생성할 수 있으며 실행이 완료되면 소멸됩니다. 프록시 개체는 실제 개체의 원본 동작을 강화하는 데 사용
출처 : https://www.geeksforgeeks.org/aspect-oriented-programming-and-aop-in-spring-framework/
잘못해서 tb1로 했으나 테이블 생성 시 tbl_
이나 t_
와같이 구분 가능한 것을 넣어주는 게 좋다.
pk 지정시에도 pk_board라는 식으로 이름 지정하는 게 좋다
pencil mockup
CREATE table tb1_todo(
tno int auto_increment primary key,
title varchar(100) not null,
dueDate date not null,
writer varchar(50) not null,
finished tinyint default 0
)
💫 mapper 클래스
는 데이터베이스 테이블과 그에 상응하는 객체 간의 변환, DTO(Data Transfer Object)와 도메인 객체 간의 변환, API 요청과 응답 객체 간의 변환 등을 다루는데 사용된다.
이를테면, 데이터베이스로부터 읽어온 데이터를 객체로 변환하거나, 반대로 객체를 데이터베이스에 저장 가능한 형태로 변환하는 작업을 수행
💫 java 객체 간의 데이터 매핑을 간단하게 처리하기 위한 라이브러리
데이터 매핑은 한 객체의 데이터를 다른 객체로 복사하거나 이동하는 작업을 의미
예를 들어, 데이터베이스 엔티티와 DTO(Data Transfer Object) 간의 변환, 또는 서로 다른 두 객체 간의 필드 복사 등이 데이터 매핑에 해당
ModelMapper
는 이러한 데이터 매핑 작업을 자동화하고 편리하게 처리할 수 있도록 도움 개발자는 별도의 복잡한 매핑 로직을 작성하지 않아도 되며, 설정을 통해 어떤 필드를 어떤 필드에 매핑할지, 어떤 변환을 수행할지 등을 지정 가능
데이터베이스의 엔티티 객체를 웹 애플리케이션의 화면에 보여줄 DTO로 변환
웹 애플리케이션에서 사용자 입력 데이터를 데이터베이스 엔티티로 변환하여 저장
REST API에서 입력 받은 JSON 데이터를 도메인 객체로 변환하여 비즈니스 로직 처리
ModelMapper
는 반복적이고 번거로운 매핑 작업을 간단하게 처리할 수 있도록 해주며, 코드의 가독성과 유지보수성을 높여준다.
ModelMapper
를 스프링 애플리케이션 컨텍스트에 빈으로 등록하는 방법의 코드ModelMapper
를 스프링의 빈으로 등록하는 이유는 설정의 중앙화, 인스턴스 관리, 테스트 용이성, 일관성 유지 등 다양한 이점을 활용하기 위해서이다.package com.multicampus.springex.config;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.modelmapper.spi.MatchingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //ModelMapperConfig가 스프링의 설정 빈으로 등록함을 명시
public class ModelMapperConfig {
@Bean //bean 등록
public ModelMapper getMapper(){
//VO를 받아오는 코드
// ModelMapper 라이브러리를 이용해서 객체 간의 매핑을 수행하는 설정 변경
// ModelMapper는 객체 간의 속성값을 자동으로 매핑해주는 라이브러리로 객체 간의 변환 작업 편리
ModelMapper modelMapper = new ModelMapper();
// setFieldMatchingEnabled(true) : 필드 기반 설정, 객체의 필드들 간의 이름이 같은 경우 자동 매칭
// setFieldAccessLevel : 매핑할 필드의 접근 레벨 지정하며, PRIVATE 레벨로 설정되어 있는 경우 private 접근 제어자로
// 선언된 필드도 매핑 대상에 포함
// setMatchingStrategy은 매칭 전략을 지정하면 STRICT 전략시, 매핑되지 않는 속성이 있는 경우 예외 발생하며 매핑 작업 실패
modelMapper.getConfiguration().setFieldMatchingEnabled(true)
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE) //setFieldAccessLevel : 레벨을 정함
.setMatchingStrategy(MatchingStrategies.STRICT);
return modelMapper;
}
}
화면 처리
: 부트스트랩 - css 지원<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<div class="card-body">
<h5 class="card-title">Todo List</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="https://www.naver.com" class="btn btn-primary">네이버 이동</a>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
MyBatis 와 스프링을 이용한 영속성 처리
package com.multicampus.springex.dto;
import lombok.*;
import java.time.LocalDate;
@ToString
@Data //setter, getter을 알아서 처리 해줌
@Builder
// 모든 속성에 대한 아규먼트 처리
@AllArgsConstructor //public Todo(Long tno, String title){}을 만들어줌
@NoArgsConstructor //Default 생성자를 만들어줌
public class TodoDTO {
private Long tno;
private String title;
private LocalDate dueDate;
private boolean finished;
private String writer;
}
TodoMapper.java
TodoMapper.getTime()
: 실제 동작하는 객체package com.multicampus.springex.mapper;
public interface TodoMapper {
String getTime();
}
**<?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.multicampus.springex.mapper.TodoMapper">
<!--어노테이션이 아니라 xml 파일로 sql 작성-->
<!--TodoMapperjava에 있는 메소드 명이랑 select id가 같아야 함-->
<select id="getTime" resultType="string">
select now()
</select>
</mapper>**
@ContextConfiguration
을 통해 테스트 클래스가 필요한 스프링 빈 및 설정 정보를 로드하여 테스트 환경을 설정하고, 이를 통해 스프링의 다양한 기능을 테스트할 수 있다.package com.multicampus.springex.mapper;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoMapperTests {
@Autowired(required = false)
private TodoMapper todoMapper;
// mybatis 잘 동작하는지 getTime 수행여부를 알 수 있다.
@Test
public void testGetTime(){
log.info(todoMapper.getTime());
}
}
<!--로그레벨 TRACE 좀 더 상세하게 보겠다라는 의미-->
<logger name="com.multicampus.springex.mapper" level="TRACE" additivity="false">
<appender-ref ref="console"/>
</logger>
package com.multicampus.springex.mapper;
import com.multicampus.springex.domain.TodoVO;
public interface TodoMapper {
String getTime();
/*TodoVO를 파라미터로 입력받는 insert() 정의*/
void insert(TodoVO todoVO); //일은 TodoMapper.xml이 함
}
tb1_todo
로 함<?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.multicampus.springex.mapper.TodoMapper">
<!--어노테이션이 아니라 xml 파일로 sql 작성-->
<!--TodoMapperjava에 있는 메소드 명이랑 select id가 같아야 함-->
<select id="getTime" resultType="string">
select now()
</select>
<!--TodoMapper.java에 있는 메소드 명이랑 inesert id가 같아야 함-->
<insert id="insert">
/*prepareStatment에서 쓴 ?대신 인파라미터를 #으로 사용*/
insert into tb1_todo (title, dueDate, writer) values (#{title}, #{dueDate}, #{writer})
</insert>
</mapper>
package com.multicampus.springex.mapper;
import com.multicampus.springex.domain.TodoVO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoMapperTests {
@Autowired(required = false)
private TodoMapper todoMapper;
// mybatis 잘 동작하는지 getTime 수행여부를 알 수 있다.
@Test
public void testGetTime(){
log.info(todoMapper.getTime());
}
@Test
public void testInsert(){
TodoVO todoVO = TodoVO.builder()
.title("스프링 TodoTest")
.dueDate(LocalDate.of(2023, 8, 9))
.writer("user1").build();
todoMapper.insert(todoVO);
}
}
package com.multicampus.springex.service;
import com.multicampus.springex.dto.TodoDTO;
public interface TodoService {
// TodoDTO 객체를 매개변수로 받아들여 Todo 아이템을 등록하는 역할을 수행하는 것
void register(TodoDTO todoDTO);
}
@Service
: 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용@RequiredArgsConstructor
: Lombok 어노테이션으로, 해당 클래스의 final 필드에 대한 생성자를 생성 생성자 주입을 통해 의존성을 주입받을 수 있다.package com.multicampus.springex.service;
import com.multicampus.springex.domain.TodoVO;
import com.multicampus.springex.dto.TodoDTO;
import com.multicampus.springex.mapper.TodoMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
@Service //서비스 빈으로 등록
@Log4j2
@RequiredArgsConstructor //싱글톤으로 지정한 빈을 생성자 주입에 의해 injection
public class TodoServiceImpl implements TodoService{
//의존성 주입이 필요한 객체타입을 final로 고정하고 생성자를 생성하여 주입
private final TodoMapper todoMapper;
private final ModelMapper modelMapper;
@Override
public void register(TodoDTO todoDTO) {
log.info(modelMapper);
TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
log.info(todoVO);
//TodoMapper가 받음 => TodoMapper.xml이 들어가서 동작
todoMapper.insert(todoVO);
}
}
<!--timeMapper와 연결해줘, 빈에 등록할거야의 의미-->
<context:component-scan base-package="com.multicampus.springex.config"/>
<context:component-scan base-package="com.multicampus.springex.service"/>
package com.multicampus.springex.service;
import com.multicampus.springex.dto.TodoDTO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations="file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoServiceTests {
@Autowired
private TodoService todoService;
@Test
public void testRegister(){
//todoDTO 생성
TodoDTO todoDTO = TodoDTO.builder()
.title("Test Todo 1")
.dueDate(LocalDate.now())
.writer("user2")
.build();
// 값을 넣어줌
todoService.register(todoDTO);
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Todo Register</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<div class="card-body">
<form action="/todo/register" method="post">
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-contro" placeholder="Title">
</div>
<div class="input-group mb-3">
<span class="input-group-text">DueDate</span>
<input type="date" name="dueDate" class="form-contro" placeholder="Duedate">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="Writer" name="writer" class="form-contro" placeholder="Writer">
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
💫 이 컨트롤러는 클라이언트의 요청에 따라 각각의 요청을 처리하고, 적절한 로직을 수행한 후 뷰로 결과를 반환하거나 리다이렉트,이를 통해 사용자와 애플리케이션 간의 상호작용을 관리하고, 서비스 로직과 뷰 간의 중재 역할을 수행
RedirectAttributes redirectAttributes
매개변수는 리다이렉트 시에 데이터를 전달할 수 있는 객체public class TodoController {
@RequestMapping("/list") //localhost:8090/todo/list
public void list(Model model){
log.info("todo_list");
}
//@RequestMapping(value="/register", method = RequestMethod.GET) //localhost:8090/todo/register
@GetMapping("/register")
/*제대로 불러오는지 확인하는 작업, 제대로 불러오면 콘솔에 뜬다. */
public void registerGET(){
log.info("GET todo_register.....");
}
// register은 post와 get방식 따로 따로, 객체를 알아서 넣어줌
@PostMapping("/register")
public String registerPost(TodoDTO todoDTO, RedirectAttributes redirectAttributes){
log.info("Post todo register");
log.info(todoDTO);
return "redirect:/todo/list";
}
}
@NotEmpty
추가@Future
추가package com.multicampus.springex.dto;
import lombok.*;
import javax.validation.constraints.Future;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
@ToString
@Data //setter, getter을 알아서 처리 해줌
@Builder
// 모든 속성에 대한 아규먼트 처리
@AllArgsConstructor //public Todo(Long tno, String title){}을 만들어줌
@NoArgsConstructor //Default 생성자를 만들어줌
public class TodoDTO {
private Long tno;
@NotEmpty //Null, 빈 문자열 불가
private String title;
@Future //현재 보다 미래인가?
private LocalDate dueDate;
private boolean finished;
@NotEmpty
private String writer;
}
@RequiredArgConstructor
: final로 지정해서 지정된것만 넣겠다.addFlashAttribute
추가@Valid
바인딩을 위해 BindingResult bindingResult 추가💫
BindingResult
는 입력값과 관련된 유효성 검사의 결과와 에러 정보를 담는 컨테이너
만약 입력값이 유효하지 않을 경우, 해당 에러 정보가 BindingResult
에 저장되어 컨트롤러에서 이를 확인하고 처리할 수 있게 되며 이를 통해 사용자로부터 들어오는 데이터의 검증과 관련된 처리를 간편하게 구현할 수 있다.
데이터 베이스에 넘어온 값 추가
해주기 위해 todoService.register(todoDTO);처리package com.multicampus.springex.controller;
import com.multicampus.springex.dto.TodoDTO;
import com.multicampus.springex.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller //스프링 MVC에서 컨트롤러 역할, 스프링의 빈(Bean)으로 등록
@RequestMapping("/todo")
@Log4j2
@RequiredArgsConstructor //final로 지정해서 지정된 것만 주입하겠다.
public class TodoController {
// injection을 안해서 데이터 베이스에 값이 넣어지지 않은 것
private final TodoService todoService;
@RequestMapping("/list") //localhost:8090/todo/list
public void list(Model model){
log.info("todo_list");
}
//@RequestMapping(value="/register", method = RequestMethod.GET) //localhost:8090/todo/register
@GetMapping("/register")
/*제대로 불러오는지 확인하는 작업, 제대로 불러오면 콘솔에 뜬다. */
public void registerGET(){
log.info("GET todo_register.....");
}
// register은 post와 get방식 따로 따로, 객체를 알아서 넣어줌
@PostMapping("/register")
//서버 사이드 검증 redirect에 바인딩
public String registerPost(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes){
log.info("Post todo register");
if(bindingResult.hasErrors()){
log.info("has error");
// addFlashAttribute : 한 번만 일회성으로 사용자에게 에러를 보여줌 a
redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
return "redirect:/todo/register";
}
return "redirect:/todo/list";
}
}
@Future
지정해서<%@ page contentType="text/html; UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Todo Register</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<div class="card-body">
<form action="/todo/register" method="post">
<div class="input-group mb-3">
<span class="input-group-text">Title</span>
<input type="text" name="title" class="form-contro" placeholder="Title">
</div>
<div class="input-group mb-3">
<span class="input-group-text">DueDate</span>
<input type="date" name="dueDate" class="form-contro" placeholder="Duedate">
</div>
<div class="input-group mb-3">
<span class="input-group-text">Writer</span>
<input type="text" name="writer" class="form-control" placeholder="Writer">
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="reset" class="btn btn-secondary">Reset</button>
</div>
</div>
</form>
<script>
const serverVaildResult ={}
//TodoController의 에러
<c:forEach items="${errors}" var="error">
// 객체에다가 넣기
serverVaildResult['${error.getField()}'] ='${error.defaultMessage}'
</c:forEach>
console.log(serverVaildResult)
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
// 추가해주기 => 데이터베이스에 드디어 추가 가능
log.info(todoDTO);
todoService.register(todoDTO);
return "redirect:/todo/list";
package com.multicampus.springex.mapper;
import com.multicampus.springex.domain.TodoVO;
import java.util.List;
public interface TodoMapper {
String getTime();
/*TodoVO를 파라미터로 입력받는 insert() 정의*/
void insert(TodoVO todoVO); //일은 TodoMapper.xml이 함
// 가장 최근에 등록된 글 순서대로 tb1_todo 테이블의 모든 row들을 가져온다. todolist_selectall 작업
List<TodoVO> selectAll();
}
package com.multicampus.springex.service;
import com.multicampus.springex.dto.TodoDTO;
import java.util.List;
public interface TodoService {
void register(TodoDTO todoDTO);
List<TodoDTO> getAll();
}
package com.multicampus.springex.service;
import com.multicampus.springex.domain.TodoVO;
import com.multicampus.springex.dto.TodoDTO;
import com.multicampus.springex.mapper.TodoMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service //서비스 빈으로 등록
@Log4j2
@RequiredArgsConstructor //싱글톤으로 지정한 빈을 생성자 주입에 의해 injection
public class TodoServiceImpl implements TodoService{
//의존성 주입이 필요한 객체타입을 final로 고정하고 생성자를 생성하여 주입
// 해당 클래스의 의존성이며, final로 선언되어 변경되지 않음을 보장
private final TodoMapper todoMapper;
private final ModelMapper modelMapper;
// todo 항목등록하는 메서드
@Override
public void register(TodoDTO todoDTO) {
log.info(modelMapper);
// modelMapper을 사용하여 TodoDTO 객체를 TodoVO객체로 변환하고
// 이를 TodoMapper를 통해 데이터베이스 등록
TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
log.info(todoVO);
//TodoMapper가 받음 => TodoMapper.xml이 들어가서 동작
todoMapper.insert(todoVO);
}
// 구현 객체이기 때문에 TodoService가 바뀌면 바뀜
// 모든 할 일 항목 조회
@Override
public List<TodoDTO> getAll() {
// 데이터베이스에서 조회한 할일 항목을 VO에서 DTO로 변환하여 리스트로 반환하는 작업을 수행
// todoMapper를 통해 데이터 베이스에서 모든 할일 조회 => stream 변환 => TodoDTO 변환
// VO객체를 DTO 객체로 변환하여 매핑 => 리스트 수집후 생성
List<TodoDTO> dtoList = todoMapper.selectAll().stream()
.map(vo-> modelMapper.map(vo, TodoDTO.class))
.collect(Collectors.toList());
return dtoList;
}
}
@RequestMapping("/list") //localhost:8090/todo/list
public void list(Model model){
log.info("todo_list");
// TodoService에서 리턴한 List<TodoDTO> getAll();을 model에다가 담기
model.addAttribute("dtoList", todoService.getAll());
//model 'dtoList' 이름으로 목록 데이터가 담겨있다. => list.jsp가 처리해줘야 함
}
<?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.multicampus.springex.mapper.TodoMapper">
<!--어노테이션이 아니라 xml 파일로 sql 작성-->
<!--TodoMapperjava에 있는 메소드 명이랑 select id가 같아야 함-->
<select id="getTime" resultType="string">
select now()
</select>
<!--TodoMapper.java에 있는 메소드 명이랑 inesert id가 같아야 함-->
<insert id="insert">
/*prepareStatment에서 쓴 ?대신 인파라미터를 #으로 사용*/
insert into tb1_todo (title, dueDate, writer) values (#{title}, #{dueDate}, #{writer})
</insert>
<!--리턴 타입을 정해줌.. 왜? 모든 데이터를 가져올 것이기 때문에 resultset형태로 반환해주는데 그게 모든 데이터가
있으니 row 값들이 다 넘어오는데 한줄 단위로 묶어 담아야 하니
하나의 TodoVO로 넘어와서 한줄 단위로 묶어줌-->
<select id="selectAll" resultType="com.multicampus.springex.domain.TodoVO">
select * from tb1_todo order by tno desc
</select>
</mapper>
package com.multicampus.springex.mapper;
import com.multicampus.springex.domain.TodoVO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.LocalDate;
import java.util.List;
@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
public class TodoMapperTests {
@Autowired(required = false)
private TodoMapper todoMapper;
// mybatis 잘 동작하는지 getTime 수행여부를 알 수 있다.
@Test
public void testGetTime(){
log.info(todoMapper.getTime());
}
@Test
public void testInsert(){
TodoVO todoVO = TodoVO.builder()
.title("스프링 TodoTest")
.dueDate(LocalDate.of(2023, 8, 9))
.writer("user1").build();
todoMapper.insert(todoVO);
}
@Test
public void testSelectALL(){
List<TodoVO> voList = todoMapper.selectAll();
voList.forEach(vo -> log.info(vo));
// for(TodoVO vo:voList){
// log.info(vo);
// }
}
}
7. list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<h1>Header</h1>
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="https://www.daum.net">Daum</a>
<a class="nav-link" href="https://www.naver.com">Naver</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled">Disabled</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
<div class="row content">
<h1>Content</h1>
<div class="row content">
<div class="col">
<div class="card">
<div class="card-header">
Featured
</div>
<div class="card-body">
<h5 class="card-title">Special title treatment</h5>
<table class="table">
<thead>
<tr>
<th scope="col">Tno</th>
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">DueDate</th>
<th scope="col">Finished</th>
</tr>
</thead>
<tbody>
<%--모델의 dtoList를 가져올 것이니--%>
<c:forEach items="${dtoList}" var="dto">
<tr>
<th scope="row"><c:out value="${dto.tno}"/></th>
<td>
<a href="" class="text-decoration-none" data-tno="${dto.tno}" >
<c:out value="${dto.title}"/>
</a>
</td>
<td><c:out value="${dto.writer}"/></td>
<td><c:out value="${dto.dueDate}"/></td>
<td><c:out value="${dto.finished}"/></td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row footer">
<div class="row fixed-bottom" style="z-index: -100">
<footer class="py-1 my-1 ">
<p class="text-center text-muted">geumjuLee</p>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
기능별로 분리를 잘해두는 것이 중요
오늘은 CS 스터디 방향성에 대해 팀원분들과 나눴다. 한 분은 아무래도 지금은 CS스터디 보다는 알고리즘 쪽이나 스프링부분을 좀 더 집중하고 싶다고 하셔서 오늘까지만 진행하기로 하셨다. 강사님과 나눈 피드백을 공유해주셔서 방향성과 계획을 다시 잡고 공부하기로 했다. CS를 주로 가되 다른 수업부분이나 알고리즘 모르는 부분있으면 카톡방에서 공유하기로 했다. 이렇게 조율해가며 완성해 나가는 것도 하나의 즐거움인 것 같다. 그 뿐 아니라 다른 얘기 목표나, 어느정도의 지식 수준인지 등에 대한 대화도 나눴다. 한 결 가까워진 느낌..?
그리고 나서 오늘 공부한 거 좀 더 주석이랑 코드 해석하면서 복습했다. 후 이제 GITHUB에 링크 연동해두고 자야지... 내일도 화이팅 하자
잘 봤습니다. 좋은 글 감사합니다.