본문 바로가기
ICIA 수업일지

2021.10.08 수업일지(Spring Framework 연습)

by 주성씨 2021. 10. 8.

- Transaction

만약 DB의 데이터를 수정하는 도중에 예외가 발생한다면 이전 상태로 롤백하기 위해 사용되는 것이 트랜잭션이다. 트랜잭션은 더 이상 쪼갤 수 없는 최소 작업 단위를 의미한다. 그래서 트랜잭션은 commit으로 성공 하거나 rollback으로 실패 이후 취소되어야 한다.

 

- Spring에서 제공하는 Transaction을 이용해 코딩을 해보겠다.

- 트랜잭션을 위해서 라이브러리를 추가하겠다.

- setup 프로젝트에도 동일하게 적용시켜준다.

- /ex04/pom.xml

.....
		<!-- 트랜잭션 처리를 위한 라이브러리 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
.....

 

라이브러리 추가 확인

 

- /ex04/src/main/webapp/WEB-INF/spring/root-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:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="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.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="url" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/boarddb"></property>
		<property name="username" value="board"></property>
		<property name="password" value="pass"></property>
	</bean>
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <<<---
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations" value="classpath:/mapper/**/*.xml" />
	</bean>
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" <<<---
		destroy-method="clearCache">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
	</bean>
	<context:component-scan base-package="com.example.mapper"></context:component-scan>
	<context:component-scan base-package="com.example.service"></context:component-scan>
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	<tx:annotation-driven />
</beans>

 

check

 

그래프 확인

 

- /ex04/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml

.....

	<context:component-scan base-package="com.example.controller" />
	<context:component-scan base-package="com.example.service"/>
	
</beans:beans>

 

서버를 다시 구동하면 정상적으로 들어간 것이다.

 

- /ex04/src/main/java/com/example/domain/BoardVO.java

package com.example.domain;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class BoardVO {
	private int bno;
	private String title;
	private String content;
	private String writer;
	
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul")
	private Date regdate;
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul")
	private Date updatedate;
	
	private int viewcount;
    .....

 

- /ex04/src/main/resources/mapper/BoardMapper.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.mapper.BoardMapper">

	<!-- list 출력 -->
	<select id="list" resultType="com.example.domain.BoardVO">
		select * from tbl_board
		order by bno desc
		limit 0,5
	</select>

	<!-- 글 읽어오기 read -->
	<select id="read" resultType="com.example.domain.BoardVO">
		select * from tbl_board
		where bno=#{bno}
	</select>

	<!-- 조회수 update -->
	<update id="updateview">
		update tbl_board set viewcount=viewcount+1
		where bno=#{bno}
	</update>
    
</mapper>

 

- mapper interface를 만들겠다.

- /ex04/src/main/java/com/example/mapper/BoardMapper.java

package com.example.mapper;

import java.util.List;

import com.example.domain.BoardVO;

public interface BoardMapper {
	public List<BoardVO> list();
	public BoardVO read(int bno);
	public void updateview(int bno);
}

 

- 트랜잭션 작업을 위해서 인터페이스를 가지고 구현체를 만들겠다.

- /ex04/src/main/java/com/example/mapper/BoardMapperImpl.java

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.BoardVO;

// 구현체임을 나타낸다.
@Repository
public class BoardMapperImpl implements BoardMapper {
	// db 실행을 위한 세션 연결
	@Autowired
	SqlSession session;

	// namespace 지정
	String namespace = "com.example.mapper.BoardMapper";

	@Override
	public List<BoardVO> list() {
		return session.selectList(namespace + ".list"); // .xml 의 id와 동일하게
	}

	@Override
	public BoardVO read(int bno) {
		return session.selectOne(namespace + ".read", bno); // .xml 의 id와 동일하게
	}

	@Override
	public void updateview(int bno) {
		session.update(namespace + ".updateview", bno); // .xml 의 id와 동일하게
	}

}

 

- 테스트 해보겠다.

- /ex04/src/test/java/com/example/controller/MysqlTest.java

package com.example.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.example.mapper.BoardMapper;
import com.example.mapper.MysqlMapper;

@RunWith(SpringJUnit4ClassRunner.class) // 먼저 SpringJUnit4ClassRunner.class
										// import한다.
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class MysqlTest {

	@Autowired
	BoardMapper bmapper;
	
	// list get test
	@Test
	public void list(){
		bmapper.list();
	}

}

 

확인

 

- 이제 트랜잭션을 위한 service interface를 만들어 보겠다.

- /ex04/src/main/java/com/example/service/BoardService.java

package com.example.service;

import com.example.domain.BoardVO;

public interface BoardService {
	public BoardVO read(int bno);
}

 

- 구현체를 만들겠다.

- /ex04/src/main/java/com/example/service/BoardServiceImpl.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.domain.BoardVO;
import com.example.mapper.BoardMapper;

// 서비스 어노테이션
@Service
public class BoardServiceImpl implements BoardService {

	@Autowired
	BoardMapper bmapper;
	
	@Override
	public BoardVO read(int bno) {
		bmapper.updateview(bno);
		return bmapper.read(bno);
	}
}

 

- 테스트 해보겠다.

- /ex04/src/test/java/com/example/controller/MysqlTest.java

package com.example.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.example.domain.BoardVO;
import com.example.domain.Criteria;
import com.example.mapper.BoardMapper;
import com.example.mapper.MysqlMapper;
import com.example.service.BoardService;

@RunWith(SpringJUnit4ClassRunner.class) // 먼저 SpringJUnit4ClassRunner.class
										// import한다.
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class MysqlTest {

	@Autowired
	BoardMapper bmapper;
	@Autowired
	BoardService service;
	
	// relation list get test
	@Test
	public void list(){
		bmapper.list();
	}
	
	// tuple read test
//	@Test
//	public void read(){
//		bmapper.read(259);
//	}
	
	// update test
	@Test
	public void read(){
		service.read(262);
	}

}

 

확인

 

- 데이터 무결성을 위해서 트랜잭션 어노테이션을 추가해주겠다.

- /ex04/src/main/java/com/example/service/BoardServiceImpl.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.BoardVO;
import com.example.mapper.BoardMapper;

// 서비스 어노테이션
@Service
public class BoardServiceImpl implements BoardService {

	@Autowired
	BoardMapper bmapper;
	
	// 트랜잭션 어노테이션 둘중의 하나라도 오류가 나면 이전 상태로 롤백한다.
	@Transactional
	@Override
	public BoardVO read(int bno) {
		bmapper.updateview(bno);
		return bmapper.read(bno);
	}
}

 

- 이제 컨트롤러를 만들겠다.

- /ex04/src/main/java/com/example/controller/BoardController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.domain.BoardVO;
import com.example.mapper.BoardMapper;
import com.example.service.BoardService;

@Controller
//@RequestMapping("/board")
public class BoardController {
	// @Autowired 해당 변수 및 메서드에 스프링이 관리하는 Bean을 자동으로 매핑
	@Autowired
	BoardMapper bmapper;
	
	@Autowired
	BoardService bservice;
	
	// 2. 데이터 페이지에 출력
	@RequestMapping("/list")
	public String getList(){
		return "list";
	}
	
	// 1. data get
	@RequestMapping("/list.json")
	@ResponseBody // 데이터 출력을 위한 어노테이션
	public List<BoardVO> list(){
		return bmapper.list();
	}
}

 

확인

 

- 이제 리스트 출력 페이지를 만들겠다.

- 리스트의 row 클릭시 특정 데이터를 불러오는 read 페이지를 만들겠다.

- /ex04/src/main/webapp/WEB-INF/views/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="/resources/home.css" />
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
</head>
<body>
	<h1>[게시판 목록]</h1>
	<table id="tbl"></table>
	<script id="temp" type="text/x-handlebars-template">
	<tr class="title">
		<td width="50">No.</td>
		<td width="250">제목</td>
		<td width="100">작성자</td>
		<td width="200">작성일</td>
		<td width="200">수정일</td>
		<td width="50">조회수</td>
	</tr>
	{{#each .}}
		<tr class="row" onClick="location.href='/read?bno={{bno}}'">
			<td>{{bno}}</td>
			<td>{{title}}</td>
			<td>{{writer}}</td>
			<td>{{regdate}}</td>
			<td>{{updatedate}}</td>
			<td>{{viewcount}}</td>
		</tr>
	{{/each}}
</script>
</body>
<script>
	getList();
	function getList(){
		$.ajax({
			type:'get',
			url:'/list.json',
			dataType:'json',
			success:function(data){
				var temp = Handlebars.compile($('#temp').html());
				$('#tbl').html(temp(data));
			}
		})
	}
</script>
</html>

 

- /ex04/src/main/java/com/example/controller/BoardController.java

.....
	// 3. 특정 row의 데이터 불러오기
	@RequestMapping("/read")
	public String getRead(int bno, Model model){
		model.addAttribute("vo", bservice.read(bno));
		return "read";
	}
.....

 

- /ex04/src/main/webapp/WEB-INF/views/read.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>게시글 정보</title>
<style>
h1 {
	text-align: center;
}

h4:nth-child(1) {
	text-align: left;
}

h4:nth-child(2) {
	text-align: right;
}
</style>
</head>
<body>
	<h1>[게시글 정보]</h1>
	<div>
		<h1>${vo.title }</h1>
		<h4>
			<f:formatDate value="${vo.regdate}" pattern="yyyy-MM-dd HH:mm:ss" />
		</h4>
		<h4>조회수 : ${vo.viewcount }</h4>
		<hr />
		<p>${vo.content }</p>
		<a href="/list">목록으로</a>
	</div>
</body>
</html>

 

확인

 

 

- 이제 댓글 갯수를 출력해보도록 하겠다.

- go to mysql

# 테이블에 컬럼 삽입
alter table tbl_board add replycount int default 0;

# 댓글 테이블 데이터 업데이터
update tbl_board set replycount=(select count(*) from tbl_reply where tbl_reply.bno = tbl_board.bno)
where bno>0;

# 확인
select * from tbl_board order by bno desc;

 

- /ex04/src/main/java/com/example/domain/BoardVO.java

.....
	private int replycount;
.....

 

- /ex04/src/main/webapp/WEB-INF/views/list.jsp

.....
	<script id="temp" type="text/x-handlebars-template">
	<tr class="title">
		<td width="50">No.</td>
		<td width="250">제목</td>
		<td width="100">작성자</td>
		<td width="200">작성일</td>
		<td width="200">수정일</td>
		<td width="50">조회수</td>
		<td width="50">댓글수</td> <<<---
	</tr>
	{{#each .}}
		<tr class="row" onClick="location.href='/read?bno={{bno}}'">
			<td>{{bno}}</td>
			<td>{{title}}</td>
			<td>{{writer}}</td>
			<td>{{regdate}}</td>
			<td>{{updatedate}}</td>
			<td>{{viewcount}}</td>
			<td>{{replycount}}</td> <<<---
		</tr>
	{{/each}}
</script>
.....

 

확인

 

- 이제 댓글 입력 댓글 삭제 작업을 해보도록 하겠다.

- /ex04/src/main/resources/mapper/ReplyMapper.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.mapper.ReplyMapper">
	<select id="list" resultType="com.example.domain.ReplyVO">
		select * from tbl_reply
		where bno=#{bno}
		order by rno desc
		limit 0,5
	</select>
</mapper>

 

- /ex04/src/main/java/com/example/domain/ReplyVO.java

package com.example.domain;

import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;

public class ReplyVO {
	private int rno;
	private int bno;
	private String reply;
	private String replyer;
	
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul")
	private Date replydate;
	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="Asia/Seoul")
	private Date updatedate;
    .... getter, setter, tostring

 

- 인터페이스를 만들겠다.

- /ex04/src/main/java/com/example/mapper/ReplyMapper.java

package com.example.mapper;

import java.util.List;

import com.example.domain.ReplyVO;

public interface ReplyMapper {
	public List<ReplyVO> list(int bno);
}

 

- /ex04/src/main/java/com/example/mapper/ReplyMapperImpl.java

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.ReplyVO;

@Repository
public class ReplyMapperImpl implements ReplyMapper {
	@Autowired
	SqlSession session;
	
	String namespace="com.example.mapper.ReplyMapper";
	
	@Override
	public List<ReplyVO> list(int bno) {
		return session.selectList(namespace+".list",bno);
	}

}

 

- 이제 컨트롤러를 만들겠다.

- /ex04/src/main/java/com/example/controller/ReplyController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.domain.ReplyVO;
import com.example.mapper.ReplyMapper;

@RestController
@RequestMapping("/reply")
public class ReplyController {
	@Autowired
	ReplyMapper mapper;

	// @Autowired

	@RequestMapping("/list.json")
	public List<ReplyVO> list(int bno) {
		return mapper.list(bno);
	}

}

 

확인

 

- 이제 리드 페이지에 출력하도록 하겠다.

- /ex04/src/main/webapp/WEB-INF/views/read.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
<title>게시글 정보</title>
<style>
h1 {
	text-align: center;
}

h4:nth-child(1) {
	text-align: left;
}

h4:nth-child(2) {
	text-align: right;
}

.box {
	text-align: left;
	margin-top: 10px;
	padding: 10px;
	border-bottom: 1px dotted gray;
}

.replyer {
	font-weight: bold;
	color: #396dc0;
	margin-bottom: 5px;
}

.replydate {
	text-align: right;
}

.replyBox {
	padding: 10px;
	width: 850px;
	margin: 0px auto;
	border: 1px dotted gray;
}
</style>
</head>
<body>
	<!-- 게시글 란 -->
	<h1>[게시글 정보]</h1>
	<div>
		<h1>${vo.title }</h1>
		<h4>
			<f:formatDate value="${vo.regdate}" pattern="yyyy-MM-dd HH:mm:ss" />
		</h4>
		<h4>조회수 : ${vo.viewcount }</h4>
		<hr />
		<p>${vo.content }</p>
		<a href="/list">목록으로</a>
	</div>
	<hr />
	<!-- 댓글 입력란 -->
	<div>
		<h3>댓글</h3>
		<h4>댓글수:${vo.replycount}</h4>
		<input type="text" id="txtReply" size=80 />
		<button id="btnInsert">등록</button>
	</div>
	<hr />
	<!-- 댓글 출력란 -->
	<div id="reply"></div>
	<script id="temp" type="text/x-handlebars-template">
	{{#each .}}
	<div class="box">
		<div class="replyer">
			<span>[{{rno}}]</span>
			<span>{{replyer}}</span>
		</div>
		<div class="reply">
			<span>{{reply}}</span>
		</div>
		<div class="replydate">
			<span>{{replydate}}</span>
			<a href="#">삭제</a>
		</div>
	</div>
	{{/each}}
	</script>
</body>
<script>
	var bno = "${vo.bno}"
	getList();
	// 댓글 가지고오기
	function getList() {
		$.ajax({
			type : 'get',
			url : '/reply/list.json',
			dataType : 'json',
			data : {
				"bno" : bno
			},
			success : function(data) {
				var temp = Handlebars.compile($('#temp').html());
				$("#reply").html(temp(data));
			}
		})
	}
</script>
</html>

 

확인

 

- 댓글 입력 삭제할 수 있도록 하겠다.

- /ex04/src/main/resources/mapper/ReplyMapper.xml

....
	<!-- 댓글 입력 -->
	<insert id="insert">
		insert into tbl_reply(bno,reply,replyer)
		values(#{bno},#{reply},'user03')
	</insert>
	
	<!-- 댓글 삭제 -->
	<delete id="delete">
		delete from tbl_reply
		where rno=#{rno}
	</delete>
	
</mapper>

 

- /ex04/src/main/java/com/example/mapper/ReplyMapper.java

.....
	public void insert(ReplyVO vo);
	public void delete(int rno);
}

 

- /ex04/src/main/java/com/example/mapper/ReplyMapperImpl.java

.....
	@Override
	public void insert(ReplyVO vo) {
		session.insert(namespace+".insert",vo);
		
	}

	@Override
	public void delete(int rno) {
		session.delete(namespace+".delete",rno);
		
	}
}

 

- 이제 컨트롤러

- /ex04/src/main/java/com/example/controller/ReplyController.java

.....
	// bno, reply 가지고감
	@RequestMapping("/insert")
	public void insert(ReplyVO vo) {
		mapper.insert(vo);
	}

	// rno 가지고감
	@RequestMapping("/delete")
	public void delete(int rno) {
		mapper.delete(rno);
	}

}

 

1. insert

- /ex04/src/main/webapp/WEB-INF/views/read.jsp

.....
	// 등록 버튼을 눌렀을때
	$('#btnInsert').on('click',function(){
		var reply = $("#txtReply").val();
		$.ajax({
			type:'get',
			url:'/reply/insert',
			data:{"bno":bno, "reply":reply},
			success:function(){
				alert("등록되었습니다.");
				// 댓글란 초기화
				$("#txtReply").val("");
				getList();
			}
		})
	})
</script>
</html>

 

insert 확인

 

2. delete

- /ex04/src/main/webapp/WEB-INF/views/read.jsp

	// 삭제 버튼을 눌렀을때
	$('#reply').on('click','.box a',function(e){
		e.preventDefault();
		var rno = $(this).attr("href");
		$.ajax({
			type:'get',
			url:'/reply/delete',
			data:{"rno":rno},
			success:function(){
				alert("삭제되었습니다.");
				getList();
			}
		})
	})
</script>
</html>

 

delete 확인

 

 

- 트랜잭션 연습을 다시 한번 해보겠다.

- 프로젝트 ex05를 setup 프로젝트를 복사해서 생성한다.

- ex04에서 BoardVO, ReplyVO를 가지고 온다.

- /ex05/src/main/resources/mapper/Board.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.mapper.BoardMapper">
	<select id="list" resultType="com.example.domain.BoardVO">
		select * from tbl_board
		order by bno desc
		limit 0,5
	</select>
</mapper>

 

- 인터페이스 생성한다.

- /ex05/src/main/java/com/example/mapper/BoardDAO.java

package com.example.mapper;

import java.util.List;

import com.example.domain.BoardVO;

// 인터페이스는 메서드의 틀만 잡아주는 역할을 한다.
public interface BoardDAO {
	public List<BoardVO> list();
}

 

- 틀(인터페이스)을 이용해 클래스를 만들어주도록 하겠다.

- /ex05/src/main/java/com/example/mapper/BoardDAOImpl.java

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.BoardVO;

@Repository
// add unimplements method
public class BoardDAOImpl implements BoardDAO {

	// 세션을 이용해서 SQL문을 실행한다.
	@Autowired
	SqlSession session;
	String namespace="com.example.mapper.BoardMapper";
	
	@Override
	public List<BoardVO> list() {
		return session.selectList(namespace+".list"); // namespace위치에 id가 list인
	}

}

 

- 테스트 해보겠다.

- /ex05/src/test/java/com/example/controller/MysqlTest.java

package com.example.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.example.mapper.BoardDAO;
import com.example.mapper.MysqlMapper;

@RunWith(SpringJUnit4ClassRunner.class) // 먼저 SpringJUnit4ClassRunner.class
										// import한다.
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class MysqlTest {
	// 자동으로 연결
	@Autowired
	private BoardDAO dao;

	// 연결해서 getTime을 테스트
	@Test
	public void getList() {
		dao.list();
	}
}

getlist 확인

 

- 특정 bno를 read 할 수 있도록 하겠다.

- /ex05/src/main/resources/mapper/Board.xml

.....
	<!-- 특정 데이터 READ -->
	<select id="read" resultType="com.example.domain.BoardVO">
		select * from tbl_board
		where bno=#{bno}
	</select>
</mapper>

 

-/ex05/src/main/java/com/example/mapper/BoardDAO.java

....

	public BoardVO read(int bno);
}

 

- /ex05/src/main/java/com/example/mapper/BoardDAOImpl.java

.....
	// add unimplements method(read)
	@Override
	public BoardVO read(int bno) {
		return session.selectOne(namespace+".read",bno);
	}

}

 

- /ex05/src/test/java/com/example/controller/MysqlTest.java

.....
	// 연결해서 read을 테스트
	@Test
	public void read() {
		dao.read(262);
	}
}

 

read 확인

 

- 이제 컨트롤러에서 실행해보도록 하겠다.

- Board 컨트롤러를 만들겠다.

- /ex05/src/main/java/com/example/controller/BoardController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.domain.BoardVO;
import com.example.mapper.BoardDAO;

@Controller
public class BoardController {
	@Autowired
	BoardDAO dao;
	
	@ResponseBody
	@RequestMapping("/list.json")
	public List<BoardVO> list(){
		return dao.list();
	}
}

확인

 

 

- 이제 페이지에 출력해보도록 하겠다.

- /ex05/src/main/java/com/example/controller/BoardController.java

.....
	// 2. 리스트 페이지 만들기
	@RequestMapping("/list")
	public String listPage(){
		return "list";
	}
.....

 

- 페이지를 만든다.

- /ex05/src/main/webapp/WEB-INF/views/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   <title>Insert title here</title>
   <script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
</head>
<body>
   <h1>[게시판목록]</h1>
   <table id="tbl" border=1></table>
   <script id="temp" type="text/x-handlebars-template">
      {{#each .}}
      <tr onClick="location.href='read?bno={{bno}}'">
          <td width=100>{{bno}}</td>
         <td width=300>{{title}}</td>
         <td width=100>{{writer}}</td>
         <td width=100>{{viewcount}}</td>
         <td width=100>{{replycount}}</td>
      </tr>
      {{/each}}
   </script>
</body>
<script>
   getList();
   function getList(){
      $.ajax({
         type: "get",
         url: "/list.json",
         dataType: "json",
         success: function(data){
            var temp=Handlebars.compile($("#temp").html());
            $("#tbl").html(temp(data));
         }
      });
   }
</script>
</html>

 

리스트 출력 확인

 

- 이제 특정 bno를 읽어 페이지에 보여줄 수 있도록 하겠다.

- /ex05/src/main/java/com/example/controller/BoardController.java

.....
	// 3. 리드 페이지 주소 지정
	@RequestMapping("/read")
	public String readPage(int bno, Model model) {
		model.addAttribute("vo", dao.read(bno));
		return "read";
	}
.....

확인

 

 

- 리드 페이지를 만들겠다.

- /ex05/src/main/webapp/WEB-INF/views/read.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
<title>게시글 정보</title>
</head>
<body>
	<h1>[게시글 정보]</h1>
	<div>
		<h3>${vo.title }</h3>
		<h4>조회수 : ${vo.viewcount }</h4>
		<h4>댓글수 : ${vo.replycount }</h4>
		<hr />
		<p>${vo.content }</p>
		<a href="/list">목록으로 이동</a>
	</div>
</body>
</html>

 

확인

 

- 이제 조회수 증가가 되도록 하겠다.

- /ex05/src/main/resources/mapper/Board.xml

.....
	<!-- 조회수가 증가하게 하는 update -->
	<update id="updateview">
		update tbl_board set viewcount=viewcount+1
		where bno=#{bno}
	</update>
	
</mapper>

 

- DAO에 update 틀을 만들겠다.

-/ex05/src/main/java/com/example/mapper/BoardDAO.java

....
	public void updateview(int bno);
}

 

- 메서드를 오버라이딩 하겠다.

- /ex05/src/main/java/com/example/mapper/BoardDAOImpl.java

.....
	// add unimplements method(viewcount update)
	@Override
	public void updateview(int bno) {
		session.update(namespace+".updateview",bno);		
	}

}

 

- 서비스 생성

- /ex05/src/main/java/com/example/service/BoardService.java

package com.example.service;

import com.example.domain.BoardVO;

public interface BoardService {
	public BoardVO read(int bno);
}

 

- 인터페이스 재정의 임플리먼트

-/ex05/src/main/java/com/example/service/BoardServiceImpl.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.BoardVO;
import com.example.mapper.BoardDAO;

@Service
public class BoardServiceImpl implements BoardService {
	@Autowired
	BoardDAO dao;

	@Transactional // 트랜잭션 설정
	@Override
	public BoardVO read(int bno) {
		dao.updateview(bno);
		return dao.read(bno);
	}

}

 

- 이제 컨트롤러에서 서비스의 메서드를 실행할 수 있도록 하겠다.

- /ex05/src/main/java/com/example/controller/BoardController.java

.....
@Controller
public class BoardController {
	@Autowired
	BoardDAO dao;
	@Autowired
	BoardService service;

	// 3. 리드 페이지 주소 지정
	@RequestMapping("/read")
	public String readPage(int bno, Model model) {
		model.addAttribute("vo", service.read(bno));
		return "read";
	}
    .....

 

조회수 증가 확인

 

 

- 댓글 목록 출력 댓글 입력 댓글 삭제를 마찬가지로 해보도록 하겠다.

 

/ex05/src/main/resources/mapper/ReplyMapper.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.mapper.ReplyMapper">
	<!-- 댓글 목록 가지고 오기 -->
	<select id="list" resultType="com.example.domain.ReplyVO">
		select * from tbl_reply
		where bno=#{bno}
		order by rno desc
		limit 0,5
	</select>
	
	<!-- 댓글 입력 insert -->
	<insert id="insert">
		insert into tbl_reply(bno,reply,replyer)
		values(#{bno},#{reply},'user02')
	</insert>
	
	<!-- 댓글 삭제 delete -->
	<delete id="delete">
		delete from tbl_reply
		where rno=#{rno}
	</delete>
    v
</mapper>

 

- 인터페이스 생성

/ex05/src/main/java/com/example/mapper/ReplyDAO.java

package com.example.mapper;

import java.util.List;

import com.example.domain.ReplyVO;

public interface ReplyDAO {
	public List<ReplyVO> list(int bno);
	public void insert(ReplyVO vo);
	public void delete(int rno);
}

 

- 인터페이스 오버리이딩 임플리먼트 클래스 생성

/ex05/src/main/java/com/example/mapper/ReplyDAOimpl.java

package com.example.mapper;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.example.domain.ReplyVO;

@Repository
public class ReplyDAOimpl implements ReplyDAO {
	@Autowired
	SqlSession session;
	BoardDAO dao;
	
	String namespace="com.example.mapper.ReplyMapper";
	
	@Override
	public List<ReplyVO> list(int bno) {
		return session.selectList(namespace+".list",bno);
	}

	@Override
	public void insert(ReplyVO vo) {
		session.insert(namespace+".insert",vo);
	}

	@Override
	public void delete(int rno) {
		session.delete(namespace+".delete",rno);
	}

}

 

- 컨트롤러 생성

/ex05/src/main/java/com/example/controller/ReplyController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.domain.ReplyVO;
import com.example.mapper.ReplyDAO;

@RestController
@RequestMapping("/reply")
public class ReplyController {
	@Autowired
	ReplyDAO dao;
	
	
	// 3. 댓글 delete
	@RequestMapping(value="/delete", method=RequestMethod.POST)
	public void delete(int rno){
		dao.delete(rno);
	}
	
	// 2. 댓글 insert
	@RequestMapping(value="/insert", method=RequestMethod.POST)
	public void insert(ReplyVO vo){
		dao.insert(vo);
	}
	
	// 1. json list get
	@RequestMapping("/list.json")
	public List<ReplyVO> list(int bno){
		return dao.list(bno);
	}
}

 

- 이제 read 페이지에 댓글을 출력해주도록 하겠다.

/ex05/src/main/webapp/WEB-INF/views/read.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/resources/home.css" />
<script src="http://code.jquery.com/jquery-3.1.1.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.js"></script>
<style>
h1 {
	text-align: center;
}

h4:nth-child(1) {
	text-align: left;
}

h4:nth-child(2) {
	text-align: right;
}

.box {
	text-align: left;
	margin-top: 10px;
	padding: 10px;
	border-bottom: 1px dotted gray;
}

.replyer {
	font-weight: bold;
	color: #396dc0;
	margin-bottom: 5px;
}

.replydate {
	text-align: right;
}

.replyBox {
	padding: 10px;
	width: 850px;
	margin: 0px auto;
	border: 1px dotted gray;
}
</style>
<title>게시글 정보</title>
</head>
<body>
	<h1>[게시글 정보]</h1>
	<!-- 글 정보 -->
	<div>
		<h3>${vo.title }</h3>
		<h4>조회수 : ${vo.viewcount }</h4>
		<hr />
		<p>${vo.content }</p>
		<a href="/list">목록으로 이동</a>
	</div>
	<hr />
	<!-- 댓글 입력란 -->
	<div>
		<h3>댓글</h3>
		<h4>댓글수:${vo.replycount}</h4>
		<input type="text" id="txtReply" size=80 />
		<button id="btnInsert">등록</button>
	</div>
	<hr />
	<!-- 댓글 출력란 -->
	<div id="reply"></div>
	<script id="temp" type="text/x-handlebars-template">
	{{#each .}}
	<div class="box">
		<div class="replyer">
			<span>[{{rno}}]</span>
			<span>{{replyer}}</span>
		</div>
		<div class="reply">
			<span>{{reply}}</span>
		</div>
		<div class="replydate">
			<span>{{replydate}}</span>
			<a href="{{rno}}">삭제</a>
		</div>
	</div>
	{{/each}}
	</script>
</body>
<script>
	var bno = "${vo.bno}"
	getList();

	// 댓글 가지고오기
	function getList() {
		$.ajax({
			type : 'get',
			url : '/reply/list.json',
			dataType : 'json',
			data : {
				"bno" : bno
			},
			success : function(data) {
				var temp = Handlebars.compile($('#temp').html());
				$("#reply").html(temp(data));
			}
		})
	}

	// 등록 버튼을 눌렀을때
	$('#btnInsert').on('click', function() {
		var reply = $("#txtReply").val();
		$.ajax({
			type : 'post',
			url : '/reply/insert',
			data : {
				"bno" : bno,
				"reply" : reply
			},
			success : function() {
				alert("등록되었습니다.");
				// 댓글란 초기화
				$("#txtReply").val("");
				getList();
			}
		})
	})

	// 삭제 버튼을 눌렀을때
	$('#reply').on('click', '.box a', function(e) {
		e.preventDefault();
		var rno = $(this).attr("href");
		$.ajax({
			type : 'post',
			url : '/reply/delete',
			data : {
				"rno" : rno
			},
			success : function() {
				alert("삭제되었습니다.");
				getList();
			}
		})
	})
</script>
</html>

 

댓글 등록 삭제 확인

 

 

- 이제 댓글수의 증감을 나타낼 수 있도록 하겠다.

1. insert시 증가

/ex05/src/main/resources/mapper/Board.xml

.....
	<!-- 댓글 수 증감 update -->
	<update id="updatereply">
		update tbl_board set replycount=replycount + #{amount}
		where bno = #{bno}
	</update>
	
</mapper>

 

/ex05/src/main/java/com/example/mapper/BoardDAO.java

.....
	public void updatereply(int bno, int amount);
}

 

/ex05/src/main/java/com/example/mapper/BoardDAOImpl.java

.....
	// add unimplements method(replycount update)
	@Override
	public void updatereply(int bno, int amount) {
		HashMap<String, Object> map = new HashMap<>();
		map.put("bno", bno);
		map.put("amount", amount);
		session.update(namespace + ".updatereply", map);
	}

}

 

/ex05/src/main/java/com/example/service/BoardService.java

package com.example.service;

import com.example.domain.ReplyVO;

public interface ReplyService {
	public void insert(ReplyVO vo);
	public void delete(int rno);
}

 

/ex05/src/main/java/com/example/service/ReplyServiceImpl.java

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.ReplyVO;
import com.example.mapper.BoardDAO;
import com.example.mapper.ReplyDAO;

@Service
public class ReplyServiceImpl implements ReplyService {
	@Autowired
	ReplyDAO rdao;

	@Autowired
	BoardDAO bdao;

	@Transactional
	@Override
	public void insert(ReplyVO vo) {
		rdao.insert(vo);
		bdao.updatereply(vo.getBno(), 1);
	}

	@Override
	public void delete(int rno) {
		rdao.delete(rno);

	}

}

 

/ex05/src/main/java/com/example/controller/ReplyController.java

package com.example.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.domain.BoardVO;
import com.example.domain.ReplyVO;
import com.example.mapper.BoardDAO;
import com.example.mapper.ReplyDAO;
import com.example.service.ReplyService;

@RestController
@RequestMapping("/reply")
public class ReplyController {
	@Autowired
	ReplyDAO dao;
	
	@Autowired
	ReplyService service;
	
	@Autowired
	BoardDAO bdao;
	
	// 3. 댓글 delete
	@RequestMapping(value="/delete", method=RequestMethod.POST)
	public void delete(int rno){
		service.delete(rno);
	}
	
	// 2. 댓글 insert
	@RequestMapping(value="/insert", method=RequestMethod.POST)
	public int insert(ReplyVO vo){
		service.insert(vo);
		// insert 후 변영된 댓글 수를
		BoardVO bvo = bdao.read(vo.getBno());
		// vo로 가지고와서 ajax에서 insert시 리턴해서 read에 반영하면 된다.
		return bvo.getReplycount();
	}
	
	// 1. json list get
	@RequestMapping("/list.json")
	public List<ReplyVO> list(int bno){
		return dao.list(bno);
	}
}

 

/ex05/src/main/webapp/WEB-INF/views/read.jsp

.....
	// 등록 버튼을 눌렀을때
	$('#btnInsert').on('click', function() {
		var reply = $("#txtReply").val();
		$.ajax({
			type : 'post',
			url : '/reply/insert',
			data : {
				"bno" : bno,
				"reply" : reply
			},
			success : function(data) {
				alert("등록되었습니다."+data);
				// 댓글란 초기화
				$("#txtReply").val("");
				// 리턴한 댓글수를 삽입한다.
				$("#replycount").html("댓글수:"+data);
				getList();
			}
		})
	})
.....

 

2. delete시 감소

- 삭제할 댓글의 bno를 가지고 오기위해서 read sql을 만든다.

/ex05/src/main/resources/mapper/ReplyMapper.xml

.....
	<!-- 삭제할 댓글을 불러오기 -->
	<select id="read" resultType="com.example.domain.ReplyVO">
		select * from tbl_reply
		where rno=#{rno}
	</select>
	
</mapper>

 

- read 메서드를 인터페이스에 정의한다.

/ex05/src/main/java/com/example/mapper/ReplyDAO.java

.....
	public ReplyVO read(int rno);
}

 

- 인터페이스에 정의한 read 메서드를 임플리먼트에 오버라이딩하여 sql을 실행한다.

/ex05/src/main/java/com/example/mapper/ReplyDAOimpl.java

.....
	@Override
	public ReplyVO read(int rno) {
		return session.selectOne(namespace+".read",rno);
	}

}

 

- sql문을 실행하여 가지고 온 데이터를 vo에 담아 해당 vo에 있는 bno값을 이용해 댓글수를 업데이트할 수 있게 하겠다. 단 트랜잭션 처리를 하여 작업 중 실패시 롤백하여 반영이 안되도록 하겠다.

/ex05/src/main/java/com/example/service/ReplyServiceImpl.java

.....
	@Transactional
	@Override
	public void delete(int rno) {
		ReplyVO vo = rdao.read(rno);
		//System.out.println(vo.toString());
		rdao.delete(rno);
		//System.out.println(vo.getBno());
		bdao.updatereply(vo.getBno(), -1);
	}

}

 

- 이제 컨트롤러에서 기존 dao가 아닌 service를 이용하여 댓글 삭제 시 댓글 갯수가 업데이트 되도록 하겠다. 댓글이 삭되면 삭제된 게시글의 댓글 수를 불러와 리턴하겠다.

/ex05/src/main/java/com/example/controller/ReplyController.java

.....
	@Autowired
	ReplyService service;
	
	@Autowired
	BoardDAO bdao;
	
	// 3. 댓글 delete
	@RequestMapping(value="/delete", method=RequestMethod.POST)
	public int delete(int rno){
		ReplyVO bvo = dao.read(rno);
		service.delete(rno);
		BoardVO vo = bdao.read(bvo.getBno());
		return vo.getReplycount();
	}
.....

 

- 리턴된 값을 insert와 마찬가지로 read page 댓글 수에 적용되도록 하겠다.

/ex05/src/main/webapp/WEB-INF/views/read.jsp

.....
	// 삭제 버튼을 눌렀을때
	$('#reply').on('click', '.box a', function(e) {
		e.preventDefault();
		var rno = $(this).attr("href");
		$.ajax({
			type : 'post',
			url : '/reply/delete',
			data : {
				"rno" : rno
			},
			success : function(data) {
				alert("삭제되었습니다."+data);
				$("#replycount").html("댓글수:"+data);
				getList();
			}
		})
	})
</script>
</html>

 

확인