<?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">
<mapper namespace="com.spring.db.repository.IScoreMapper">
<!-- DB컬럼명과 VO의 필드(멤버변수)명을 맞추는 ResultMap 선언
컬럼명과 VO의 변수명이 서로 일치하지 않는다면,
SQL을 아무리 잘 작성해도, MyBatis에서 값을 끌고 오지 못한다...
-->
<resultMap type="com.spring.db.model.ScoreVO" id="ScoreMap">
<id property="stuId" column="stu_id" /> <!-- id는 primary key 맵핑 -->
<result property="stuName" column="stu_name" /> <!-- 나머지 컬럼은 다 result -->
</resultMap>
<!-- 점수 등록 기능 -->
<insert id="insertScore">
INSERT INTO scores
VALUES(id_seq.NEXTVAL,#{stuName},#{kor},#{eng},#{math},#{total},#{average})
</insert>
<!-- 점수 목록 조회 기능 -->
<select id="selectAllScores" resultMap="ScoreMap">
SELECT * FROM scores
ORDER BY stu_id ASC
</select>
<!-- 점수 삭제 기능 -->
<delete id="deleteScore">
DELETE FROM scores
WHERE stu_id=#{num}
</delete>
<!-- 점수 개별 조회 기능 -->
<select id="selectOne" resultMap="ScoreMap">
SELECT * FROM scores
WHERE stu_id=#{num}
</select>
</mapper>
어제에 이어서 목록 조회, 점수 삭제, 개별 조회 기능이 추가되었다.
각 쿼리문은 충분히 이해가 되는 수준이므로 따로 설명하지는 않겠다.
그리고 해당 쿼리가 어떤 작업을 수행하는지에 따라 태그의 시작이 <insert>
나 <select>
와 같이 지정되어 있고, EL문과 비슷하게 #{num}
등과 같이 변수를 지정하고 있다는 점을 잊지 말자.
간과하고 넘어갈 뻔 했는데, 상단의 <resultMap>
태그는 다중 행을 반환받는 쿼리문의 경우에 그것에 관해 지정해 두는 부분인데, VO
의 멤버변수와, DB
내의 변수 이름이 일치할 경우 별 문제 없이 resultSet
을 받아올 수 있지만, 자바는 카멜케이스, DB는 스네이크케이스로 작성하므로 변수명이 일치하지 않아 VO에 값이 대입되지 않는 문제가 생길 수 있다.
따라서, <resultMap>
태그에 type
에는 어떤 type인지 알려주기 위해서 VO
를, id
에는 resultMap
에 대한 id
를 지정해 두고 (나중에 호출하기 위함),
그 안의 <id>
태그의 경우에는 DB상의 primary key
에 매칭되는 VO의 멤버변수와 컬럼명을 적는다.
그 외의 컬럼이나 멤버변수의 경우에는 <result>
태그로 맵핑하면 된다.
이렇게 VO
의 멤버변수와 DB
상의 컬럼명이 서로 다르더라도, <resultMap>
태그로 매핑시켜주면 값이 잘 대입되게 된다.
IBoardMapper.java
package com.spring.db.repository;
import java.util.List;
import com.spring.db.model.BoardVO;
public interface IBoardMapper {
//게시글 등록
void insertArticle(BoardVO vo);
//전체 게시글 목록
List<BoardVO> getArticles();
//게시글 상세 보기
BoardVO getArticle(int bId);
//게시글 삭제
void deleteArticle(int bId);
//게시글 수정
void updateArticle(BoardVO vo);
//게시글 검색
List<BoardVO> searchList(String keyword);
}
게시판에 대해서 일련의 기능을 수행할 메서드를 IBoardMapper
인터페이스에 정의해 둔다.
BoardMapper.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">
<mapper namespace="com.spring.db.repository.IBoardMapper">
<resultMap type="com.spring.db.model.BoardVO" id="BoardMap">
<id property="boardNo" column="board_no" />
</resultMap>
<insert id="insertArticle">
INSERT INTO jdbc_board
VALUES(bid_seq.NEXTVAL,#{writer},#{title},#{content})
</insert>
<select id="getArticles" resultMap="BoardMap">
SELECT * FROM jdbc_board
ORDER BY board_no DESC
</select>
<select id="getArticle" resultMap="BoardMap">
SELECT * FROM jdbc_board
WHERE board_no=#{bId}
</select>
<delete id="deleteArticle">
DELETE FROM jdbc_board
WHERE board_no=#{bId}
</delete>
<update id="updateArticle">
UPDATE jdbc_board SET
writer=#{writer}, title=#{title}, content=#{content}
WHERE board_no=#{boardNo}
</update>
<select id="searchList" resultMap="BoardMap">
SELECT * FROM jdbc_board
WHERE writer LIKE #{keyword}
</select>
</mapper>
이제 BoardMapper.xml
파일에 이것이 MyBatis를 통한 Mapper 파일임을 알리기 위해 어제 만든 ScoreMapper 파일의 상단 부분을 복사해 오고, 내용을 채워나간다.
앞서 설명한 <resultMap>
태그 내의 <id>
등이 어떤 역할을 하고 있는지는 설명했으므로 추가 설명 없이 넘어가겠다.
BoardService.java
package com.spring.db.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.spring.db.model.BoardVO;
import com.spring.db.repository.IBoardDAO;
import com.spring.db.repository.IBoardMapper;
@Service
public class BoardService implements IBoardService {
// @Autowired
// @Qualifier("boardDAO")
// private IBoardDAO dao;
@Autowired
private IBoardMapper mapper;
@Override
public void insertArticle(BoardVO vo) {
mapper.insertArticle(vo);
}
@Override
public List<BoardVO> getArticles() {
return mapper.getArticles();
}
@Override
public BoardVO getArticle(int bId) {
return mapper.getArticle(bId);
}
@Override
public void deleteArticle(int bId) {
mapper.deleteArticle(bId);
}
@Override
public void updateArticle(BoardVO vo) {
mapper.updateArticle(vo);
}
@Override
public List<BoardVO> searchList(String keyword) {
return mapper.searchList("%" + keyword + "%");
}
}
서비스 클래스가 원래는 DAO
와 연결되어 있었겠지만, mapper
를 사용하도록 변경해 준다.
이렇게 해 두면, Board 프로젝트가 문제없이 이전처럼 잘 작동하는 것을 확인할 수 있다.
전에 Spring 프로젝트들 pom.xml 수정했던거랑 똑같이 라이브러리 버전들 수정해줌. 그리고 추가로 아래의 작업 진행.
MvnRepository로 접속 - Spring TestContext Framework 검색 - 5.3.18 코드 복사
https://mvnrepository.com/artifact/org.springframework/spring-test/5.3.18
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
<scope>test</scope>
</dependency>
CREATE TABLE mvc_board (
board_no NUMBER PRIMARY KEY,
title VARCHAR2(100) NOT NULL,
content VARCHAR2(2000) NOT NULL,
writer VARCHAR2(50) NOT NULL,
reg_date DATE DEFAULT sysdate,
view_cnt NUMBER DEFAULT 0
);
CREATE SEQUENCE board_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 1000
NOCYCLE
NOCACHE;
일단은 클래스 생성만 해둔다.
https://projectlombok.org/download
해당 링크에서 최신 버전 다운로드.
이후 STS.exe 나 eclipse.exe 파일이 존재하는 동일 디렉토리에 파일을 놓으면 된다.
단, 경로상에 한글이 포함되는경우 lombok이 동작하지 않을 여지가 있다.
이후 lombok.jar 파일을 더블 클릭하여 실행하고,
ide를 잘 찾아낸 경우 Install / Update 를 눌러 설치 및 실행한다.
https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.24
MvnRepository 로 이동해서 1.18.24 xml 코드를 복사해와 pom.xml에 붙여넣는다.
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.spring</groupId>
<artifactId>mvc</artifactId>
<name>SpringWebMvcProject</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.8</java-version>
<org.springframework-version>5.3.18</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- HikariCP: 커넥션 풀 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.3.1</version>
</dependency>
<!-- ojdbc6 DB 커넥터 드라이버 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4</version>
</dependency>
<!-- mybatis 라이브러리 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mybatis와 spring을 연동해 주는 api -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- spring-test 모듈 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.18</version>
<scope>test</scope>
</dependency>
<!-- lombok 라이브러리 추가 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
이런 느낌으로 pom.xml 이 구성된다.
이제 lombok을 통해 BoardVO를 이전과는 다르게 구성할 것이다.
package com.spring.mvc.board.model;
import java.sql.Timestamp;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/*
-- 게시판 테이블 생성
CREATE TABLE mvc_board (
board_no NUMBER PRIMARY KEY,
title VARCHAR2(100) NOT NULL,
content VARCHAR2(2000) NOT NULL,
writer VARCHAR2(50) NOT NULL,
reg_date DATE DEFAULT sysdate,
view_cnt NUMBER DEFAULT 0
);
-- board_no에 대한 시퀀스 설정
CREATE SEQUENCE board_seq
START WITH 1
INCREMENT BY 1
MAXVALUE 1000
NOCYCLE
NOCACHE;
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class BoardVO {
private int boardNo;
private String title;
private String content;
private String writer;
private Timestamp regDate;
private int viewCnt;
}
클래스명에 붙어있는 어노테이션들은 lombok 라이브러리를 로드해야만 사용할 수 있는 어노테이션들이다.
@Getter
, @Setter
- getter, setter
@NoArgsConstructor
- 매개값 없는 생성자
@AllArgsConstructor
- 모든 멤버변수를 매개변수로 받는 생성자
@ToString
- ToString()
메서드
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- JDBC, DB 관련 빈을 등록하고 관리하는 설정 파일 -->
<!-- 히카리 커넥션 풀 빈 등록 -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="spring" />
<property name="password" value="spring" />
</bean>
<!-- 히카리 데이터소스 빈 등록 -->
<bean id="ds" class="com.zaxxer.hikari.HikariDataSource">
<constructor-arg ref="hikariConfig" />
</bean>
<!-- 마이바티스 SQL 동작을 위한 핵심 객체 SqlSessionFactory 클래스 빈 등록 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds" />
<property name="mapperLocations" value="classpath:/mappers/**/*Mapper.xml" />
</bean>
<!-- 지정한 패키지를 스캔하여 존재하는 mapper 인터페이스를 빈 타입으로 등록.
나중에 sqlSessionFactory가 xml파일을 클래스로 변환하여 빈으로 등록하려는 시도를 할 때
타입을 지정해 줘야 하기 때문.
-->
<mybatis-spring:scan base-package="com.spring.mvc.board.repository"/>
</beans>
그리고 나서 jdbc, mvc, mybatis 3개를 namespace 탭에서 추가한다. 위 코드상에선 이미 추가되어있긴 하지만 이미 설정을 하고 난 뒤에 복사했기 때문이다.
package com.spring.mvc.board.repository;
import java.util.List;
import com.spring.mvc.board.model.BoardVO;
public interface IBoardMapper {
//게시글 등록 기능
void insert(BoardVO article);
//게시글 전체 조회 기능(페이징 전)
List<BoardVO> getArticleList();
//게시글 상세 조회 기능
BoardVO getArticle(int boardNo);
//게시글 수정 기능
void update(BoardVO article);
//게시글 삭제 기능
void delete(int boardNo);
//게시글 수 조회 기능
}
패키지는 com.spring.mvc.board.repository
하위에 만든다.
classpath:
(src/main/resources
) 하위에 board 디렉토리를 만들고 그 안에 생성한다.
내용은 이렇게.
<?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">
<mapper namespace="com.spring.mvc.board.repository.IBoardMapper">
</mapper>
일단 매퍼 만들어만 놓고, 화면이 없는 상태에서 테스트를 진행해본다. 화면이 없는데 테스트를 어떻게 진행하나 생각했는데 junit 라이브러리를 사용하면 된다고 한다.
src/test/java
디렉토리 하위에 com.spring.mvc.board
패키지 내에 BoardMapperTest.java
파일을 만든다.
package com.spring.mvc.board;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.spring.mvc.board.repository.IBoardMapper;
@RunWith(SpringJUnit4ClassRunner.class)
//테스트 환경에서 Mapper 객체 활용을 위해 빈 등록 설정이 있는 xml 파일을 로딩.
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/mvc-config.xml"})
public class BoardMapperTest {
@Autowired
private IBoardMapper mapper;
}
@RunWith(SpringJUnit4ClassRunner.class)
이렇게 어노테이션을 붙여두면, 테스트를 SpringJUnit4ClassRunner
이 객체 타입으로 돌리겠다는 뜻이 된다. (정확히 뭐 하는 객체인지는 모르겠고)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/mvc-config.xml"})
이 부분은, mapper 객체를 자동주입하기 위해서는 mapper.xml의 위치를 알아야 하는데, 설정 파일을 끌어와서 그것을 해결하는 부분이다.
아까 mvc-config.xml
파일 안에 이 부분을 참고하면 된다.
mvc-config.xml
<!-- 지정한 패키지를 스캔하여 존재하는 mapper 인터페이스를 빈 타입으로 등록.
나중에 sqlSessionFactory가 xml파일을 클래스로 변환하여 빈으로 등록하려는 시도를 할 때
타입을 지정해 줘야 하기 때문.
-->
<mybatis-spring:scan base-package="com.spring.mvc.board.repository"/>
이 부분에 닿아서 *.repository
패키지에 닿아서 그 패키지 안의 IBoardMapper
인터페이스를 찾게 되어 자동 주입을 해 줄 것이다.
이것으로 오늘 강의는 마치게 되었다!