romworld

Spring 04 - 도서CRUD(3) - select문 , detail 페이지 본문

Spring

Spring 04 - 도서CRUD(3) - select문 , detail 페이지

inderrom 2023. 1. 20. 16:58

   SQL문 쿼리

SELECT BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT
FROM BOOK
WHERE BOOK_ID = 2;

                               


   

패키지 구조

                                                                                                                                                         

 

<book_SQL.xml>

 

insert문 아래 select문 추가

  	<!-- p.71 -->
  	<!-- 책 상세보기 
  		select 태그는 조회(select) 쿼리를 실행하기 위한 mybatis 태그
  		드루와 : parameterType(book_SQL.xml로 드루와) - bookVO 타입
  		가즈아 : resultType(BookDao로 가즈아) - bookVO 타입
  		
  		(전) 	bookVO{bookId:2,title:롬이야기,category:소설price:10000,insertDate:null
					content:null}
		(후)		bookVO{bookId:2,title:롬이야기,category:소설price:10000,insertDate:23/01/20
					content:롬이의 모험 이야기}
  	 -->
  	<select id="detail" parameterType="bookVO" resultType="bookVO">
  		SELECT BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT
		FROM BOOK
		WHERE BOOK_ID = #{bookId}
  	</select>

여기에서 왜 컬럼명 표기법이 다른데 적용이 잘 되어지냐면

 

mybatisAlias.xml을 보면 알 수 있다.

 

<mybatisAlias.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>
   <!-- 
   [마이바티스] 스프링에서 "_"를 사용한 컬럼명을 사용 시(BOOK 테이블의 BOOK_ID)
   카멜케이스로 읽어줌(bookId)
   ex) 테이블 컬러명이 member_id인 경우 jsp화면단에서 이 값을 사용 시 memberId로 사용
   -->
   <settings>
      <setting name="mapUnderscoreToCamelCase" value="true"/>
   </settings>
   <!-- 자주 사용하는 타입의 별칭을 세팅 -->
   <typeAliases>
      <typeAlias type="kr.or.ddit.vo.BookVO" alias="bookVO" />      
   </typeAliases>
</configuration>

<BookDao.java>

상세보기 메소드 추가 해준다.

// 책 상세보기
	public BookVO detail(BookVO bookVO) {
		// 쿼리를 실행해주는 객체?(힌트 : root_contxt.xml)
		// selectOne() 메소드 : 1행을 가져올 때 사용 
		// selectList() 메소드 : 결과 집합 목록 반환(다중행)
		// 결과 행 수가 0이면? null을 반환
		// 결과 행 수가 2 이상이면? TooManyResultException 예외 발생
		// .selectOne("namespace명.id명", 파라미터)
		return this.sqlSessionTemplate.selectOne("book.detail", bookVO);
	}

 

package kr.or.ddit.dao;

import java.lang.reflect.Parameter;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import kr.or.ddit.vo.BookVO;
import lombok.extern.slf4j.Slf4j;

/*
매퍼XML(book_SQL.xml)을 실행시키는 DAO(Data Access Object) 클래스
Repository 어노테이션 : 데이터에 접근하는 클래스
=> 스프링이 데이터를 관리하는 클래스라고 인지하여 자바빈 객체로 등록하여 관리함
*/
@Slf4j
@Repository
public class BookDao {
	
	/* DI(Dependency Injection) : 의존성 주입
	new 키워드를 통해 개발자가 직접 생성하지 않고, 
	스프링이 미리 만들어 놓은(톰켓서버 실행 시 스프링이 미리 객체를 인스턴스화 해놓음)
	sqlSessionTemplate 타입 객체를 BookDao 객체에 주입함
	
	IoC(Inversion of Control) : 제어의 역전
	*/
	
	//sqlSessionTemplate : 모더나/ BookDao : 새롬이 -> 모더나를 새롬이 팔에 주입
	/*
	root-context.xml <bean id="sqlSessionTemplate"...
		데이터베이스에 개별적으로 쿼리를 실행시키는 객체
		이 객체를 통해 query를 실행함
	*/
	@Autowired
	SqlSessionTemplate sqlSessionTemplate;
	
	// 도서 테이블(Book)에 입력
	//<insert id="createPost" parameterType="bookVO"> insert id와 메서드 명을 같게 설정
	public int createPost(BookVO bookVO) {
		// .insert("namespace값.id값",파라미터)
		// book_SQL.xml 파일의 namespace가 book이고, id가 insert인
		// 태그를 찾아 그 안에 들어있는 sql을 실행함
		// this는 클래스를 말함 ->BookDao
		// this는 생략 가능 하나 만약 BookDao에도 변수를 선언하고 createPost 파라미터에도 같은 변수가
		// 선언되어 있으면 구분을 해주기 위해 this를 써줘야 한다.
		
		log.info("bookVO :" + bookVO.toString());
		
		// insert,update,delete는 반영된 행수가 return됨
		// insert성공 : 1이상. 실패면 0
		
		int result = this.sqlSessionTemplate.insert("book.createPost",bookVO);
		int bookId = 0;
		
		if(result >0) { // insert 성공
			bookId = bookVO.getBookId(); // 0? 1(1 증가된 값이 들어있음. selectKey에 의해 생성됨)?
		}else { // insert 실패
			bookId = 0;
		}
		return bookId;
	}
	
	// 책 상세보기
	public BookVO detail(BookVO bookVO) {
		// 쿼리를 실행해주는 객체?(힌트 : root_contxt.xml)
		// selectOne() 메소드 : 1행을 가져올 때 사용 
		// selectList() 메소드 : 결과 집합 목록 반환(다중행)
		// 결과 행 수가 0이면? null을 반환
		// 결과 행 수가 2 이상이면? TooManyResultException 예외 발생
		// .selectOne("namespace명.id명", 파라미터)
		return this.sqlSessionTemplate.selectOne("book.detail", bookVO);
	}
}

 

<BookService.java>

package kr.or.ddit.service;

import kr.or.ddit.vo.BookVO;

// 서비스 interface : 비즈니스 로직(비지니스 레이어)
public interface BookService {
	// 메소드 시그니처
	public int createPost(BookVO bookVO);
	
	// 상세보기
	public BookVO detail(BookVO bookVO);
	
}

 

<BookServiceImpl.java>

package kr.or.ddit.service.impl;

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

import kr.or.ddit.dao.BookDao;
import kr.or.ddit.service.BookService;
import kr.or.ddit.vo.BookVO;

// 서비스 클래스 : 비즈니스 로직
// 스프링 MVC 구조에서 Controller와 DAO를 연결하는 역할

/*
스프링 프레임워크(디자인패턴 + 라이브러리)는 직접 클래스를 생성하는 것을 지양(싫음)하고,
인터페이스를 통해 접근하는 것을 권장하고 있기 때문에(확장성)
프링이는 인터페이스를 좋아해 ..
그래서 서비스 레이어는 인터페이스(BookService)와 클래스(BookServiceImpl)를 함께 사용함

Impl : implement의 약자
*/
// Service 어노테이션 : "프링아 이 클래스는 서비스 클래스야" 라고 알려줌.
//					 프링이가 자바빈으로 등록해줌
@Service
public class BookServiceImpl implements BookService {
	
	// 데이터베이스에 접근하기 위해 BookDao 인스턴스를 주입받자
	@Autowired
	BookDao bookDao;
	
	// 도서 테이블(Book)에 입력
	//<insert id="createPost" parameterType="bookVO"> insert id와 메서드 명을 같게 설정
	@Override
	public int createPost(BookVO bookVO) {
		//bookId를 리턴받음
		return bookDao.createPost(bookVO);
	}
	
	// 책 상세보기
	@Override
	public BookVO detail(BookVO bookVO) {
		return bookDao.detail(bookVO);
	}
}

 

<BookController.java>

ModelAndView는 나중에는 잘 안 쓰지만 배우기 위해 사용해본다.

상세보기로 데이터를 보내는 메서드 추가                                                                                                                                                                                                 

// 책 상세보기
	// 요청URI : /detail?bookId=2
	// 요청URI : /detail
	// 요청파라미터 : (HTTP파라미터=QueryString)bookId=2
	//bookVO{bookId:2,title:null,category:nullprice:0,insertDate:null
	//content:null}
	@RequestMapping(value="/detail",method=RequestMethod.GET)
	public ModelAndView detail(BookVO bookVO, ModelAndView mav) {
		log.info("bookVO : " + bookVO );
		
		BookVO data = bookService.detail(bookVO);
		log.info("data : " + data);
		
		// ModelAndView mav = new ModelAndView(); 를 해줘도 되고
		// 파라미터에 넣어서 변수를 호출해도 된다. (mav.addObject)
		// model : 데이터를 jsp로 넘겨줌
		mav.addObject("data", data);
		mav.addObject("bookId", data.getBookId()); // 기본키 값
		
		/* servlet-context.xml에서 매핑 되어서 주소를 book/detail로 생략해서 쓰기 가능
		 <beans:property name="prefix" value="/WEB-INF/views/" />
		 <beans:property name="suffix" value=".jsp" />
		 prefix + 뷰경로 +.jsp가 조립
		servlet-context에서  중복 되는 주소를 지운다.
		/WEB-INF/views/book/create.jsp
		 */
		
		//forwarding => detail.jsp를 해석/컴파일 => html로 크롬에게 리턴. 데이터 전달 기능
		mav.setViewName("book/detail");
		
		return mav;
	}

         

package kr.or.ddit.controller;

import java.util.Date;

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.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import kr.or.ddit.service.BookService;
import kr.or.ddit.vo.BookVO;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.dbcp2.BasicDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;

/*	Controller 어노테이션
스프링 프레임워크에게 "이 클래스는 웹 브라우저(클라이언트)의 요청(request)을
받아들이는 컨트롤러야" 라고 알려주는 것임.
스프링은 servlet-context.xml의 context:component-scan의 설정에 의해
이 클래스를 자바빈 캑체로 미리 등록 (메모리에 바인딩)
 */
// slf4j를 쓰면  lombok을 쓸 수 있다.
@Slf4j
@Controller	
public class BookController {
	// 서비스를 호출하기 위해 의존성 주입 (Dependency Injection -DI)
	@Autowired
	BookService bookService;
	
	// 요청 URI : /create
	// 방법 : get
	// 요청 - (매핑) - 메소드
	@RequestMapping(value="/create", method=RequestMethod.GET)
	public ModelAndView create() {
		/* ModelAndView
		   1) Model : Controller가 반환할 데이터(String, int ,List, Map, VO ..)를 담당.
		   2) View : 화면을 담당(뷰(View : jsp)의 경로).jsp파일의 위치
		*/
		ModelAndView mav = new ModelAndView();
		/*
		 <beans:property name="prefix" value="/WEB-INF/views/" />
		 <beans:property name="suffix" value=".jsp" />
		 prefix + 뷰경로 +.jsp가 조립
		servlet-context에서  중복 되는 주소를 지운다.
		/WEB-INF/views/book/create.jsp
		 */
		// forwarding
		mav.setViewName("book/create");
		return mav;
	}
	
	/*
	 요청URI : /cretae?title=롬이야기&category=소설&price=10000
	요청URL : /create
	요청파라미터 : {title=롬이야기&category=소설&price=10000}
	요청방식 : post
	
	bookVO{bookId:null,title:롬이야기,category:소설price:10000,insertDate:null
					content:null}
	private int bookId;
	private String title;
	private String category;
	private int price;
	private Date insertDate;
	private String content;
	*/
	@RequestMapping(value="/create", method=RequestMethod.POST)
	public ModelAndView createPost(BookVO bookVO, ModelAndView mav) {
		log.info("bookBO : " + bookVO.toString());
		//<selectKey resultType="int" order="BEFORE" keyProperty="bookId">
		//1 증가된 기본키 값을 받음
		int bookId = bookService.createPost(bookVO);
		
		log.info("bookId :" + bookId);
		
		if(bookId <1) { // 등록 실패
			// /create로 요청을 다시 함 => uri주소가 바뀜
			mav.setViewName("redirect:/create");
			
		}else {// 등록 성공 : 상세보기 페이지로 이동
			mav.setViewName("redirect:/detail?bookId=" + bookId);
		}
		
		return mav;
	}
	
	// 책 상세보기
	// 요청URI : /detail?bookId=2
	// 요청URI : /detail
	// 요청파라미터 : (HTTP파라미터=QueryString)bookId=2
	//bookVO{bookId:2,title:null,category:nullprice:0,insertDate:null
	//content:null}
	@RequestMapping(value="/detail",method=RequestMethod.GET)
	public ModelAndView detail(BookVO bookVO, ModelAndView mav) {
		log.info("bookVO : " + bookVO );
		
		BookVO data = bookService.detail(bookVO);
		log.info("data : " + data);
		
		// ModelAndView mav = new ModelAndView(); 를 해줘도 되고
		// 파라미터에 넣어서 변수를 호출해도 된다. (mav.addObject)
		// model : 데이터를 jsp로 넘겨줌
		mav.addObject("data", data);
		mav.addObject("bookId", data.getBookId()); // 기본키 값
		
		/* servlet-context.xml에서 매핑 되어서 주소를 book/detail로 생략해서 쓰기 가능
		 <beans:property name="prefix" value="/WEB-INF/views/" />
		 <beans:property name="suffix" value=".jsp" />
		 prefix + 뷰경로 +.jsp가 조립
		servlet-context에서  중복 되는 주소를 지운다.
		/WEB-INF/views/book/create.jsp
		 */
		
		//forwarding => detail.jsp를 해석/컴파일 => html로 크롬에게 리턴. 데이터 전달 기능
		mav.setViewName("book/detail");
		
		return mav;
	}
}

                                                                                                                                                                                                 


detail.jsp를 만들기 전 새로운 템플릿 적용 

 

새로운 jsp template 적용해보자!                                                                                                                                              

new jsp file (html5) 복사후

new template                                               

taglib, script를 추가해준다.

          

새로만든 템플릿을 선택해서 Finish 하면 jsp 생성됨


<detail.jsp>           

BookController에서 넣어놨던 data를 꺼내보자 ~ (JSTL 사용)
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/resources/js/jquery.3.6.0.js"></script>
<title>도서관리시스템</title>
</head>
<body>
<!-- mav.addObject("data", data); => bookVO 데이터 -->
${data}
<hr />
<!-- mav.addObject("bookId", data.getBookId());기본키 값(int 타입) -->
${bookId}
</body>
</html>

ckeditor와 jstl를 사용해서 상세보기 출력을 해보자 ~
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="/resources/js/jquery.3.6.0.js"></script>
<title>도서관리시스템</title>
<script type="text/javascript" src="/resources/ckeditor/ckeditor.js"></script>
</head>
<body>
<!-- mav.addObject("data", data); => bookVO 데이터 -->
<%-- ${data} --%>
<!-- <hr /> -->
<!-- mav.addObject("bookId", data.getBookId());기본키 값(int 타입) -->
<%-- ${bookId} --%>
<!-- 
JSTL(JSP Standard Tag Library) : 개발자가 자주 사용하는 패턴을 모아놓은 집합
=> BookController에서 보내준 데이터를 뷰에 표현하도록 도와줌
JSTL은 maven에서 설정되어 있음 => pom.xml => jstl
 -->
 	<h1>책 상세</h1>
 	<p>제목 : ${data.title} </p>
 	<p>카테고리 : ${data.category}</p>
 	<p>가격 : ${data.price}</p>
 	<p>입력일 : ${data.insertDate}</p>
 	<p>내용 : 
 	<textarea name="content" rows="5" cols="30" readonly>${data.content}</textarea>
 	</p>
 <script type="text/javascript">
 CKEDITOR.replace("content");
// CKEDITOR.instances['content'].setReadOnly(false);
 </script>
</body>
</html>


 

 

Comments