Upload
others
View
35
Download
0
Embed Size (px)
Citation preview
Spring Data JPA, Spring Boot, Oracle, AngulerJS적용 게시판 실습
http://ojc.asia, http://ojcedu.com
게시판 리스트보기
Spring JDBC 또는 MyBatis로 만들 때 보다 쉽고 빠르게 작성할 수 있다.
스프링 컨트롤러는 RestController를 적용 했으며, 뷰 페이지에 Bootstrap 및 AngulerJS 적용
했다.
프로젝트 전체 구조는 다음과 같다.
[BoardRepository.java]
기본적으로 제공하는 JpaRepository를 상속받아 만들면 된다.
JpaRepository는 PagingAndSortingRepository를 상속받았고 PagingAndSortingRepository는
CrudRepository(기본적인 CRUD 기능을 제공한다)를 상속 받았으며 다시 CrudRepository는
Repository 인터페이스를 상속받았다.
그러므로 JpaRepository는 모든 기능을 다 사용할 수 있고 추가적으로 영속성 컨텍스트에
flush하거나 엔티티를 배치로 삭제할 수 있다.
기본 기능만으로도 게시판 기능을 구현할 수 있으므로 JpaRepository를 상속 받자.
package jpa.board.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import jpa.board.model.Board;
public interface BoardRepository extends JpaRepository<Board, Integer> {
}
서비스쪽 클래스를 작성하자.
[BoardService.java]
package jpa.board.service;
import org.springframework.data.domain.Page;
import jpa.board.model.Board;
public interface BoardService {
//게시판 리스트 보기
public Page<Board> findAll(Integer curPage);
}
JpaRepository의 findAll() 메소드를 호출만 하면 되는데 페이징 기능을 구현하기 위해
PageRequest를 만들고 이를 findAll() 메소드의 인자로 넣어주면 된다.
Board 테이블에서 정렬한 칼럼이 두개 이상이므로 Sort 를 new하면서 Order를 필요한 만큼
생성해주면 되고 ASC는 오름차순, DESC는 내림차순, 그뒤의 문자열은 Board 엔티티의 속성
이다.
[BoardServiceImpl.java]
package jpa.board.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Service;
import jpa.board.model.Board;
import jpa.board.repository.BoardRepository;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
BoardRepository boardRepository;
@Override
//-----------------------------------------
// 게시판 리스트 보기, 한페이지에 3개씩
// curPage:요청하는 페이지, 첫페이지는 0
//-----------------------------------------
public Page<Board> findAll(Integer curPage) {
PageRequest pr = new PageRequest(curPage, 3,
new Sort(
new Order(Direction.DESC, "reply"),
new Order(Direction.ASC, "replystep")));
return boardRepository.findAll(pr);
}
}
컨트롤러를 작성하자.
[BoardController.java]
package jpa.board.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jpa.board.model.Board;
import jpa.board.service.BoardService;
@RestController
@RequestMapping("/board")
public class BoardController {
@Autowired
BoardService boardService;
//---------------------------------------------
// 루트요청(localhost:8080/board/)시 리스트 보기로
//---------------------------------------------
@RequestMapping(method = RequestMethod.GET)
public ModelAndView index() {
//jsp아래 board.jsp 호출
return new ModelAndView("board");
}
//---------------------------------------------
// 게시판 리스트 보기
//---------------------------------------------
@RequestMapping(value="/{curPage}", method = RequestMethod.GET)
public ResponseEntity<Page<Board>> list(Model model, Pageable pageable,
@PathVariable Integer curPage) {
Page<Board> page = boardService.findAll(curPage);
return new ResponseEntity<Page<Board>>(page, HttpStatus.OK);
}
}
src/main/webapp/js , src/main/webapp/jsp 폴더를 만들고 app.js, board_controller.js,
board_service.js, list.jsp를 만들자.
[src/main/webapp/js /app.js]
'use strict';
var App = angular.module('myBoard',[]);
[src/main/webapp/js /board_service.js]
'use strict';
App.factory('BoardService', ['$http', '$q', function($http, $q){
return {
list: function(curPage) {
return $http.get('http://localhost:8080/board/' + curPage)
.then(
function(response){
console.log("[service:list]server call suceeded.");
return response.data;
},
function(errResponse){
console.error('Error while fetching contents');
return $q.reject(errResponse);
}
);
}
};
}]);
[src/main/webapp/js /board_controller.js]
'use strict';
App.controller('BoardController', ['$scope', 'BoardService',
function($scope, BoardService) {
var self = this;
self.board={id:null,name:'',passwd:'',title:'',content:''};
self.page=[];
//리스트 보기
self.list = function(curPage){
BoardService.list(curPage)
.then(
function(data) {
self.page = data;
console.log("[controller:list]", self.page);
//alert("목록보기 성공!");
},
function(errResponse){
console.error('Error while fetching page...');
}
);
};
self.list(0);
}]);
[board.jsp]
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<title>Spring JPA 게시판</title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<style>
.name.ng-valid {
background-color: lightgreen;
}
.name.ng-dirty.ng-invalid-required {
background-color: red;
}
.name.ng-dirty.ng-invalid-maxlength {
background-color: yellow;
}
.passwd.ng-valid {
background-color: lightgreen;
}
.passwd.ng-dirty.ng-invalid-required {
background-color: red;
}
.passwd.ng-dirty.ng-invalid-maxlength {
background-color: yellow;
}
.title.ng-valid {
background-color: lightgreen;
}
.title.ng-dirty.ng-invalid-required {
background-color: red;
}
.title.ng-dirty.ng-invalid-maxlength {
background-color: yellow;
}
body, #mainWrapper {
height: 70%;
background-color: rgb(245, 245, 245);
}
body, .form-control {
font-size: 12px !important;
}
.floatRight {
float: right;
margin-right: 18px;
}
.has-error {
color: red;
}
.formcontainer {
background-color: #DAE8E8;
padding: 20px;
}
.tablecontainer {
padding-left: 20px;
}
.generic-container {
width: 80%;
margin-left: 20px;
margin-top: 20px;
margin-bottom: 20px;
padding: 20px;
background-color: #EAE7E7;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 0 30px black;
}
.custom-width {
width: 80px !important;
}
.pointer {
cursor: pointer;
}
</style>
<!-- For AngularJS -->
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"></script>
<script src="<c:url value='/js/app.js' />"></script>
<script src="<c:url value='/js/board_service.js' />"></script>
<script src="<c:url value='/js/board_controller.js' />"></script>
</head>
<body ng-app="myBoard" class="ng-cloak" ng-controller="BoardController as ctrl">
<div class="generic-container" ng-controller="BoardController as ctrl"
style="width: 800px;">
<div class="panel panel-default">
<div class="panel-heading">
<span class="lead">Spring Data JPA 게시판 글 쓰기 </span>
</div>
<div class="formcontainer">
<form ng-submit="ctrl.submit()" name="myForm" class="form-
horizontal">
<input type="hidden" ng-model="ctrl.board.id" />
<div class="row">
<div class="form-group col-md-6">
<label class="col-md-2 control-lable"
for="name">Name : </label>
<div class="col-md-7">
<input type="text" ng-
model="ctrl.board.name" id="name"
class="name form-
control input-sm"
placeholder="Enter
your Name" required
ng-maxlength="20"
/>
<div class="has-error" ng-
show="myForm.$dirty">
<span ng-
show="myForm.name.$error.required">This is a
required
field</span> <span ng-show="myForm.name.$error.maxlength">Maximum
length is
20</span> <span ng-show="myForm.name.$invalid">This
field is
invalid </span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="col-md-2 control-lable"
for="passwd">Password
:</label>
<div class="col-md-7">
<input type="password" ng-
model="ctrl.board.passwd" id="passwd"
class="passwd
form-control input-sm"
placeholder="Enter
your Password" required ng-maxlength="20" />
<div class="has-error" ng-
show="myForm.$dirty">
<span ng-
show="myForm.passwd.$error.required">This is a
required
field</span> <span ng-show="myForm.passwd.$error.maxlength">Maximum
length is
20</span> <span ng-show="myForm.passwd.$invalid">This
field is
invalid </span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="col-md-2 control-lable"
for="title">Title :</label>
<div class="col-md-7">
<input type="text" ng-
model="ctrl.board.title" id="title"
class="title form-
control input-sm"
placeholder="Enter
your Title" required />
<div class="has-error" ng-
show="myForm.$dirty">
<span ng-
show="myForm.title.$error.required">This is a
required
field</span> <span ng-show="myForm.title.$invalid">This
field is
invalid </span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="col-md-2 control-lable"
for="content">Contents
:</label>
<div class="col-md-7">
<textarea rows="4"
ng-
model="ctrl.board.content" id="content"
class="content
form-control input-sm"
placeholder="Enter
your Contents" required >
</textarea>
<div class="has-error" ng-
show="myForm.$dirty">
<span ng-
show="myForm.content.$error.required">This is a
required
field</span> <span ng-show="myForm.content.$invalid">This
field is
invalid </span>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-actions floatRight">
<input type="submit"
value="{{!ctrl.board.id ?
'Add' : 'Update'}}"
class="btn btn-primary btn-
sm" ng-disabled="myForm.$invalid">
<button type="button" ng-
click="ctrl.reset()"
class="btn btn-warning btn-
sm" ng-disabled="myForm.$pristine">Reset
Form</button>
</div>
</div>
</form>
</div>
</div>
<div class="panel panel-default" style="float: center;">
<div class="panel-heading">
<span class="lead">Spring Data JPA 게시판 리스트보기
</span>
</div>
<h5>
총 {{ctrl.page.totalElements}}</span>건
</h5>
<div class="tablecontainer">
<table width="600" border="1" align="left" class="table table-
hover">
<tr align="left">
<th align="center">순번</th>
<th align="center">글번호</th>
<th align="center">제목</th>
<th align="center">글쓴이</th>
<th align="center">등록일</th>
<th align="center">조회수</th>
<th align="center">조회/삭제</th>
</tr>
<tr data-ng-repeat="board in ctrl.page.content">
<td align="center"><span ng-
bind="{{$index+1}}"></span></td>
<td align="center"><span ng-
bind="board.id"></span></td>
<td align="left">
<!-- 레벨의 수만큼 글을 뒤로 민다
--> <span
ng-repeat="n in
[].constructor(board.replylevel) track by $index">
</span>
<span ng-bind="board.title"></span>
</td>
<td align="center"><span ng-
bind="board.name"></span></td>
<td align="center">{{board.regdate |
date:"yy.MM.dd hh:mm"}}</td>
<td align="center"><span ng-
bind="board.readcount"></span></td>
<td>
<button type="button" ng-
click="ctrl.edit(board.id)"
class="btn btn-success
custom-width">Edit</button>
<button type="button" ng-
click="ctrl.remove(board.id)"
class="btn btn-danger
custom-width">Remove</button>
</td>
</tr>
</tbody>
</table>
<div>
<!-- 게시판 페이징 -->
<ul class="pagination">
<li ng-class="{disabled: ctrl.page.number ===
0}"><a
ng-show="ctrl.page.number !== 0"
class="pointer"
ng-click="ctrl.list(ctrl.page.number-
1)">Prev</a>
<span ng-show="ctrl.page.number
=== 0">Prev</span></li>
<li ng-class="{disabled: ctrl.page.number ===
ctrl.page.totalPages - 1}">
<a ng-show="ctrl.page.number !==
ctrl.page.totalPages - 1"
class="pointer"
ng-
click="ctrl.list(ctrl.page.number+1)">Next</a>
<span ng-show="ctrl.page.number
=== ctrl.page.totalPages - 1">Next</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>
[JpaboardApplication.java] – 스프링 부트 메인
package jpa.board;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import jpa.board.model.Board;
import jpa.board.repository.BoardRepository;
@SpringBootApplication
public class JpaboardApplication implements CommandLineRunner{
@Autowired
BoardRepository boardRepository;
public static void main(String[] args) {
SpringApplication.run(JpaboardApplication.class, args);
}
public void run(String...args) {
//테스트를 위해 글9개 입력
Board b1 = new Board();
b1.setContent("JPA강좌 추천해 주세요1~");
b1.setName("홍길동1"); b1.setPasswd("1111");
b1.setReadcount(0); b1.setRegdate(new Date());
b1.setReply(1); b1.setReplylevel(0);
b1.setReplystep(0); b1.setTitle("강좌추천요망1");
boardRepository.save(b1);
Board b2 = new Board();
b2.setContent("JPA강좌 추천해 주세요2~");
b2.setName("홍길동2"); b2.setPasswd("1111");
b2.setReadcount(0); b2.setRegdate(new Date());
b2.setReply(2); b2.setReplylevel(0);
b2.setReplystep(0); b2.setTitle("강좌추천요망2");
boardRepository.save(b2);
Board b3 = new Board();
b3.setContent("JPA강좌 추천해 주세요3~");
b3.setName("홍길동3"); b3.setPasswd("1111");
b3.setReadcount(0); b3.setRegdate(new Date());
b3.setReply(3); b3.setReplylevel(0);
b3.setReplystep(0); b3.setTitle("강좌추천요망3");
boardRepository.save(b3);
Board b4 = new Board();
b4.setContent("OJC로 가세요...");
b4.setName("홍길동4"); b4.setPasswd("1111");
b4.setReadcount(0); b4.setRegdate(new Date());
b4.setReply(6); b4.setReplylevel(1);
b4.setReplystep(1); b4.setTitle("[답변]강좌추천요망6");
boardRepository.save(b4);
Board b5 = new Board();
b5.setContent("OJC로 가세요...");
b5.setName("홍길동5"); b5.setPasswd("1111");
b5.setReadcount(0); b5.setRegdate(new Date());
b5.setReply(2); b5.setReplylevel(1);
b5.setReplystep(1); b5.setTitle("[답변]강좌추천요망2");
boardRepository.save(b5);
Board b6 = new Board();
b6.setContent("JPA강좌 추천해 주세요6~");
b6.setName("홍길동6"); b6.setPasswd("1111");
b6.setReadcount(0); b6.setRegdate(new Date());
b6.setReply(6); b6.setReplylevel(0);
b6.setReplystep(0); b6.setTitle("강좌추천요망6");
boardRepository.save(b6);
Board b7 = new Board();
b7.setContent("JPA강좌 추천해 주세요7~");
b7.setName("홍길동7"); b7.setPasswd("1111");
b7.setReadcount(0); b7.setRegdate(new Date());
b7.setReply(7); b7.setReplylevel(0);
b7.setReplystep(0); b7.setTitle("강좌추천요망7");
boardRepository.save(b7);
Board b8 = new Board();
b8.setContent("OJC로 가세요...");
b8.setName("홍길동8"); b8.setPasswd("1111");
b8.setReadcount(0); b8.setRegdate(new Date());
b8.setReply(7); b8.setReplylevel(1);
b8.setReplystep(1); b8.setTitle("[답변]강좌추천요망7");
boardRepository.save(b8);
Board b9 = new Board();
b9.setContent("JPA강좌 추천해 주세요9~");
b9.setName("홍길동9"); b9.setPasswd("1111");
b9.setReadcount(0); b9.setRegdate(new Date());
b9.setReply(9); b9.setReplylevel(0);
b9.setReplystep(0); b9.setTitle("강좌추천요망9");
boardRepository.save(b9);
}
}
실행(http://localhost/board/) – 리스트 보기에는 페이지 기능이 적용되어 있다.