@Slf4j // 로그 객체 자동 생성
@Controller // 요청/응답 제어 역할 명시 + Bean 등록
public class MainController {
@Autowired // DI (의존성 주입) - 상속관계이거나 같은 타입
private TodoService service;
@RequestMapping("/") // 최상위(메인 페이지 요청)
public String mainPage(Model model) {
// 의존성 주입(DI) 확인(진짜 Service 객체 들어옴!)
log.debug("service : " + service);
// Service 메서드 호출 후 결과 반환 받기
// 목록조회, 카운트 결과 2개 기능 수행하고 싶음
// --> Map 사용
Map<String, Object> map = service.selectAll();
// map에 담긴 내용 추출
List<Todo> todoList = (List<Todo>)map.get("todoList");
int completeCount = (int)map.get("completeCount");
// Model : 값 전달용 객체(request scope) + session 변환 가능
model.addAttribute("todoList", todoList);
model.addAttribute("completeCount", completeCount);
return "common/main"; // templates/common/main.html -> 이쪽으로 forward
}
}
public interface TodoService {
/** 할 일 목록 + 완료된 할 일 개수 조회
* @return map
*/
Map<String, Object> selectAll();
}
@Service // 비즈니스 로직 처리(데이터 가공, 트랜잭션 처리) 역할 명시 + Bean 등록
public class TodoServiceImpl implements TodoService {
@Autowired
private TodoMapper mapper;
// 할 일 목록 + 완료된 할 일 개수 조회
@Override
public Map<String, Object> selectAll() {
// 1. 할 일 목록 조회
List<Todo> todoList = mapper.selectAll();
// 2. 완료된 할 일 개수 조회
int completeCount = mapper.getCompleteCount();
// 3. Map으로 묶어서 반환
Map<String, Object> map = new HashMap<>();
map.put("todoList", todoList);
map.put("completeCount", completeCount);
return map;
}
}
/* @Mapper
* - MyBatis 제공 어노테이션
* - 해당 어노테이션이 작성된 인터페이스는
* namespace에 해당 인터페이스가 작성된
* mapper.xml 파일과 연결되어 SQL 호출/수행/결과 반환 가능!
*
* - MyBatis에서 제공하는 Mapper 상속 객체가 Bean 등록됨.
*/
@Mapper
public interface TodoMapper {
/* Mapper의 메서드명 == mapper.xml 파일 내 태그의 id
* - 메서드명과 id가 같은 태그가 서로 연결됨
*
*/
/** 할 일 목록 조회
* @return todoList
*/
List<Todo> selectAll();
/** 완료된 할 일 개수 조회
* @return completeCount
*/
int getCompleteCount();
}
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
private int todoNo; // 할 일 번호
private String todoTitle; // 할 일 제목
private String todoContent; // 할 일 내용
private String complete; // 할 일 완료 여부("Y" / "N")
private String regDate; // 할 일 등록일(String으로 변환)
}
<mapper namespace="edu.kh.todo.model.mapper.TodoMapper">
<!-- Mapper 인터페이스 경로 작성(필수!!) 패키지명.인터페이스명 -->
<!-- 아래 태그는 mapper.xml 파일 만들자마자 삭제! -->
<!-- <cache-ref namespace=""/> -->
<!-- 수행 하려는 SQL 구문 태그 작성-->
<!-- <select> 태그 속성
- id : 태그를 식별하는 값
(연결된 Mapper의 메서드명과 동일하게 작성해서 연결)
- resultType : 조회 결과 한 줄(1행)을 저장하여
반환할 변수/객체 자료형 지정
조회 결과가 여러 행이면
행 별로 변수/객체 자료형으로 저장 후
모아서 List로 변환해서 반환
-->
<!--
edu.kh.todo.model.mapper.TodoMapper 인터페이스의
selectAll() 메서드와 연결
=> selectAll() 메서드 호출 시 연결된 SQL 실행
* mybatis-config.xml 안에
<setting name="mapUnderscoreToCamelCase" value="true" />
구문이 있기 때문에 값을 얻어올 수 있음
* DBConfig에서 별칭 등록을 해두었기 때문에 Todo라고 써도 된다.
=> sessionFactoryBean.setTypeAliasesPackage("edu.kh.todo")
-->
<!-- 할 일 목록 조회 -->
<select id="selectAll" resultType="edu.kh.todo.model.dto.Todo">
SELECT TODO_NO, TODO_TITLE, TODO_CONTENT, COMPLETE,
TO_CHAR(REG_DATE, 'YYYY-MM-DD HH24:MI:SS') REG_DATE
FROM TB_TODO
ORDER BY TODO_NO
</select>
<!-- Java의 int는 MyBatis의 _int -->
<!-- 완료된 할 일 개수 조회 -->
<select id="getCompleteCount" resultType="_int">
SELECT COUNT(*)
FROM TB_TODO
WHERE COMPLETE = 'Y'
</select>
</mapper>
<h1>TodoList-boot</h1>
<!-- ${#lists.size(todoList)} == ${todoList.size()} -->
<h3 th:text="|전체 Todo 개수 : ${#lists.size(todoList)}개 /
완료된 Todo 개수 : ${completeCount}개|">내용</h3>
log.debug("service : " + service); 의존성 주입(DI) 확인 결과


인터페이스 메서드 부분 생략
@Controller
@RequestMapping("todo") // "/todo"로 시작하는 모든 요청 매핑
public class TodoController {
@Autowired // 같은 타입/상속관계 Bean 의존성 주입(DI)
private TodoService service;
@PostMapping("add") // "/todo/add" Post 방식 요청 매핑
public String addTodo(
@RequestParam("todoTitle") String todoTitle,
@RequestParam("todoContent") String todoContent,
RedirectAttributes ra) {
// RedirectAttributes : 리다이렉트 시 값을 1회성으로 전달하는 객체
// RedirectAttributes.addFlashAttribute("key", value) 형식으로 잠깐 세션에 속성을 추가
// [원리]
// 응답 전 : request scope
// redirect 중 : session scope로 이동
// 응답 후 : request scope 복귀
// 서비스 메서드 호출 후 결과 반환 받기
int result = service.addTodo(todoTitle, todoContent);
// 삽입 결과에 따라 message 값 지정
String message = null;
if(result > 0) message = "할 일 추가 성공!!";
else message = "할 일 추가 실패..ㅠㅠ";
// 리다이렉트 후 1회성으로 사용할 데이터를 속성으로 추가
ra.addFlashAttribute("message", message);
return "redirect:/"; // 메인페이지 재요청 - HTML 주소가 아님!!!!!!!!!
}
}
// 할 일 추가
@Override
public int addTodo(String todoTitle, String todoContent) {
// Connection 생성 / 반환 X
// 트랜잭션 제어 처리 -> @Transactional 어노테이션
// MyBatis에서 SQL에 전달할 수 있는 파라미터의 개수는
// 오직 1개
// -> todoTitle, todoContent를 Todo DTO로 묶어서 전달
Todo todo = new Todo();
todo.setTodoTitle(todoTitle);
todo.setTodoContent(todoContent);
return mapper.addTodo(todo);
}
<!-- parameterType : 전달 받은 값(전달 인자/매개변수)의 타입을 명시
-> DTO, Map인 경우 필드명/key를 입력해서 하나씩 꺼낼 수 있음
-->
<!--
mapper에서 Java의 data(파라미터)를 SQL에 삽입하는 방법
1) #{변수명 | 필드명} : SQL에 삽입 시 양쪽에 '' 붙여서 삽입
2) ${변수명 | 필드명} : SQL에 삽입 시 양쪽에 '' 붙이지 않고 삽입
-> ${} 언제 사용할까?
1) 삽입할 값이 숫자인 경우 (숫자 리터럴에 '' 없음)
2) SQL문 자체가 변해야 하는 경우에 사용
ex) SELECT MEMBER_NAME, ${key} FROM TB_MEMBER
- ${key} == ENROLL_DATE인 경우
SELECT MEMBER_NAME, ENROLL_DATE FROM TB_MEMBER
- ${Key} == MEMBER_ADDRESS인 경우
SELECT MEMBER_NAME, MEMBER_ADDRESS FROM TB_MEMBER
-->
<!-- 할 일 추가 -->
<insert id="addTodo" parameterType="Todo">
INSERT INTO TB_TODO
VALUES(SEQ_TODO_NO.NEXTVAL, #{todoTitle}, #{todoContent}, DEFAULT, DEFAULT )
</insert>
<form action="/todo/add" method="post">
<h4>할 일 추가</h4>
<div>
제목 : <input type="text" name="todoTitle">
</div>
<div>
<textarea name="todoContent" cols="50" rows="5" placeholder="상세 내용"></textarea>
</div>
<button>추가하기</button>
</form>
<!-- footer.html 조각을 해당 위치에 대입
조각의 경로는 forward 하듯이 접두사, 접미사 생략하고 작성 -->
<th:block th:replace="~{common/footer}"></th:block>
<!-- 스크립트 태그의 경우 바디 태그 하단에 작성하기 때문에 footer-->
<!-- message 값 alert 띄워줄 script 작성 -->
<script th:inline="javascript">
const message = /*[[${message}]]*/ "전달받은 메세지";
// th:inline="javascript"
// 전달받은 message가 없으면 null
if(message != null) {
alert(message);
}
</script>


