728x90

이전 글들 중에서 작성한 코드 중 일부 수정, 추가하여 진행합니다. (Controller, .jsp, .js)

코드 : https://github.com/wmdwjddyd6/Board-Spring-MVC

 

GitHub - wmdwjddyd6/Board-Spring-MVC

Contribute to wmdwjddyd6/Board-Spring-MVC development by creating an account on GitHub.

github.com


 

1. View (write.jsp)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
	language="java"%>
<%@ page session="false"%>
<html>
<head>
<title>글쓰기</title>
</head>
<body>
	<h1>글쓰기 화면</h1>

	<form>
		제목 <br />
		<input size="120" type="text" id="title" name="title" value="${board.title}" /> <br /> <br />
		내용 <br />
		<textarea cols="100" rows="13" id="content" name="content"><c:out value="${board.content}"></c:out></textarea>
		<button type="button" id="write_btn">작성</button>
	</form>

	<a href="/">메인으로 돌아가기</a>

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
	<script src="/resources/js/write.js"></script>
	
</body>
</html>
  • [글쓰기 버튼을 클릭하여 접근하는 새 글 작성 & 수정 버튼을 클릭하여 접근하는 글 수정] 화면을 한 jsp파일을 사용하여 구현합니다.
  • 수정버튼을 클릭하여 접근할 경우 해당 글의 제목과 내용을 보여주기위해 기존 write.jsp 코드에서 input의 value를 추가했습니다. 

 

2. JavaScript (write.js)

var writeBtn;
var boardId = location.search.substring(9);

if(boardId < 1) {
	writeBtn = document.getElementById("write_btn").addEventListener("click", write);
} else {
	writeBtn = document.getElementById("write_btn").addEventListener("click", update);
}

function write() {
    console.log("작성 메서드 실행");
    var title = document.getElementById("title").value;
    var content = document.getElementById("content").value;
    var data = { "title": title, "content": content };

    $.ajax({
    	contentType: 'application/json',
        type: "POST",
        url: "/board",
        data: JSON.stringify(data),
        success: function (response) {
            console.log("성공 : " + response);
            location.href="list";
        },
        error: function (response) {
            console.log("실패 : " + response);
            console.log(response);
        }
    })
}

function update() {
    console.log("수정 메서드 실행");
    var title = document.getElementById("title").value;
    var content = document.getElementById("content").value;
    var data = { "title": title, "content": content };

    $.ajax({
    	contentType: 'application/json',
        type: "PATCH",
        url: "/board/" + boardId,
        data: JSON.stringify(data),
        
        success: function (response) {
            console.log("성공 : " + response);
            location.href="list";
        },
        error: function (response) {
            console.log("실패 : " + response);
            console.log(response);
        }
    })
}
  • 기존 write.js 코드를 수정, 추가했습니다.
  • location.search를 통해 url 파라미터를 받아 올 수 있습니다. 받아온 파라미터를 사용하기 위해 substring으로 사용할 부분만 잘라냈습니다. (예로 /board/write?boardId=27 의 경우 ?boardId=27을 추출합니다.)
  • 새 글 작성 & 기존 게시글 수정 유/무(url에 boardId 존재 유/무)에 따라 작성 버튼 이벤트를 분리했습니다.
  • contentType을 JSON형식에 맞추기 위해 application/json으로 수정합니다.
  • data 또한 JSON형태로 데이터를 보내기 위해 JSON.stringify()를 사용하여 가공합니다.
    • Controller에서 @RequestBody 어노테이션을 사용할 것인데 위와 같이 contentType과 data를 JSON에 맞게 작성하지 않으면 WARN 레벨의 에러를 반환합니다. (물론 기능 작동 또한 제대로 진행되지 않습니다.)

 

 

 

3. Controller

@Controller
public class BoardController {

	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);

	@Autowired
	private BoardService boardService;

	/*
	 * 게시글 작성 & 수정 화면 이동
	 */
	@RequestMapping(method = RequestMethod.GET, value = "/board/write")
	public String writeForm(Model model, @RequestParam(required = false) Long boardId) {
		if (boardId != null) {
			BoardDTO boardDTO = boardService.getPost(boardId);
			model.addAttribute("board", boardDTO);
		}

		return "board/write";
	}

	/*
	 * 게시글 수정
	 */
	@ResponseBody
	@RequestMapping(method = RequestMethod.PATCH, value = "/board/{boardId}")
	public int update(Model model, @RequestBody BoardDTO boardDTO, @PathVariable(value = "boardId") Long boardId) {
		logger.debug("{}번 게시글 수정 호출", boardId);

		int result = 0;
		result = boardService.update(boardDTO, boardId);
		logger.debug("result is {}", result);

		return result;
	}

}
  • 기존 게시글 작성 화면으로 이동하는 코드를 수정하고, 게시글을 수정하는 메서드를 추가합니다.
  • writeForm
    • 게시글 작성 시에는 새로운 글을 작성하는 것으로써, boardId가 존재하지 않습니다. 반면, 게시글 수정 시엔 해당 게시글을 조회하고 수정 버튼을 클릭하여 접근하기 때문에 기존 boardId가 존재하게 됩니다.
      • 즉, boardId가 존재한다면 수정을 목적으로 접근했기 때문에 수정하고자하는 데이터를 보여주기 위해 해당 게시글의 정보를 addAttribute해줍니다.
    • @RequestParam을 이용하여 url에 존재하는 boardId를 가져와서 어떤 게시글을 수정하는지 알 수 있도록 합니다.
  • update
    • 게시글의 정보를 일부 수정하는 것이기 때문에 PUT이 아닌 PATCH method를 사용합니다.
    • value & PathVariable : /board/27의 경우 27번 글의 수정을 의미합니다. 또한, @PathVariable 어노테이션을 통해 이 27 값을 변수로 사용할 수 있게 합니다.
    • @RequestBody를 사용하여 JSON 형태로 들어오는 값으로 BoardDTO를 초기화 해줍니다.
    • boardService의 update 함수에서 수정 로직을 수행할 수 있도록 호출합니다.

 

4. Service

@Service
public class BoardServiceImpl implements BoardService {

	private final BoardMapper boardMapper;
	
	@Autowired
	public BoardServiceImpl(BoardMapper boardMapper) {
		this.boardMapper = boardMapper;
	}

	@Override
	public int update(BoardDTO boardDTO, Long boardId) {
		boardDTO.setId(boardId);
		
		return boardMapper.updateBoard(boardDTO);
	}

}
  • Service구현체의 update메서드에서 수정하고자 하는 게시판ID를 setter 합니다.
  • DB가 정상적으로 이뤄지면 1을 반환합니다.

 

5. Mapper

public interface BoardMapper {

	// 게시글 수정
	int updateBoard(BoardDTO boardDTO);
	
}
<?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.spring.shoppingmall.mapper.BoardMapper">

	<sql id="boardColumns">
		id
		,title
		,content
		,delete_yn
		,create_date
		,views
		,type
	</sql>

	<!-- 게시글 수정 -->
	<update id="updateBoard" parameterType="com.spring.shoppingmall.model.BoardDTO">
		UPDATE 
			tb_board
		SET
			title = #{title},
			content = #{content}
		WHERE
			id = #{id}
	</update>
</mapper>
  • Update 구문을 통해 해당 id의 게시글과 제목을 수정합니다.

 

6. 결과

결과

 

728x90

이번 포스팅은 게시글 상세 글 확인을 위한 게시글 조회 기능을 구현합니다.

* 코드는 https://github.com/wmdwjddyd6/Board-Spring-MVC 에서 확인 가능합니다.

 

GitHub - wmdwjddyd6/Board-Spring-MVC

Contribute to wmdwjddyd6/Board-Spring-MVC development by creating an account on GitHub.

github.com


 

1. View (post.jsp)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
	language="java"%>
<%@ page session="false"%>
<html>
<head>
<title>게시글 상세 화면</title>
</head>
<body>
	<h1>게시글 상세 화면</h1>

	<h2>
		제목 :
		<c:out value="${board.title}" />
	</h2>
	내용
	<br />
	<textarea rows="13" style="background-color: white;" readonly><c:out value="${board.content}" /></textarea> <br /><br />
	<a href="/">메인으로 돌아가기</a>

</body>
</html>
  • 이전 글에서 만든 list.jsp에서 제목을 클릭하면 보여줄 화면입니다.
  • c:out으로 제목과 내용을 각각 보여줍니다.
  • readonly 속성을 통해 수정을 불가능하게 해요.

 

2. Controller

@Controller
public class BoardController {

	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);

	@Autowired
	private BoardService boardService;

	/*
	 * 게시글 상세 페이지 화면 이동
	 */
	@RequestMapping(method = RequestMethod.GET, value = "/board/post")
	public String detailPost(Model model, @RequestParam(required = false) Long boardId) {
		BoardDTO boardDTO = boardService.getPost(boardId);
		model.addAttribute("board", boardDTO);
		
		return "board/post";
	}
}
  • @RequestParam을 이용해서 url의 게시글 id를 받아옵니다.
    • 예) http://localhost:8080/board/post?boardId=24
  • 게시글 데이터를 담을 boardDTO를 선언하고 boardService를 통해 받아옵니다.
  • 받아온 데이터를 View에 보여주기 위해 model.addAttribute를 사용

 

3. Service

public interface BoardService {

	// 게시글 조회
	BoardDTO getPost(Long boardId);

}
@Service
public class BoardServiceImpl implements BoardService {

	private final BoardMapper boardMapper;
	
	@Autowired
	public BoardServiceImpl(BoardMapper boardMapper) {
		this.boardMapper = boardMapper;
	}

	@Override
	public BoardDTO getPost(Long boardId) {
		BoardDTO boardDTO = boardMapper.selectPost(boardId);
		
		return boardDTO;
	}

}
  • Service에선 Mapper로 id값을 넘겨주고, 게시글 데이터를 조회하여 가져옵니다.

 

4. Mapper

public interface BoardMapper {

	// 게시글 조회
	BoardDTO selectPost(Long boardId);
	
}
	<!-- 게시글 조회 -->
	<select id="selectPost" resultType="com.spring.shoppingmall.model.BoardDTO">
		SELECT
			<include refid="boardColumns" />
		FROM
			tb_board
		WHERE
			id = #{id}
	</select>
  • 각각 인터페이스와 xml 파일입니다.
  • Service에서 넘겨받은 boardId를 #{id}로 사용하고 있습니다.

 

5. 결과

글 작성하고

테스트 데이터 입력

 

작성된 글 확인하고 제목을 클릭하면

list.jsp

 

조회가 잘 되네요.

post.jsp

 

728x90

이번 글에선 저번 글에서 작성한 글들의 리스트를 불러오는 기능을 구현합니다.

작성한 글들은 DB에 저장돼있습니다. (DB : MySQL)

* 코드는 https://github.com/wmdwjddyd6/Board-Spring-MVC 에서 확인 가능합니다.

 

GitHub - wmdwjddyd6/Board-Spring-MVC

Contribute to wmdwjddyd6/Board-Spring-MVC development by creating an account on GitHub.

github.com

 


 

1. View (list.jsp)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
	language="java"%>
<%@ page session="false"%>
<html>
<head>
<title>목록</title>
</head>
<body>
	<h1>게시글 목록</h1>

	<table border="1">
		<caption>List</caption>
		<thead>
			<tr align="center" bgcolor="white">
				<th>No</th>
				<th>제목</th>
				<th>작성일</th>
				<th>조회수</th>
			</tr>
		</thead>

		<tbody>
			<c:forEach var="board" items="${boards}">
				<tr align="center" bgcolor="white">
					<td><c:out value="${board.id}" /></td>
					<td><a href="/board/post?boardId=${board.id}"><c:out value="${board.title}" /></a></td>
					<td><fmt:formatDate pattern="yyyy-MM-dd hh:mm"
							value="${board.createDate}" /></td>
					<td><c:out value="${board.views}" /></td>
				</tr>
			</c:forEach>
		</tbody>
	</table>

	<br />
	<a href="/">메인으로 돌아가기</a>

</body>
</html>
  • 첫 줄과 둘째 줄의 taglib을 선언함으로써 기본 jstl(forEach 등)과 fmt 태그를 사용할 수 있게 됩니다.
  • 데이터를 보여줄 테이블을 정의하고, Controller에서 전달받은 데이터(List)의 양만큼 반복해서 보여주기 위해 c:forEach를 사용합니다.
    • var : 임시로 사용할 변수명
    • items : Controller에서 전달받은 Collection 객체 (해당 글에선 List입니다.)
  • fmt:formatDate를 통해 화면에 보여줄 날짜 형식을 지정합니다.
  • * 추가
    • c:out value="${board.title}" : a태그로 감싸서 제목 클릭 시 해당 글로 이동하는 기능 구현을 위해 추가했습니다.
    • [3. 게시글 조회] 에서 상세 기능 구현

 

2. Controller

@Controller
public class BoardController {

	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	@Autowired
	private BoardService boardService;
	
	/*
	 * 게시글 목록 화면 이동
	 * */
	@RequestMapping(method = RequestMethod.GET, value = "/board/list")
	public String list(Model model) {
		List<BoardDTO> boards = boardService.getList();
		model.addAttribute("boards", boards);
		
		return "board/list";
	}
}
  • 조회 관련 기능으로 GET method를 사용합니다. /board/list url 요청 시 해당 메서드로 매핑됩니다.
  • boardService의 getList 함수를 통해 현재 DB에 저장된 모든 게시글 목록을 반환 받습니다.
  • model.addAttribute("View로 넘겨줄 변수명", View로 넘겨줄 변수) : 위 1번 View의 forEach의 items부분 입니다. 

 

3. Service

public interface BoardService {
	
	// 게시글 목록 확인
	List<BoardDTO> getList();

}
@Service
public class BoardServiceImpl implements BoardService {

	private final BoardMapper boardMapper;
	
	@Autowired
	public BoardServiceImpl(BoardMapper boardMapper) {
		this.boardMapper = boardMapper;
	}
	
	@Override
	public List<BoardDTO> getList() {
		List<BoardDTO> list = boardMapper.selectAll();
		
		return list;
	}
}
  • getList에서는 boardMapper를 통해 모든 게시글을 List에 담아 Controller로 반환합니다.

 

4. Mapper (DAO)

public interface BoardMapper {

	// 게시글 목록 조회
	List<BoardDTO> selectAll();
	
}
<?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.spring.shoppingmall.mapper.BoardMapper">

	<sql id="boardColumns">
		id
		,title
		,content
		,delete_yn
		,create_date
		,views
		,type
	</sql>

	<!-- 게시글 목록 확인 -->
	<select id="selectAll" resultType="com.spring.shoppingmall.model.BoardDTO">
		SELECT 
			<include refid="boardColumns" />
		FROM
			tb_board
		ORDER BY
			id desc
	</select>
</mapper>
  • 게시글 작성 시 마다 Auto-Increment되는 값인 id를 desc(내림차순)으로 조회함으로써, 제일 최근에 생성된 게시글을 제일 앞에 위치하도록 합니다.

 


 

결과

 

728x90

지난 포스팅 [프로젝트 셋팅]에 이어 게시글 생성에 대한 구현 방법을 정리합니다.

게시글 생성을 위해 생성할 파일들은 다음과 같습니다.

BoardDTO - BoardController - BoardService (interface) - BoardServiceImpl (구현체) - BoardMapper - View (+ JS)

* 코드는 https://github.com/wmdwjddyd6/Board-Spring-MVC 에서 확인 가능합니다.

 

GitHub - wmdwjddyd6/Board-Spring-MVC

Contribute to wmdwjddyd6/Board-Spring-MVC development by creating an account on GitHub.

github.com

 


 

0.A 게시판 테이블 생성

CREATE TABLE `shoppingmall`.`tb_board` (
  `id` int AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `delete_yn` varchar(1) DEFAULT 'N',
  `create_date` datetime DEFAULT NULL,
  `views` int DEFAULT '0',
  `type` varchar(20) DEFAULT 'board',
  PRIMARY KEY (`id`)
)
  • id (게시글 번호) : 게시글 고유 번호 (PK)
  • delete_yn : 게시글 삭제 여부 (N: 삭제 안 된 상태, Y: 삭제 된 상태)
  • create_date : 게시글 생성 날짜
  • views : 조회수
  • type : 게시글 타입 (후에 기능의 확장을 위해 넣음. 자유게시판, 비밀게시판, 공지사항 등 분리 수단)

 

0.B servlet-context.xml 설정

	<context:component-scan base-package="com.spring.shoppingmall.controller" />
	<context:component-scan base-package="com.spring.shoppingmall.service" />
  • 다음 코드를 추가하여 @Component 어노테이션이 붙은 코드를 읽을 수 있도록 합니다. (@Service, @Controller 등 @Component에 포함)

 

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
		<beans:property name="contentType" value="text/html; charset=UTF-8"/>
	</beans:bean>
	
	<context:component-scan base-package="com.spring.shoppingmall.controller" />
	<context:component-scan base-package="com.spring.shoppingmall.service" />
	
</beans:beans>
  • servlet-context.xml 전체 코드

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
		http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"
			value="com.mysql.cj.jdbc.Driver"></property>
		<property name="url"
			value="jdbc:mysql://localhost:3306/shoppingmall?serverTimezone=Asia/Seoul">
		</property>
		<property name="username" value="root"></property>
		<property name="password" value="asdf1234"></property>
	</bean>

	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
		<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
	</bean>

	<mybatis-spring:scan base-package="com.spring.shoppingmall.mapper"/>
	
</beans>
  • root-context.xml 전체 코드
  • 현재는 DataBase를 위한 설정 코드가 대부분 (전 포스팅에서 설정했습니다.)

 

0.C View (write.jsp)

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
	language="java"%>
<%@ page session="false"%>
<html>
<head>
<title>글쓰기</title>
</head>
<body>
	<h1>글쓰기 화면</h1>

	<form>
		제목 <br />
		<input size="120" type="text" id="title" name="title" /> <br /> <br />
		내용 <br />
		<textarea cols="100" rows="13" id="content" name="content"></textarea>
		<button type="button" id="write_btn">작성</button>
	</form>

	<a href="/">메인으로 돌아가기</a>

	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
	<script src="/resources/js/write.js"></script>
	
</body>
</html>
  • 제목과 내용을 담을 input 태그를 작성해줍니다.
  • button에 이벤트를 추가하기 위해 id를 지정해줍니다.
  • ajax사용을 위해 아래의 스크립트 코드를 삽입합니다.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

 

1. BoardDTO ( +DataBase)

@Data
@Getter
@Setter
public class BoardDTO {
	private Long id;
	private String title;
	private String content;
	private String deleteYn;
	private Timestamp createDate;
	private Long views;
	private String type;
}
  • Board테이블에 생성한 컬럼들을 변수로 선언해줍니다.

 

2.. BoardController

@Controller
public class BoardController {

	private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
	
	@Autowired
	private BoardService boardService;
	
	/*
	 * 게시글 작성 화면 이동
	 * */
	@RequestMapping(method = RequestMethod.GET, value = "/board/write")
	public String writeForm(Model model) {
		return "board/write";
	}
	
	/*
	 * 게시글 작성
	 */
	@ResponseBody
	@RequestMapping(method = RequestMethod.POST, value = "/board")
	public int write(Model model, @RequestBody BoardDTO boardDTO) {
		logger.debug("게시글 작성 호출");

		int result = 0;
		result = boardService.write(boardDTO);
		logger.debug("result is {}", result);

		return result;
	}
}
  • GET 요청은 게시글 작성 화면으로 이동하고, POST 요청은 게시글 등록 로직을 수행합니다.
  • boardService.write()를 통해 로직을 수행합니다.
  • @RequestBody로 JSON 데이터를 받아 BoardDTO객체를 생성 및 초기화합니다. (@RequestParam은 url을 통해 값을 받아오기 때문에 해당 작업에 사용하지 않음)

 

3. BoardService

public interface BoardService {
	
	// 게시글 작성
	int write(BoardDTO boardDTO);
}
@Service
public class BoardServiceImpl implements BoardService {

	@Autowired
	private BoardMapper boardMapper;
	
	@Override
	public int write(BoardDTO boardDTO) {
		boardDTO.setCreateDate(Timestamp.valueOf(LocalDateTime.now()));
		boardDTO.setType("board");
        
		return boardMapper.insertBoard(boardDTO);
	}
}
  • Controller에서 받은 boardDTO에 나머지 설정할 값을 setter를 통해 설정합니다.
  • MyBatis를 이용하여 Mysql에 데이터를 추가하기 위해 boardMapper.insertBoard()를 호출합니다.

 

4. Mapper

public interface BoardMapper {
	public int insertBoard(BoardDTO boardDTO);
}
  • 게시글 관련 Mapper를 생성하고, 게시글 생성 쿼리를 작성할 메서드를 정의합니다.

 

<?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.spring.shoppingmall.mapper.BoardMapper">

	<sql id="boardColumns">
		id
		,title
		,content
		,delete_yn
		,create_date
		,views
		,type
	</sql>


	<!--게시글 작성 -->
	<insert id="insertBoard" parameterType="com.spring.shoppingmall.model.BoardDTO">
		
		INSERT INTO
			tb_board (title, content, create_date, type)
		VALUES
			(#{title}, #{content}, #{createDate}, #{type})
			
	</insert>
</mapper>
  • 이전 포스팅에서 생성했던 BoardMapper.xml을 수정해서 사용합니다.
  • namespace랑 parameterType 등 패키지 명에 주의!
  • 해당 구문이 정상 실행되면 결과값으로 1이 나오고, 실패 시 0이 나옵니다.

 

5. js 코드 (ajax 요청)

var writeBtn = document.getElementById("write_btn").addEventListener("click", write);

function write() {
    var title = document.getElementById("title").value;
    var content = document.getElementById("content").value;
    var data = { "title": title, "content": content };

    $.ajax({
        contentType: 'application/json',
        type: "POST",
        url: "/board",
        data: JSON.stringify(data),
        success: function (response) {
            console.log("성공 : " + response);
            // location.href="list"; (성공 시 /board/list.jsp로 이동)
        },
        error: function (response) {
            console.log("실패 : " + response);
            console.log(response);
        }
    })
}
  • 작성 버튼 클릭 시 이벤트를 추가하기 위해 addEventListener를 사용합니다.
  • title과 content 값을 가져오고 ajax를 통해 /board/write url로 POST 요청을 보냅니다.
  • 서버와 통신에 성공(요청에 대한 응답이 제대로 왔을 때)하면 success, 실패하면 error의 response로 응답 값이 들어옵니다.
  • 서버의 Controller와 통신 성공 시 응답 받은 값은 1 또는 0일테니 이를 이용해서 수행할 동작을 정의 가능
  • location.href를 통해 게시글 등록 성공 시 이동 할 페이지를 지정 가능합니다.
  • JSON타입으로 데이터를 주고받기 위해 contentType은 json으로 맞춰주고, JSON.stringify를 사용하여 JSON 문자열로 변환하여 서버에 데이터를 전달합니다.

 

 


 

구현 중 겪은 에러 참고 ^-'

https://black-mint.tistory.com/74

 

[Spring] org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class

ajax를 이용하여 게시글 등록 구현 중 에러가 발생했습니다. 게시글 등록 요청 시 database에 입력은 잘 되지만 ajax요청의 응답으로 success구문이 실행돼야 하는데 아래의 error 부분 구문이 실행되던

black-mint.tistory.com

 

728x90

ajax를 이용하여 게시글 등록 구현 중 에러가 발생했습니다.

게시글 등록 요청 시 database에 입력은 잘 되지만 ajax요청의 응답으로 success구문이 실행돼야 하는데 아래의 error 부분 구문이 실행되던 것입니다.

				error: function(response) {
					console.log("실패 : " + response);
					console.log(response);
				}

 

  •  크롬 개발자 도구의 콘솔에서 나오는 정보는 500error와 [Object object]

 

스프링 프로젝트(서버)의 로그에는 WARN level로 아래 내용이 찍혀나왔습니다.

org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class java.lang.Integer

빨간색 내용을 검색 결과 크게 2가지 원인을 찾을 수 있었습니다.

 

원인

1. jackson-core 라이브러리 추가

  • 컨트롤러에서 리턴해주는 값을 json타입으로 변경하지 못해서 생기는 상황을 방지

2. getter 메서드 또는 @getter의 부재

 

해결

pom.xml 파일에 다음 의존성 추가

		<!-- jackson --> 
		<dependency> 
			<groupId>com.fasterxml.jackson.core</groupId> 
			<artifactId>jackson-core</artifactId>
			<version>2.9.2</version> 
		</dependency>
		<!-- jackson-core --> 
		<dependency> 
			<groupId>com.fasterxml.jackson.core</groupId> 
			<artifactId>jackson-databind</artifactId> 
			<version>2.9.2</version> 
		</dependency>

 

확실히 스프링부트를 사용할 때와 다르게 설정이 많이 필요한 것 같습니다..

'Web > Spring' 카테고리의 다른 글

[Spring MVC] 4. 게시글 수정  (0) 2022.03.31
[Spring MVC] 3. 게시글 조회  (0) 2022.03.29
[Spring MVC] 2. 게시글 목록 조회  (0) 2022.03.29
[Spring MVC] 1. 게시글 작성  (0) 2022.03.22
[Spring MVC] 0. 프로젝트 셋팅  (0) 2022.03.08
728x90

이번 프로젝트는 spring legacy project의 프로젝트 세팅을 기록하고자 합니다.

제 블로그 모든 글은 개인 공부 차원에서 진행하는 포스팅이기에 정답과 상이할 수 있습니다.

Tool : Eclipse IDE

개발 환경 : [Spring Framework, Maven, Mybatis, MySQL]

 

< 목차 >

1. 이클립스 다운로드

2.톰캣 설치

3. 프로젝트 생성

4. spring 및 java 버전 설정

5. 톰캣 연결

6. 프로젝트 실행

7. 인코딩

8. 롬복 설치

9. mysql 및 mybatis 설정

참고 : 프로젝트 구조

 

1. 이클립스 및 STS 다운로드

1.1 이클립스 다운로드

해당 페이지에서 이클립스를 다운로드합니다.

https://www.eclipse.org/downloads/

 

Eclipse Downloads | The Eclipse Foundation

The Eclipse Foundation - home to a global community, the Eclipse IDE, Jakarta EE and over 415 open source projects, including runtimes, tools and frameworks.

www.eclipse.org

1.2 STS 다운로드

  • 이클립스 실행 후 상단 help - Eclipse Marketplace 로 진입합니다.

 

  • spring legacy project를 생성하기 위해서는 2번 째에 존재하는 Spring Tools를 설치해줍니다. (이미 설치되어 있어서 Installed라고 표시돼있습니다.)
  • 설치 후 restart now를 통해 이클립스를 재시작합니다.

 

2. 톰캣(WAS) 설치

프로젝트에서 사용할 WAS인 톰캣을 설치합니다.

Spring Boot에서는 내장 톰캣을 담고 있으나, Spring legacy project에서는 따로 추가해줘야 합니다.

https://tomcat.apache.org/download-80.cgi

 

Apache Tomcat® - Apache Tomcat 8 Software Downloads

Welcome to the Apache Tomcat® 8.x software download page. This page provides download links for obtaining the latest versions of Tomcat 8.x software, as well as links to the archives of older releases. Unsure which version you need? Specification versions

tomcat.apache.org

  • 해당 홈페이지에서 원하는 버전의 톰캣을 설치합니다. (저는 Tomcat 9을 설치하여 진행했습니다.)
  • 설치한 파일의 zip을 풀어줍니다.

 

3. Spring 프로젝트 생성

  • 프로젝트 생성을 위해 이클립스 상단 File - New - Project로 진입합니다.

 

  • spring legacy project를 클릭하고 next

 

  • 프로젝트 명을 정하고 Spring MVC Project를 클릭하여 Next

 

  • 기본 패키지 이름을 입력하고 Finish

 

4. spring 및 java 버전 설정

  • properties태그에 자신이 사용하고자 하는 Java 및 spring 버전으로 수정해줍니다.
  • 프로젝트 우클릭 후 properties 진입

 

  • Java Compiler 및 Project Facets에서도 버전을 변경해줍니다.
  • 위 2가지 원하는 버전으로 수정 후 Apply 클릭

 

5. Tomcat 연결

  • 이클립스 하단의 Server 탭을 클릭합니다. (위치는 개인에 따라 다를 수 있습니다.)
  • No servers...를 클릭하고 서버를 추가해주겠습니다.

 

  • Apache - Tomcat (설치 버전)을 클릭하고 next

 

  • 압축 해제 후 톰캣 경로를 설정하고 next

 

  • 생성한 프로젝트를 Add 후 Finish

 

  • Servers 탭에 톰캣이 추가된 것을 확인할 수 있습니다.
  • 더블클릭해서 들어가줍니다.

 

  • Overview 창이 보이는 것을 확인할 수 있으며, 8080port로 설정돼있는 것 보니 서버를 실행하면 localhost:8080으로 접속 가능하겠네요.

 

  • 여기서 톰캣이 실행하는 루트 경로를 설정해주겠습니다.
  • /shoppingmall로 설정되어있어 서버를 시작하고 웹에 접속하게 되면 localhost:8080 가 아닌 localhost:8080/shoppingmall 접속하게 됩니다.
  • 하단 Overview탭에서 Modules탭으로 들어가서 생성한 패키지 목록을선택하고 Edit 클릭

 

  • /shoppingmall을 /로 변경합니다.

 

6. 프로젝트 실행

  • 패키지 우클릭 - Run as - Run on Sever 클릭해서 실행!

 

  • 실행 Tomcat 서버를 클릭하고 Finish

 

  • home.jsp가 화면에 출력되는 것을 확인할 수 있습니다. 그러나 한글이 깨지네요. 인코딩 설정을 해주겠습니다.

 

7. 인코딩 설정

  • 이클립스 상단의 Window - Preferences - General 에서 그림과 같이 Other의 설정을 UTF-8로 변경

 

  • Web안의 CSSFiles, HTML Files, JSP Files의 인코딩을 UTF-8로 변경 후 Apply and Close!

 

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter
		</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
  • src - main - webapp - WEB-INF - web.xml의 </servlet-mapping> 아래에 다음의 인코딩 코드를 추가합니다.

 

다시 서버를 Restart 후 localhost:8080으로 접속하면?

 

만약 그래도 한글이 깨진다면 jsp파일 상단에 다음 코드 유/무를 확인해주세요.

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" language="java" %>

 

** 추가 (이클립스 빌드 시 코드 자동 저장)

  • Preferences - General - Workspace - Build에서 Save auto...를 체크

 

  • Run/Debug - Launching 제일 위의 옵션을 Always로 변경

 

8. lombok 설치

maven repository인 https://mvnrepository.com/artifact/org.projectlombok/lombok에서 원하는 버전의 maven dependency를 복제하여 pom.xml에 붙여넣기 합니다.

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

  • 붙여넣기 후 저장하면 maven dependencies에 추가된 것을 확인할 수 있습니다.
  • lombok.jar파일을 Run As 해줍니다.

 

  • 저는 바로 이클립스 경로가 잡혔으나, 안잡히는 경우 Specify location을 클릭해서 경로 지정
  • Install / Update 클릭 후 완료되면 이클립스를 재시작해줍니다.

 

9. MySQL 및 MyBatis 설정

1. pom.xml 의존성 추가

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.21</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.2</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

 

2. root-context.xml 수정

  • root-context.xml파일을 열고 namespace로 접근해서 다음 namespace를 체크해줍니다.

 

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"
			value="com.mysql.cj.jdbc.Driver"></property>
		<property name="url"
			value="jdbc:mysql://localhost:3306/shoppingmall?serverTimezone=Asia/Seoul">
		</property>
		<property name="username" value="root"></property>
		<property name="password" value="asdf1234"></property>
	</bean>

	<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
		<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
		<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
	</bean>

	<mybatis-spring:scan base-package="com.spring.shoppingmall.mapper"/>
  • dataSource와 sqlSessionFactory를 빈으로 등록합니다.
  • dataSource의 username, password의 value값은 자신의 mysql의 id와 비밀번호를 입력해주시면 됩니다.
  • mybatis-sprint:scan에 mapper 인터페이스를 둘 패키지명을 입력합니다.

 

3. 테스트용 Mapper (interface 및 xml) 생성

package com.spring.shoppingmall.mapper;

public interface BoardMapper {
	public String getTime();
}
  • mybatis와 연동해서 사용할 mapper 인터페이스 입니다.
  • 여기서 작성한 메서드 명은 xml파일의 id와 동일하게 해야 합니다.

 

  • src - main - resources - mapper 경로에 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="com.spring.shoppingmall.mapper.BoardMapper">
	<select id="getTime" resultType="String">
		SELECT sysdate() FROM dual
	</select>
</mapper>
  • 테스트를 위한 SQL문을 작성했습니다.
  • <mapper namespace= ... : Mapper 인터페이스 패키지 경로를 모두 적어줍니다.
  • select의 id는 인터페이스에서 만들어준 메서드 이름과 동일하게 작성합니다.

 

4. 테스트할 컨트롤러 설정

	<context:component-scan base-package="com.spring.shoppingmall.controller" />
  • @Controller 어노테이션을 읽기 위해 servlet-context파일에 다음 코드를 추가합니다.
  • 각자 패키지명에 맞게 수정해주세요!

 

package com.spring.shoppingmall.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.spring.shoppingmall.mapper.BoardMapper;

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@Autowired
	private BoardMapper boardMapper;
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		logger.info("time : {}.", boardMapper.getTime());
		
		return "home";
	}
}
  • / 경로로 get 요청이 들어오면 home.jsp를 반환하는 컨트롤러입니다.
  • @Autowired를 사용해 boardMapper를 DI
  • boardMapper.getTime()을 통해 호출하여 확인해보겠습니다.

결과

 

5. 추가 (mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true" />
	</settings>
</configuration>
  • 해당 코드를 통해 CamelCase(카멜케이스)를 사용 가능하도록 합니다. (DTO와 MySQL의 변수명을 맞추기 위함)
  • 해당 작업을 해주지 않으면 개발 중 List<DTO>에 mybatis를 통해 Mysql의 값을 가져와 넣어줄 때 null로 들어갈 수 있습니다.
    • 예 : DTO의 필드 변수명은 deleteYn인데 MySQL의 컬럼명은 delete_yn인 경우

 

* 참고 (프로젝트 구조)

728x90

Spring Boot로 프로젝트를 진행하면서 DB테이블에 대해 제대로 정리한 글이 없었다.

ERD를 쉽게 그릴 수 있는 툴을 발견해서 직접 사용한 결과물을 기록한다.


ERD


ERD 툴 : https://aquerytool.com/

 

AQueryTool

{{source.erd_info.erd_name + '(Ver ' + erdVersion + ', ' + source.erd_info.db_type + ')'}}

aquerytool.com

 

728x90

게시글 작성 및 수정 작업 중 @Valid 를 사용하지 않고 자바스크립트를 통해 form 데이터를 검증하도록 하였다.

관련 프로젝트 : https://black-mint.tistory.com/70

 

[Spring Boot] 게시글 작성, 수정

이전 파일 업로드 글과 연관지어 게시글 작성의 구현을 마무리하고 이를 정리하겠습니다. 파일첨부에 대해서는 언급하지 않겠습니다! 이전 글 : https://black-mint.tistory.com/62?category=982467 [Spring Boot].

black-mint.tistory.com


1. html 코드

			<form action="#" th:action="@{/board/form}" th:object="${board}" method="post" enctype="multipart/form-data"
				onsubmit="return checkForm(this);">
				<div class="mb-3">
					<label for="title" class="form-label">제목</label>
					<input type="text" class="form-control" id="title" th:field="*{title}">
				</div>
				<div class="mb-3">
					<label for="content" class="form-label">내용</label>
					<textarea class="form-control" id="content" rows="13" th:field="*{content}"></textarea>
				</div>

				<div class="nav justify-content-end mb-5">
					<button id="submit" type="submit" class="me-2 btn btn-primary">작성</button>
				</div>
			</form>
  • onsubmit을 이용해 form 양식 제출 시 값을 검증한다.
  • checkForm(this) : this를 넣어줌으로써 자바스크립트에서 form 태그에 담긴 데이터를 컨트롤하는데 용이

2. 검증 script 코드

		// form submit 검증
		function checkForm(form) {
			if (form.title.value == "") {
				alert('제목을 입력하세요.');
				form.title.classList.add('is-invalid');
				form.title.focus();
				return false;
			}

			if (form.content.value == "") {
				alert('내용을 입력하세요.');
				form.title.classList.remove('is-invalid');
				form.content.classList.add('is-invalid');
				form.content.focus();
				return false;
			}
			form.content.classList.remove('is-invalid');
			return true;
		}
  • 파라미터로 넘어온 form(this)를 이용해 값을 확인한다.
  • form.[name].value : 제출된 [name] 이름을 가진 element의 값
  • classList.add : 해당 태그에 클래스를 추가한다.
  • classList.remove : 해당 태그의 클래스를 제거한다.
  • focus() : 해당 태그로 커서를 옮긴다.
  • return false의 경우 서버로 값이 넘어가지 않음.
728x90

이전 파일 업로드 글과 연관지어 게시글 작성의 구현을 마무리하고 이를 정리하겠습니다.

파일첨부에 대해서는 언급하지 않겠습니다!

이전 글 : https://black-mint.tistory.com/62?category=982467 

 

[Spring Boot] 파일 첨부 (게시글 이미지 첨부)

게시글 작성에서 파일 첨부하는 방법을 기록한다. (본 글은 이미지 첨부 방법을 다룸) 1. Mysql - file 테이블 CREATE TABLE `tb_file` ( `id` int NOT NULL AUTO_INCREMENT, `board_id` int NOT NULL, `original_..

black-mint.tistory.com

 


 

1. Mysql - 테이블

CREATE TABLE `tb_board` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `writer_id` int NOT NULL,
  `writer` varchar(20) NOT NULL,
  `delete_yn` varchar(1) DEFAULT 'N',
  `create_date` datetime DEFAULT NULL,
  `views` int DEFAULT '0',
  `type` varchar(20) DEFAULT 'board',
  PRIMARY KEY (`id`),
  KEY `writer_id` (`writer_id`),
  CONSTRAINT `tb_board_ibfk_1` FOREIGN KEY (`writer_id`) REFERENCES `tb_userinfo` (`id`) ON DELETE CASCADE
)

 

2. BoardDTO

@Data
public class Board {
    private Long id;
    private String title;
    private String content;
    private Long writerId;
    private String writer;
    private String deleteYN;
    private Timestamp createDate;
    private Long views;
    private String type;
}

 

3. 게시글 작성 Controller

// 게시글 신규 작성 폼 진입 & 기존 게시글 불러오기
@GetMapping("/form")
public String form(Model model, @RequestParam(required = false) Long boardId) {
    if (boardId == null) {
        model.addAttribute("board", new Board());
    } else {
        Board board = boardService.contentLoad(boardId, "board");
        model.addAttribute("board", board);
    }

    return "board/form";
}
  • 게시글 작성, 수정 화면으로 이동하는 GetMapping
  • 기존 게시글을 수정하는 경우 기존 게시글 내용을 추가해주기 위해 Service를 통해 정보를 얻어와 addAttribute를 진행

 

// 게시글 작성 & 수정
@PostMapping("/form")
public String boardSubmit(Board board, Principal principal,
                          @RequestParam(value = "files", required = false) List<MultipartFile> files,
                          @RequestParam(value = "boardId", required = false) Long boardId) throws IOException, SQLException {
    if (board.getTitle().length() < 1 || board.getContent().length() < 1 || (!CollectionUtils.isEmpty(files) && files.size() > 7)) {
        // 잘못된 입력값이 들어왔을 때 다시 해당 페이지로 로딩
        if(boardId != null) {
            return "redirect:/board/form?boardId=" + boardId;
        }
        return "redirect:/board/form";
    }

    String loginUsername = principal.getName();
    String type = "board";

    if (boardId == null) { // 새 글 작성
        Long newBoardId = boardService.save(board, loginUsername, type); // Insert

        // 첨부파일 있을 때
        if (!files.get(0).getOriginalFilename().isEmpty()) {
            for (int i = 0; i < files.size(); i++) {
                if (files.get(i).getContentType().contains("image/")) {
                    fileService.saveFile(files.get(i), newBoardId);
                } else {
                    System.out.println("이미지 타입이 아닙니다");
                }
            }
        }
    } else { // 기존 글 수정
        boardService.update(board, boardId, type); // Update
    }

    return "redirect:/board/list";
}
  • 이 전글에선 @Valid를 사용했지만 이번 글에서는 Javascript를 이용해 View에서 제목과 내용의 유무를 체크할 예정입니다. 물론 첫 줄의 검증 코드로 어느정도 검증을 진행! (Valid를 통해서 값을 검증하니 여러 오류가 발생했어요.. 이에 대한 대체 방안입니다,,,)
  • 작성(create), 수정(update)를 하나의 Controller와 View에서 처리하기 때문에 boardId(게시판 id)가 null인 상태와 null이 아닌 상태를 나눠서 처리했습니다.
  • type : 향후 다른 종류 게시판을 만들거나 공지사항 추가 등 서비스의 확장을 고려해 추가했습니다.

 

4. Service

// id를 이용해서 해당 글 수정
public Board contentLoad(Long id, String type) {
    Board board = new Board();
    board.setId(id);
    board.setType(type);
    try {
        board = boardRepository.findById(board);
    } catch (Exception e) {
        System.out.println("boardService.contentLoad() .. error : " + e.getMessage());
    } finally {
        return board;
    }
}
  • GetMapping에서 사용되는 contentLoad입니다.
// 글 등록
public Long save(Board board, String loginUsername, String type) {
    board.setCreateDate(Timestamp.valueOf(LocalDateTime.now()));

    try {
        Member member = memberService.getMember(loginUsername);

        board.setWriterId(member.getId());
        board.setWriter(member.getUsername());
        board.setType(type);

        boardRepository.insertBoard(board);
    } catch (Exception e) {
        System.out.println("boardService.save() .. error : " + e.getMessage());
    }
    return board.getId();
}
// 글 수정
public Long update(Board board, Long boardId, String type) {
    board.setId(boardId);
    board.setType(type);

    try {
        boardRepository.updateBoard(board);
    } catch (Exception e) {
        System.out.println("boardService.update() .. error : " + e.getMessage());
    }
    return board.getId();
}

 

5. Mapper.xml

<!--특정 아이디 게시글 조회-->
<select id="findById" resultType="Board">
    SELECT
        <include refid="boardColumns"/>
    FROM
        tb_board
    WHERE
        id = #{id} AND type = #{type}
</select>
  • type : board, notice 등으로 조회합니다. (해당 포스트 같은 경우 board입니다.)
  • 만약 공지사항을 조회한다면 notice가 들어갈 것입니다.

 

<!--게시글 작성-->
<insert id="insertBoard" parameterType="Board" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO
         tb_board (title, content, writer_id, writer, create_date, type)
    VALUES
        (#{title}, #{content}, #{writerId}, #{writer}, #{createDate}, #{type})
</insert>

<!--게시글 업데이트-->
<update id="updateBoard" parameterType="Board" useGeneratedKeys="true" keyProperty="id">
    UPDATE
        tb_board
    SET
        title = #{title}
        ,content = #{content}
    WHERE
        id = #{id} AND type = #{type}
</update>
  • useGenratedKeys, keyProperty : insert와 update 시 결과값으로 완료된 row의 pk(id)를 반환받습니다.

 

6. View

			<form action="#" th:action="@{/board/form}" th:object="${board}" method="post" enctype="multipart/form-data"
				onsubmit="return checkForm(this);">
				<input type="hidden" name="boardId" th:value="*{id}">
				<input type="hidden" th:field="*{writerId}">
				<div class="mb-3">
					<label for="title" class="form-label">제목</label>
					<input type="text" class="form-control" id="title" th:field="*{title}">
				</div>
				<div class="mb-3">
					<label for="content" class="form-label">내용</label>
					<textarea class="form-control" id="content" rows="13" th:field="*{content}"></textarea>
				</div>

				<div id="imageThumbnail">
				</div>

				<!-- button -->
				<th:block th:if="${param.boardId} == null">
					<div id="uploadForm">
						<div id="uploadElement">
							<input id="uploadInput" type="file" class="btn btn-outline-primary" name="files"
								accept="image/*" onchange="setThumbnail(event);" multiple /> <span
								style="font-size: small;"> * jpeg / png 타입의
								이미지는
								최대 7개까지
								등록해주세요.</span>
						</div>
						<a id="reset" class="mt-3 btn btn-danger" onclick="resetImg()">초기화</a>
					</div>
				</th:block>


				<div class="nav justify-content-end mb-5">
					<button id="submit" type="submit" class="me-2 btn btn-primary">작성</button>
					<a type="button" class="btn btn-primary" th:href="@{/board/list}">나가기</a>
				</div>
			</form>
  • th:object - form을 제출할 때 설정된 객체로 리턴됩니다. (이 경우 컨트롤러의 Board)
  • th:field - 해당 값(name과 value)을 th:object에서 설정된 객체의 내부 값으로 설정됩니다. (Board.setId, Board.setTitle...등)
  • onsubmit : form 태그의 값을 서버로 전송하기 전에 실행하여 유효성을 체크합니다. 

 

7. form검증을 위한 script (파일 처리부분은 이 전글 참고)

		// form submit 검증
		function checkForm(form) {
			if (form.title.value == "") {
				alert('제목을 입력하세요.');
				form.title.classList.add('is-invalid');
				form.title.focus();
				return false;
			}

			if (form.content.value == "") {
				alert('내용을 입력하세요.');
				form.title.classList.remove('is-invalid');
				form.content.classList.add('is-invalid');
				form.content.focus();
				return false;
			}
			form.content.classList.remove('is-invalid');
			return true;
		}
  • onsubmit에서 this를 통해 받았기 때문에 form.[name]으로 element를 지정가능!
  • 제목이나 내용이 없을 경우 경고 팝업을 띄워주고, 해당 input 또는 textarea의 class에 is-invalid를 추가해줍니다. (bootstrap을 이용해 빨간 input칸으로 변화)
  • false를 return 받는 경우는 서버로 form 값이 제출 안됩니다.

 

8. 시연

 

728x90
<a th:href="@{/board/post(boardId=${board.id})}" target='_blank' style="text-decoration:none; color:black;">제목</a>
  • target 속성으로 새창, 현재창 등 어디서 링크를 열지 설정가능.
  • default : "_self" (현재창에서 열기)
  • _blank : 새창에서 열기

'Web > HTML' 카테고리의 다른 글

[HTML] input 파일 입력 받기  (0) 2022.01.11

+ Recent posts