[Spring] 10.SpringProject-영속 계층
persistence 계층
저번 장에서 SpringProject를 진행함에 있어서 초기 설정을 했는데요~
이번 장에서는 DB에 접근해서 SQL문을 실행할 수 있고, 데이터를 VO형태로 읽거나 쓸 수 있는 DAO를 구현하는 작업을 해보겠습니다!
데이터에 접근하는 계층인 Data Access Layer를 구현하는 것인데요!
persistence 계층이라고도 합니다!
Mybatis 초기설정
먼저, DAO를 구현하기 위해서 최초 1회 설정해야하는 작업들을 해보겠습니다!
저번에 했던 작업과 동일하기에 빠르게 진행하겠습니다!
- DataSource 등록
Mysql 서버에 연결할 때 필요한 정보를 가지고 있는 DataSource를 root-context.xml에 추가해줍니다!
<!-- root-context.xml 추가 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://35.200.109.72:3306/testdb?useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="비~밀"></property>
</bean>
- DataSource Test
DataSource 객체가 제대로 생성되는지 테스트!
//DataSourceTest.java
package com.gguri.swp;
import java.sql.Connection;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//Runner 클래스(테스트 메소드를 실행하는 클래스) 를 SpringJUnit4ClassRunner로 함
@RunWith(SpringJUnit4ClassRunner.class)
//location 속성 경로에 있는 xml 파일을 이용해서 스프링이 로딩됨
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/**/root-context.xml")
public class DataSourceTest {
//DataSource의 객체를 new를 사용해 따로 생성해줄 필요없이 스프링이 생성해서 주입해줌
@Inject
private DataSource ds;
@Test
public void testConnection() throws Exception{
try(Connection con = ds.getConnection()){
System.out.println(con);
}catch(Exception e) {
e.printStackTrace();
}
}
}
- XML 네임스페이스 추가
root-context.xml의 NameSpaces를 확장합니다!
- mybatis-config.xml 추가
mybatis의 별도 설정을 담당하는 mybatis-config.xml 파일을 src/main/resource에 추가!
<?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>
</configuration>
- SqlSessionFactory, SqlSessionTemplate 추가
mysql과 mybatis를 연결해주는 SqlSessionFactory와 실질적으로 데이터베이스에 자동으로 연결하여 SQL문을 실행시켜주는 SqlSessionTemplate을 추가해줍니다.
<!-- sqlSessionFactory -->
<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:mappers/**/*Mapper.xml"></property>
</bean>
<!-- sqlSessionTemplate -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"
destroy-method="clearCache">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
</bean>
<!-- com.gguri.swp.persistence 패키지를 자동으로 인식 -->
<context:component-scan base-package="com.gguri.swp.persistence">
</context:component-scan>
- MyBatis Test
MyBatis와 Mysql이 제대로 연결되었는지 Test
package com.gguri.swp;
import javax.inject.Inject;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//Runner 클래스(테스트 메소드를 실행하는 클래스) 를 SpringJUnit4ClassRunner로 함
@RunWith(SpringJUnit4ClassRunner.class)
//location 속성 경로에 있는 xml 파일을 이용해서 스프링이 로딩됨
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/**/root-context.xml")
public class MyBatisTest {
//SqlSessionFactory 객체를 자동으로 생성
@Inject
private SqlSessionFactory sqlFactory;
//SqlSessionFactory 객체가 제대로 만들어졌는지 Test
@Test
public void testFactory() {
System.out.println(sqlFactory);
}
//MyBatis와 Mysql 서버가 제대로 연결되었는지 Test
@Test
public void testSession() throws Exception{
try(SqlSession session = sqlFactory.openSession()){
System.out.println(session);
}catch(Exception e) {
e.printStackTrace();
}
}
}
Board 테이블 생성
MySql 서버에 board 테이블을 생성합니다!
CREATE TABLE board (
bno int(11) NOT NULL AUTO_INCREMENT,
title varchar(200) NOT NULL,
content text,
writer varchar(50) NOT NULL,
regdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
viewcnt INT DEFAULT 0,
PRIMARY KEY (bno)
)
boardMapper 생성
실행할 SQL문을 boardMapper에 추가해줍니다.
<!-- /resource/mappers/boardMapper -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD 선언 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="BoardMapper">
<!-- 데이터 삽입 -->
<insert id="create">
insert into board (title, content, writer)
values( #{title}, #{content}, #{writer})
</insert>
<!-- 행 검색 -->
<select id="read" resultType="com.gguri.swp.domain.BoardVO">
select *
from board
where bno = #{bno}
</select>
<!-- 행 수정 -->
<update id="update">
update board
set title = #{title}, content = #{content}
where bno = #{bno}
</update>
<!-- 행 삭제 -->
<delete id="delete">
delete from board where bno = #{bno}
</delete>
<!-- board 전체 행 조회 -->
<select id="listAll" resultType="com.gguri.swp.domain.BoardVO">
select *
from board
order by bno desc, regdate desc
</select>
<!-- bno 최대 구하기 -->
<select id="getMaxBno" resultType="Integer">
select max(bno) from board
</select>
</mapper>
BoardVO 구현
테이블과 유사한 데이터를 담을 공간인 BoardVO를 만들어줍니다!
package com.gguri.swp.domain;
import java.util.Date;
public class BoardVO {
private Integer bno;
private String title;
private String content;
private String writer;
private Date regdate;
private int viewcnt;
public int getViewcnt() {
return viewcnt;
}
public void setViewcnt(int viewcnt) {
this.viewcnt = viewcnt;
}
public Integer getBno() {
return bno;
}
public void setBno(Integer bno) {
this.bno = bno;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this.regdate = regdate;
}
@Override
public String toString() {
return "BoardVO [bno=" + bno + ", title=" + title + ", content=" + content + ", writer=" + writer + ", regdate="
+ regdate + ", viewcnt=" + viewcnt + "]";
}
}
인터페이스 DAO 작성
BoardMapper에 있는 SQL문을 실행할 메소드를 BoardDAO에 작성해줍니다!
//com.gguri.swp.persistence.BoardDAO
package com.gguri.swp.persistence;
import java.util.List;
import com.gguri.swp.domain.BoardVO;
public interface BoardDAO {
public void create(BoardVO board) throws Exception;
public BoardVO read(Integer bno) throws Exception;
public void update(BoardVO board) throws Exception;
public void delete(Integer bno) throws Exception;
public List<BoardVO> listAll() throws Exception;
public Integer getMaxBno() throws Exception;
}
DAO 구현
인터페이스 DAO의 메소드들을 구현해줍니다~!
//BoardDAOImpl.java
package com.gguri.swp.persistence;
import java.util.List;
import javax.inject.Inject;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import com.gguri.swp.domain.BoardVO;
@Repository
public class BoardDAOImpl implements BoardDAO{
/*SQL문을 실행하는 메소드를 가지고 있는 인터페이스 SqlSession을 구현한 SqlSessionTemplate을 찾아서
객체를 자동으로 생성*/
@Inject
private SqlSession session;
private static String NS = "BoardMapper";
private static String CREATE = NS + ".create";
private static String READ = NS + ".read";
private static String UPDATE = NS + ".update";
private static String DELETE = NS + ".delete";
private static String LISTALL = NS + ".listAll";
private static String GETMAXBNO = NS + ".getMaxBno";
@Override
public void create(BoardVO board) throws Exception {
session.insert(CREATE, board);
}
@Override
public BoardVO read(Integer bno) throws Exception {
return session.selectOne(READ,bno);
}
@Override
public void update(BoardVO board) throws Exception {
session.update(UPDATE, board);
}
@Override
public void delete(Integer bno) throws Exception {
session.delete(DELETE, bno);
}
@Override
public List<BoardVO> listAll() throws Exception {
// TODO Auto-generated method stub
return session.selectList(LISTALL);
}
@Override
public Integer getMaxBno() throws Exception {
return session.selectOne(GETMAXBNO);
}
}
DAO 테스트
DAO가 제대로 구현되었는지 테스트를 하겠습니다!
//BoardDAOTest.java
package com.gguri.swp;
import javax.inject.Inject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.gguri.swp.domain.BoardVO;
import com.gguri.swp.persistence.BoardDAO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/**/root-context.xml")
public class BoardDAOTest {
@Inject
private BoardDAO boardDAO;
private static final Logger logger =
LoggerFactory.getLogger(BoardDAOTest.class);
private static boolean didupdate = 0;
private static int maxbno = 0;
@Before
public void getMaxBno() throws Exception{
if(maxbno == 0) {
boardDAO.create(createBoard("새로운 글을 넣음","새로운 글을 넣음"));
maxbno=boardDAO.getMaxBno();
}
}
@Test
public void readTest() throws Exception {
logger.info(boardDAO.read(maxbno).toString());
cnt++;
}
@Test
public void updateTest() throws Exception{
BoardVO board = createBoard("글이 수정됨","수정테스트");
board.setBno(maxbno);
boardDAO.update(board);
didupdate = true;
}
@After
public void deleteTest() throws Exception{
if (didupdate == true) {
logger.info(boardDAO.listAll().toString());
boardDAO.delete(maxbno);
didupdate = false;
}
}
private BoardVO createBoard(String title, String content) {
BoardVO board = new BoardVO();
board.setTitle(title);
board.setContent(content);
board.setWriter("user00");
return board;
}
}
typeAliases 의 적용
아까 BoarderMapper에 있는 쿼리문에서 resultType을 com.gguri.swp.domain.BoardVO로 하신거 기억하시나요?
Mybatis의 설정 파일인 mybatis-config.xml을 사용해서 typeAliases를 사용하면 resultType에 패키지명을 붙이지 않아도 됩니다!
<!-- mybatis-config.xml -->
<typeAliases>
<package name="com.gguri.swp.domain" />
</typeAliases>