[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();
		}
	}
}




image




  • XML 네임스페이스 추가



root-context.xml의 NameSpaces를 확장합니다!


image




  • 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();
		}
	}
}




image




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)
)




image




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




image




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>