목차
현재 내부 프로젝트 진행하고 있는 것 중 아래와 같은 요구사항이 있어서 구현을 했고 이를 기록한다.
현재 개발하며 난이도에 상관없이 기록을 하고 있는데 이는 나중에 분명 나의 좋은 자산이 될거라 믿는다.
누군가에겐 쉬운 일이 또 누군가에겐 좌절감을 줄 수도 있기에 좌절을 줄이고 자신감을 늘리기 위함이다.
처음부터 잘하는 사람도 물론 있을 수 있겠지만 그런사람이 그렇게 많지 않다고 본다.
처음엔 어려웠던 게 해보면 나중에 쉬운게 된다.
구글에 존재하면 안해봐서 어려운 것이지 못할 건 없다.
그리고 구글에 없어도 문제를 해결 할 수 있어야 프로다.(구글에 없으면 시간이 더 오래 걸리겠지)
난 아직 부족한 직장인 개발자이기에 평범한 개발자가 되기위해 끊임없이 기록하고 공부할 것이다.
요구사항은 관리자페이지에서 그리드로 데이터를 수정할 수 있어야 된다.
기존에 해왔던 데이터의 키값으로 수정 페이지에서 수정을 하는 방식이 아니라 그리드 내에서 인라인으로 수정을 하는 것이다. 예를 들면 아래와 같은 기능이다.
그리드의 행 선택 후 데이터를 수정하면 DB에 데이터가 반영이 되어야 한다.
해본적 없는 이 기능을 어떻게 구현하면 될까?여러 고민 끝에 아래의 프로세스 정의를 내렸다.
1. 화면단에서는 테이블의 td요소를 편집한다.
2. 저장 버튼을 누를 때 table tr을 루프 돌려 이를 저장한 다음 서버로 전송한다.
3. 서버에서 데이터를 받아서 여러번 DB 업데이트를 수행한다.
이 때 여러번 업데이트 수행하는 방법이 java단에서 업데이트를 여러번 수행하는 것과 mybatis에서 수행하는 것이 있는데 난 후자를 택했다.
이를 위해선 아래처럼 프로퍼티 수정이 필요하다.
#mariadb
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
#allowMultiQueries=true 이렇게 설정을 꼭 해야 한다.
spring.datasource.url=jdbc:log4jdbc:mariadb://localhost:3307/test?characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.username=test
spring.datasource.password=1234
mybatis.mapper-locations=mybatis-mapper/**/*.xml
contenteditable
구글링 결과 html의 contenteditable속성을 이용하면 입력태그(input, textarea 등)가 아닌 태그를 편집할 수 있도록 해준다. 이놈을 이용할 것이다.
아래에서 테스트 가능하다.
See the Pen Untitled by SangYeop Lee (@devLsy) on CodePen.
코드를 치기엔 귀찮지만 정확한 예제 테스트를 위해서 코드를 치겠다.
시간관계상 화면 디자인은 없다. 😅
프로젝트 생성 후 간단하게 board 테이블을 하나 만들었고 목록 조회, 등록, 다건 수정만 구현하겠다.
CREATE TABLE `study_board`(
`board_seq` bigint auto_increment,
`title` varchar (30),
`contents` varchar (100),
`name` varchar (30),
`reg_date` timestamp,
`update_date` timestamp,
primary key(board_seq)
);
BoardVo
package study.ex1.vo;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class BoardVo {
private Long boardSeq;
private String title;
private String contents;
private String name;
private String regDate;
private String updateDate;
}
mapper 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="study.ex1.mapper.BoardMapper">
<resultMap id="boardMap" type="study.ex1.vo.BoardVo">
<id column="board_seq" property="boardSeq"/>
<result column="title" property="title"/>
<result column="contents" property="contents"/>
<result column="name" property="name"/>
<result column="reg_date" property="regDate"/>
<result column="update_date" property="updateDate"/>
</resultMap>
<!-- 원래는 조회 시 *는 지양해야 하지만 예제니까 *로 함 -->
<select id="findContentsList" resultMap="boardMap">
SELECT * FROM study_board
</select>
<!-- 게시글 등록 -->
<insert id="insertContents" parameterType="study.ex1.vo.BoardVo">
INSERT INTO study_board
(
title
,contents
,name
,reg_date
,update_date
)
VALUES
(
#{title}
,#{contents}
,#{name}
,SYSDATE()
,SYSDATE()
)
</insert>
<!-- 다중 업데이트 -->
<update id="updateContentsList" parameterType="list">
<foreach collection="list" item="item" index="index" separator=";">
UPDATE study_board
SET title = #{item.title}
,contents = #{item.contents}
,name = #{item.name}
,update_date = SYSDATE()
WHERE board_seq = #{item.boardSeq}
</foreach>
</update>
</mapper>
mapper interface
package study.ex1.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import study.ex1.vo.BoardVo;
import java.util.List;
@Repository @Mapper
public interface BoardMapper {
//게시글 목록 조회
List<BoardVo> findContentsList();
//게시글 등록
void insertContents(BoardVo boardVo);
//게시글 리스트 업데이트
void updateContentsList(List<BoardVo> boardVoList);
}
service
package study.ex1.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.ex1.mapper.BoardMapper;
import study.ex1.vo.BoardVo;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class BoardService {
private final BoardMapper boardMapper;
/**
* 게시글 목록 조회
* @return
*/
public List<BoardVo> findContentsList() {
return boardMapper.findContentsList();
}
/**
* 게시글 등록
* @param boardVo
*/
@Transactional
public void insertContents(BoardVo boardVo) throws Exception{
boardMapper.insertContents(boardVo);
}
/**
* 게시글 다건 수정
* @param boardVoList
*/
@Transactional
public void updateContentsList(List<BoardVo> boardVoList) throws Exception{
boardMapper.updateContentsList(boardVoList);
}
}
jUnit 등록, 조회, 수정 테스트
package study.ex1.service;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import study.ex1.vo.BoardVo;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Slf4j
@Transactional
class BoardServiceTest {
@Autowired BoardService boardService;
@Test
@DisplayName("게시글등록")
@Commit
public void 게시글등록() throws Exception {
for (int i = 0; i < 11; i++) {
BoardVo boardVo = new BoardVo();
boardVo.setTitle("제목 : " + i);
boardVo.setContents("내용 : " + i);
boardVo.setName("아이언맨");
boardService.insertContents(boardVo);
}
}
@Test
@DisplayName("검색")
@Commit
public void 검색() throws Exception {
boardService.findContentsList();
}
@Test
@DisplayName("수정")
@Commit
public void 수정() throws Exception {
List<BoardVo> boardVoList = new ArrayList<>();
BoardVo boardVo1 = new BoardVo();
boardVo1.setBoardSeq(1L);
boardVo1.setTitle("천둥의 신");
boardVo1.setContents("천둥의 신 토르");
boardVo1.setName("thor");
BoardVo boardVo2 = new BoardVo();
boardVo2.setBoardSeq(2L);
boardVo2.setTitle("장난의 신");
boardVo2.setContents("장난의 신 로키");
boardVo2.setName("loki");
boardVoList.add(boardVo1);
boardVoList.add(boardVo2);
boardService.updateContentsList(boardVoList);
}
}
최종 DB결과
11건 데이터 등록 했고 그 중 1번과 2번의 데이터 정상적으로 수정됨
간단히 CRU 테스트 완료 했고 이제 화면단 작업
board_list.html(thymeleaf)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- bootstrap, jquery cdn load -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<title>게시글 목록</title>
</head>
<body>
<div class="container">
<h2>게시글 목록</h2>
<div>
<table class="table table-striped">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>내용</th>
<th>작성자</th>
<th>작성일</th>
<th>수정일</th>
</tr>
</thead>
<tbody>
<tr id="board_tr" th:each="item : ${list}">
<td th:text="${item.boardSeq}"></td>
<!-- 제목,내용,작성자는 편집 가능 옵션 부여-->
<td contenteditable="true" th:text="${item.title}"></td>
<td contenteditable="true" th:text="${item.contents}"></td>
<td contenteditable="true" th:text="${item.name}"></td>
<td th:text="${item.regDate}"></td>
<td th:text="${item.updateDate}"></td>
</tr>
</tbody>
</table>
<button type="button" id="updateBtn" class="btn btn-primary">수정하기</button>
</div>
</div> <!-- /.container -->
<script>
$(document).ready(function () {
// 업데이트 버튼 클릭 시 update_contents_list() 호출
$("#updateBtn").on("click", function () {
update_contents_list();
});
function update_contents_list() {
// 객체 담을 배열
let tableArr = new Array();
$("tr#board_tr").each(function (index, item) {
let td = $(this).children();
// 테이블 객체
let td_obj = {
boardSeq : td.eq(0).text(),
title: td.eq(1).text(),
contents : td.eq(2).text(),
name : td.eq(3).text()
};
// 배열에 객체를 저장
tableArr.push(td_obj);
});
// 비동기로 서버에 업데이트 호출
$.ajax({
type : "POST",
url : "/board/update",
data: JSON.stringify(tableArr),
dataType: "JSON",
contentType: "application/json; charset=UTF-8",
// 서버로 배열을 넘길 때는 반드시 아래 옵션을 붙이라고 하지만 내 경우는 JSON화한 스트링으로 넘기기 때문에 아래가 없어도 정상 작동한다.
// traditional: true,
success : function(data){
if(data.resultCode === "01")
alert("수정 되었습니다.");
location.reload();
},
error : function(XMLHttpRequest, textStatus, errorThrown){
alert("통신 실패.");
}
});
}
});
// End of $(document).ready...
</script>
</body>
</html>
화면에서 하는 일은 간단하다. study_board테이블에 저장된 데이터를 가져와서 뿌리고 제목, 내용, 작성자 편집이 가능하고 수정하기 버튼 클릭 시 번호, 제목, 내용, 작성자를 객체타입으로 배열에 담아서 JSON 변환 후 서버로 넘긴다.
화면에서 넘기는 데이터 형태는 아래와 같다.(배열안에 객체가 담겨 있다.)
[{"boardSeq":"1","title":"천둥의 신","contents":"망치의 신 토르","name":"thor"},{"boardSeq":"2","title":"장난의 신","contents":"장난의 신 로키","name":"loki"},{"boardSeq":"3","title":"제목 : 2","contents":"내용 : 2","name":"아이언맨"},{"boardSeq":"4","title":"제목 : 3","contents":"내용 : 3","name":"아이언맨"},{"boardSeq":"5","title":"제목 : 4","contents":"내용 : 4","name":"아이언맨"},{"boardSeq":"6","title":"제목 : 5","contents":"내용 : 5","name":"아이언맨"},{"boardSeq":"7","title":"제목 : 6","contents":"내용 : 6","name":"아이언맨"},{"boardSeq":"8","title":"제목 : 7","contents":"내용 : 7","name":"아이언맨"},{"boardSeq":"9","title":"제목 : 8","contents":"내용 : 8","name":"아이언맨"},{"boardSeq":"10","title":"제목 : 9","contents":"내용 : 9","name":"아이언맨"},{"boardSeq":"11","title":"제목 : 10","contents":"내용 : 10","name":"아이언맨"}]
서버에서 이를 @RequestBody로 받아서 업데이트를 수행한다.
JSON 타입의 데이터를 받을 땐 반드시 위 에노테이션을 선언해야 한다. 아니면 값이 null로 들어온다.
controller
package study.ex1.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import study.ex1.service.BoardService;
import study.ex1.vo.BoardVo;
import java.util.HashMap;
import java.util.List;
@Controller
@Slf4j
@RequiredArgsConstructor
@RequestMapping(value = "board")
public class BoardController {
private final BoardService boardService;
@GetMapping(value = "/list")
public String list(Model model) {
List<BoardVo> contentsList = boardService.findContentsList();
model.addAttribute("list", contentsList);
return "/board/board_list";
}
@PostMapping(value = "/update")
@ResponseBody
public HashMap<String, Object> updateContents(@RequestBody List<BoardVo> boardVoList) {
HashMap<String, Object> resultMap = new HashMap<>();
try {
boardService.updateContentsList(boardVoList);
resultMap.put("resultCode", "01");
resultMap.put("result", "success");
} catch (Exception e) {
log.info("에러났으니 잘 보고 고쳐~!");
log.info("exception : {}", e.getMessage());
}
return resultMap;
}
}
아래는 테스트 라이브 동영상이다.(테이블의 데이터를 수정한 내용이 DB에 잘 반영이 된다.)
막상 해보고 나면 정말 간단하지만 기록한다.
이 코드는 좋은 코드가 아니고 당연히 더 좋은 로직이 있을 것이다.
'IT > development' 카테고리의 다른 글
[spring] @RequestBody String type 받기 (0) | 2023.01.24 |
---|---|
[spring/mybatis] Rest API 계층구조(1:N) 객체 조회(feat .쉬운 예제) (0) | 2022.12.25 |
[티스토리] 코드블럭 클립보드에 복사 추가하기(feat. clipboard.js) (0) | 2022.12.17 |
[mybatis] mybatis multi update(다중 업데이트) (0) | 2022.12.16 |
[thymeleaf/javascript]thymeleaf값을 javascript에서 사용 (0) | 2022.12.08 |