Spring

Spring 03 - 도서 CRUD(2) - insert

inderrom 2023. 1. 20. 12:48
  • 스프링은 인터페이스다.

 

INSERT 쿼리문 (ex)

INSERT BOOK(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT) values((SELECT NVL(MAX(BOOK_ID),0)+1 FROM BOOK),'낭만닥터개똥이','드라마',12000,sysdate,'김사부');
Insert into Book(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT)
values(1,'검은개똥이','드라마',10000,sysdate,'검은태양 후속작');

Insert into Book(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT)
values(1,'검은개똥이','드라마',10000,sysdate,'검은태양 후속작');

Insert into Book(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT)
values(2,'수리남','드라마',11000,sysdate,'식사는 잡쉈구');

-- 이렇게 넣으면 쭉쭉 계속 들어감
Insert into Book(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT)
values((SELECT NVL(MAX(BOOK_ID),0)+1 FROM BOOK),'낭만닥터개똥이','드라마',12000,sysdate,'김사부');

SELECT * FROM BOOK;
--TCL(Transaction Control Language)
--트랜잭션 : 데이터베이스를 변경하기 위해 수행되어야 할 논리적 단위.
--          여러개의 SQL로 구성되어 있음
--1) COMMIT : DB에 반영
--2) ROLLBACK : 마지막 COMMIT/ROLLBACK 시점으로 되돌림
--3) SAVEPOINT : 중간저장
ROLLBACK;


--시퀀스에는 OR REPLACE가 없음
CREATE SEQUENCE SEQ_BOOK
START WITH 3
INCREMENT BY 1;
--현재 시퀀스
SELECT SEQ_BOOK.CURRVAL FROM DUAL;
--다음 시퀀스값
SELECT SEQ_BOOK.NEXTVAL FROM DUAL;
--서브쿼리 : 메인SQL안에 사용된 또 다른 쿼리
--1) SCALAR : SELECT
--2) NESTED : WHERE
--3) INLINE VIEW : FROM
-- 괄호로 묶어야 함
-- 널이 있으면 널봐라 (NULL이 있으면 0)
SELECT NVL(MAX(BOOK_ID),0)+1 FROM BOOK;

-- 집게함수
--SUM, AVG,MAX,MIN,COUNT

 


쿼리문을 작성할 xml 파일 생성

 

root-context.xml에서 설정한 것과 똑같이 xml 파일이름 설정

  • * : 모든 단어가 들어갈 수 있고 뒤에 _SQL.xml 이라는 이름을 똑같이 설정해야한다.

패키지 구조

<book_SQL.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">
  <!-- namespace : xml 파일이 여러개일 수 있으므로
  		이를 구별하기 위한 식별 용도로 사용 -->
  <mapper namespace="book">
  	<!-- MyBatis에서 제공해주는 데이터 입력을 나타내는 태그
  		1) 드루와 : Dao 객체가 던진 데이터타입. parameterType만 씀
  		2) 가즈아 : Dao 객체의 메소드 쪽으로 리턴할 타입. insert/update/delete의 경우 생략(당연히 int)
  			- resultType : String, int, hashMap, VO
  			- resultMap : 1:N의 관계 . MyBatis의 resultMap 태그를 사용
  	 -->
  	 <!-- 
  	 	(전) 	bookVO{bookId:0,title:롬이야기,category:소설price:10000,insertDate:null
					content:null}
		(후)		bookVO{bookId:1,title:롬이야기,category:소설price:10000,insertDate:null
					content:null}
		MyBatis 쿼리 XML에 전달되면 #{멤버변수}로 작성하여 값을 치환
  	  -->
  	<insert id="createPost" parameterType="bookVO" >
  		<!-- 키를 높이면 락(rOK)커가 될 수 있을까? -->
  		<selectKey resultType="int" order="BEFORE" keyProperty="bookId">
  			SELECT NVL(MAX(BOOK_ID),0)+1 FROM BOOK
  		</selectKey>
  		INSERT INTO BOOK(BOOK_ID, TITLE, CATEGORY, PRICE, INSERT_DATE, CONTENT)
		VALUES(#{bookId},#{title},#{category},
		#{price},SYSDATE,#{content})
  	</insert>
  </mapper>

 


mapper.xml를 실행시키는 DAO 클래스

 

<BookDao.java>

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;

/*
매퍼XML(book_SQL.xml)을 실행시키는 DAO(Data Access Object) 클래스
Repository 어노테이션 : 데이터에 접근하는 클래스
=> 스프링이 데이터를 관리하는 클래스라고 인지하여 자바빈 객체로 등록하여 관리함
*/
@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를 써줘야 한다.
		
		// 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;
	}
}

 

 


Controller와 DAO를 연결하는 Service

 

<BookService.java> - 인터페이스

package kr.or.ddit.service;

import kr.or.ddit.vo.BookVO;

// 서비스 interface : 비즈니스 로직(비지니스 레이어)
public interface BookService {
	// 메소드 시그니처
	public int createPost(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);
	}
}

 


<BookController.java>

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;
	
	}
}

localhost/create 주소를 입력하고 전송을 누르면  부적합한 열이 있다는 오류가 나온다

create.jsp 에서 cotent를 빠트리고 작성했기 때문

 

그러므로 내용을 다시 추가해주고 실행 시키면

<p>내용: <input type="text" name="content" required /></p>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
</script>
<meta charset="UTF-8">
<title>책 등록하기</title>
</head>
<body>
<h1>책 등록</h1>
<!-- 폼페이지 -->
<!-- 
요청URI : /cretae?title=롬이야기&category=소설&price=10000
요청파라미터 : tilte=롬이야기&category=소설&price=10000
요청방식 : post
 -->
<form action="/create" method="post">
	<!-- 폼데이터 -->
	<p>제목 : <input type="text" name="title" required /></p>
	<p>카테고리 : <input type="text" name="category" required /></p>
	<p>가격: <input type="number" name="price" required /></p>
	<p>내용: <textarea name="content" rows="5" cols="30"></textarea></p>
	<p>
		<input type="submit" value="저장" />
		<input type="button" value="목록" />
	</p>
</form>
</body>
</html>

데이터가 정상적으로 삽입된 것을 볼 수 있다.

 


ckeditor 사용하기

 

resources 안에 ckeditor 폴더를 넣어준다.

<servlet-context.xml>

resources에 매핑한다. (**는 resources안에 있는 모든 것이 적용됨)
	<!-- static folder설정(정적 폴더 설정) => css, images, upload, js
		http://localhost/resources/
	 -->
	<resources mapping="/resources/**" location="/resources/" />

 

<create.jsp>

  • head 밑에 스크립트 삽입
<script type="text/javascript" src="/resources/ckeditor/ckeditor.js">
  • body 안에 스크립트 삽입
<script type="text/javascript">
	CKEDITOR.replace('content');
</script>

 

 

다시 실행 시키면 ckeditor가 적용된다.

아직 detail.jsp를 만들지 않았기 때문에 화면은 전환되지 않는다.

이제 detail을 만들어 보자 !