Spring Quick Start Day 5
Mybatis 프레임워크 시작하기
Mybatis 프레임워크의 특징
Mybatis 프레임워크의 가장 중요한 특징을 두 가지로 정리하자면 첫째는 한두 줄의 자바 코드로 DB 연동을 처리한다는 것이며, 둘째는 SQL 명령어를 자바 코드에서 분리하여 XML 파일에 따로 관리한다는 것이다. 이 두가지가 기존에 우리가 사용하던 JDBC 기반의 DB 연동을 어떻게 개선하는지 살펴보자. 다음은 BoardDAO 클래스에 글 목록 검색 기능을 JDBC 기반으로 구현한 것이다.
// DAO(data Access Object)
@Repository("BoardDAO")
public class BoardDAO {
//JDBC 관련 변수
private Connection conn= null;
private PreparedStatement stmt = null;
private ResultSet rs = null;
//SQL 명령어들
private final String BOARD_LIST = "select * from board order by seq desc";
//CRUD 기능의 메소드 구현
//글 목록 조회
public List<BoardVO> getBoardList(BoardVO vo){
System.out.println("===> JDBC로 getBoardList() 기능처리");
List<BoardVO> boardList = new ArrayList<BoardVO>();
try {
conn = JDBCUtil.getConnection();
if(vo.getSearchCondition().equals("TITLE")) {
stmt = conn.prepareStatement(BOARD_LIST_T);
}else if(vo.getSearchCondition().equals("CONTENT")) {
stmt = conn.prepareStatement(BOARD_LIST_C);
}
stmt.setString(1,vo.getSearchKeyword());
rs = stmt.executeQuery();
while(rs.next()) {
BoardVO board = new BoardVO();
board.setSeq(rs.getInt("SEQ"));
board.setTitle(rs.getString("TITLE"));
board.setWriter(rs.getString("WRITER"));
board.setContent(rs.getString("CONTENT"));
board.setRegDate(rs.getDate("REGDATE"));
board.setCnt(rs.getInt("CNT"));
boardList.add(board);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.close(stmt, conn);
}
return boardList;
}
위 코드는 JDBCUtil 클래스를 이용하여 DB 커넥션 획득과 해제 작럽을 처리해서 그나마 코드가 간결해진 것이다. 만약 JDBCUtil 클래스가 없었다면 더 많은 자바 코드가 필요했을 것이다.
사실 유지보수 관점에서 보면 DB 연동에 사용된 복잡한 자바 코드는 더 이상 중요하지 않다. 개발자는 실행되는 SQL만 관리하면 되며, Mybatis는 개발자가 이 SQL 관리에만 집중할 수 있도록 도와준다. 다음은 위에서 작성한 글 목록 검색 기능을 Mybatis로 변경한 코드이다.
public class BoardDAO {
public List<BoardVO> getBoardList(BoardVO vo){
SqlSession mybatis = sqlSessionFactoryBean.getSqlSessionInstance();
return mybatis.selectList("BoardDAO.getBoardList",vo;
}
Mybatis는 XML 파일에 저장된 SQL 명령어를 대신 실행하고 실행 결과를 VO 같은 자바 객체에 자동으로 매핑까지 해준다. 그래서 Mybatis 프레임워크를 데이터 맵퍼(Data Mapper)라고 부른다. 결국 Mybatis 프레임워크를 이용하여 DB 연동을 처리하면 대부분 한두 줄의 자바 코드만으로도 원하는 DB 연동 로직을 처리할 수 있게 된다.
Mybatis의 두 번째 특징은 Java 코드에서 SQL 구문을 분리하는 것이다. 만약 SQL 명령어가 DAO 같은 자바 클래스에 저장되면 SQL 명령어만 수정하는 상황에서도 자바 클래스를 다시 컴파일 해야 한다. 그리고 SQL 명령어들을 한 곳에 모아서 관리하기도 쉽지 않다.
결국, SQL 명령어에 대한 통합 관리를 위해서라도 자바 소스에서 SQL을 분리하는 것은 매우 중요하다.
Java ORM Plugin 설치
[에러/Spring] Java ORM Plugin 설치
0. 참고도서 - 스프링 퀵 스타트 / 채규태 1. 현상 - MyBatis 사용을 위해 이클립스에서 "Java ORM" 플러그인 설치 필요! - 이클립스 Help > Eclipse Marketplace > (Find) ORM 검색 시 해당 프로그램이 나오지..
skyfox83.tistory.com
현재는 마켓 플레이스에 Java ORM Plugin이 없기 때문에 수동으로 설치해야 한다.
프로젝트 생성
Mybatis 프레임워크 구조와 개념을 이해하기 위해 Mybatis만으로 간단한 CRUD 기능을 테스트해보자.
이클립스에서 Spring Legacy Project를 선택하고 프로젝트 이름을 MybatisProject라고 입력한 후 Project 템플릿 중에서 Simple Spring Maven을 선택한 후 Finish버튼을 클릭한다.
pom.xml에 다음과 같이 mybatis와 ibatis도 추가하고 데이터베이스 관련 라이브러리도 추가한다.(오라클의 경우 ojdbc)
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
<!-- Ibatis -->
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>
VO(Value Object) 작성
public class BoardVO {
private int seq;
private String title;
private String writer;
private String content;
private Date regDate;
private int cnt;
private String searchCondition;
private String searchKeyword;
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getRegDate() {
return regDate;
}
public void setRegDate(Date regDate) {
this.regDate = regDate;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
public String getSearchCondition() {
return searchCondition;
}
public void setSearchCondition(String searchCondition) {
this.searchCondition = searchCondition;
}
public String getSearchKeyword() {
return searchKeyword;
}
public void setSearchKeyword(String searchKeyword) {
this.searchKeyword = searchKeyword;
}
@Override
public String toString() {
return "BoardVO [seq=" + seq + ", title=" + title + ", writer=" + writer + ", content=" + content + ", regDate="
+ regDate + ", cnt=" + cnt + ", searchCondition=" + searchCondition + ", searchKeyword=" + searchKeyword
+ "]";
}
}
SQL Mapper XML 작성
SQL Mapper XML 파일은 Mybatis에서 가장 중요한 파일이다. 이 XML 파일에 DB 연동에 필요한 모든 SQL 명령어들이 저장되기 때문이다. 이 XML 파일은 Java ORM 플러그인을 이용하여 간단하게 생성할 수 있다.
<?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="BoardDAO">
<insert id="insertBoard">
<selectKey>
select board_seq.nextval as seq from dual
</selectKey>
insert into board(seq,title,writer,content)
values(#{seq},#{title},#{writer},#{content})
</insert>
<select id="getBoard" resultType="board">
SELECT * FROM board where seq=#{seq}
</select>
<select id="getBoardList" resultType="board">
SELECT * FROM board
where title like '%'||#{searchKeyword}||'%'
order by seq desc
</select>
<update id="updateBoard" >
update board set title=#{title},content=#{content} where seq=#{seq}
</update>
<delete id="deleteBoards" >
delete board where seq = #{seq}
</delete>
</mapper>
SQL Mapper 파일은 <mapper>를 루트 엘리먼트로 사용한다. 그리고 <insert>, <update>, <delete>, <select> 엘리먼트를 이용하여 필요한 SQL 구문들을 등록한다. 기존에 BoardDAO 클래스에서 사용했던 SQL 구문들을 등록한다. 기존에 BoardDAO 클래스에서 사용했던 구문들을 그대로 등록하여 재사용하면 되므로 기본 설정 자체는 그리 복잡하지 않다.
Mybatis 환경설정 파일
Mybatis 환경설정 파일도 앞에서 설치한 JavaORM 플러그인을 사용하면 자동으로 생성할 수 있다.
Java ORM Plugin 폴더에서 Mybatis Contigration XML을 선택한 후 "sql-map-config"제목으로 xml파일을 생성한다. 그러면 db.properties파일과 xml 파일이 생성된다. 두 파일을 src/main/resource 폴더로 이동시킨다. 파일들을 이동했으면, 먼저 DB 커넥션 관련 프로퍼티 정보가 등록되어 있는 db.properties 파일을 수정한다.
jdbc.driver = oracle.jdbc.OracleDriver
jdbc.url = jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=database
jdbc.password=database
그리고 Mybatis 메인 환경설정 파일인 sql-map-config.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>
<!-- Properties 설정 -->
<properties resource="db.properties" />
<!-- Alias 설정 -->
<typeAliases>
<typeAlias type="com.mybatis.pro.BoardVO" alias="board"></typeAlias>
</typeAliases>
<!-- DataSource 설정 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<!-- Sql Mapper 설정 -->
<mappers>
<mapper resource="mappings/board-mapping.xml" />
</mappers>
</configuration>
<properties> 엘리먼트는 XML 설정에서 사용할 프로퍼티를 선언하거나 외부 프로퍼티 파일을 참조할 때 사용한다. 이렇게 선언된 프로퍼티는 "${프로퍼티 이름}"으로 참조하여 사용할 수 있다.
<typeAlias> 엘리먼트는 <typeAlias>를 여러 개 가질 수 있으며, <typeAlias> 엘리먼트를 이용하여 특정 클래스의 별칭(Alias)를 선언할 수 있다. 이 Alias는 SQL 명령어들이 저장되는 Sql Mapper에서 사용할 수 있으며, 이를 통해서 Sql Mapping 파일의 크기를 줄여주기도 하고 설정을 간단히 처리할 수도 있다. 현재는 BoardVO 클래스에 대한 Alias만 board로 설정한 상태이다.
myBatis는 특정 DBMS로부터 커넥션을 획득하고 DB 연동을 처리하기 위해서 반드시 DataSource 정보가 필요하다. <environmnets> 엘리먼트에는 다양한 설정을 추가할 수 있지만, 현재는 가장 중요한 DataSource 설정만 작성하였고 oracle 데이터베이스 연동을 위한 설정으로 수정했다.
마지막으로 <mappers> 엘리먼트는 여러 <mapper>를 가질 수 있으며, 이<mapper>를 이용하여 SQL명령어들이 저장된 SQL 파일들을 등록할 수 있다.
SqlSession 객체 생성하기
이제는 이 Mybatis관련 설정을 기반으로 DAO클래스를 구현해보도록 하자. 그러면 MyBatis의 장점을 더욱 확실히 느낄 수 있을 것이다.
Mybatis를 이용하여 DAO를 구현하려면 SqlSession 객체가 필요하다. 그런데 이 SqlSession 객체를 얻으려면 SqlSessionFactory객체가 필요하다. 따라서 DAO 클래스를 구현하기에 앞서 SqlSessionFactory 객체를 생성하는 유틸리티 클래스를 작성해야 한다.
package com.mybatis.pro;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionFactoryBean {
private static SqlSessionFactory sessionFactory = null;
static {
try {
if(sessionFactory==null) {
Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSessionInstance() {
return sessionFactory.openSession();
}
}
위 소스에서 가장 중요한 핵심 코드는 두 줄이다. 우선 Mybatis 메인 설정인 sql-map-config.xml 파일로부터 설정 정보를 읽어 들이기 위한 입력 스트림을 생성해야 한다. 그리고 나서 입력 스트림을 통해 sql-map-config.xml 파일을 읽어 SqlSessionFactory 객체를 생성한다.
Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
getSqlSessionInstance() 메소드는 SqlSessionFactory객체로부터 SqlSession 객체를 얻어내어 리턴하는 메소드다. 이제 이 메소드를 이용하여 SqlSession 객체가 필요한 DAO클래스를 구현하면 된다.
DAO 클래스 작성
package com.mybatis.pro;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
public class BoardDAO {
private SqlSession mybatis;
public BoardDAO() {
mybatis = SqlSessionFactoryBean.getSqlSessionInstance();
}
public void insertBoard(BoardVO vo) {
mybatis.insert("BoardDAO.insertBoard",vo);
mybatis.commit();
}
public void updateBoard(BoardVO vo) {
mybatis.insert("BoardDAO.updateBoard",vo);
mybatis.commit();
}
public void deleteBoard(BoardVO vo) {
mybatis.insert("BoardDAO.deleteBoard",vo);
mybatis.commit();
}
public BoardVO getBoard(BoardVO vo) {
return (BoardVO)mybatis.selectOne("BoardDAO.getBoard",vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
return mybatis.selectList("BoardDAO.getBoardList",vo);
}
}
BoardDAO 클래스는 생성자에서 SqlSessionFactoryBean을 이용하여 SqlSession 객체를 얻어 내고 있다. 그리고 이 SqlSession 객체의 메소드를 이용하여 CRUD 기능의 메소드를 모두 구현하고 있다.
구현된 각 메소드를 보면 두 개의 정보가 인자로 전달되고 있는데, 첫 번째 인자는 실행될 SQL의 id 정보이다. 이때 SQL Mapper에 선언된 네임스페이스와 아이디를 조합하여 아이디를 지정해야 한다. 그리고 두 번째 인자는 parameterType 속성으로 지정된 파라미터 객체이다. 등록, 수정, 삭제는 각각 insert(), update(), delete() 메소드로 처리하며, 단 건 조회, 목록 조회는 selectOne(), selectList() 메소드로 처리한다.
테스트 클라이언트 작성 및 실행
다음은 BoardDAO 클래스의 메소드를 테스트하는 클라이언트 프로그램이다. src/test/java 소스 폴더에 클라이언트 프로그램을 작성하고 실행해본다.
package com.mybatis.pro;
import java.util.List;
public class BoardServiceClient {
public static void main(String[] args) {
BoardDAO boardDAO = new BoardDAO();
BoardVO vo = new BoardVO();
vo.setTitle("mybatis 제목");
vo.setWriter("홍길동");
vo.setContent("mybatis 내용입니다....");
boardDAO.insertBoard(vo);
vo.setSearchCondition("TITLE");
vo.setSearchKeyword("");
List<BoardVO> boardList = boardDAO.getBoardList(vo);
for(BoardVO board : boardList) {
System.out.println("---->"+ board.toString());
}
}
}
테스트 클라이언트를 실행하면 다음과 같이 정상적으로 글이 등록돼고 출력됨을 알 수 있다.
Mapper XML 파일 설정
우선 SqlMapContig.xml 파일은 mybatis 메인 환경설정 파일이다. Mybatis는 이 파일을 읽어들여 어떤 DBMS와 커넥션을 맺을지, 어떤 SQL Mapper XML 파일들이 등록되어 있는지 알 수 있다.
Mybatis는 SqlMap.xml 파일에 등록된 각 SQL 명령어들을 Map 구조로 저장하여 관리한다. 각 SQL 명령어는 고유한 아이디 값을 가지고 있으므로 특정 아이디로 등록된 SQL을 실행할 수 있다. 그리고 SQL이 실행될 때 필요한 값들은 input 형태의 데이터로 할당하고, 실행된 SQL이 SELECT 구문일 때는 output 형태의 데이터로 리턴한다.
Mapper XML 파일 구조
Mybatis 프레임워크에서 가장 중요한 파일은 SQL 명령어들이 저장되는 SQL Mapper XML(이후부터는 Mapper로 통칭) 파일이다. Mapper는 <mapper>를 루트 엘리먼트로 가지는 XML파일이다.
Mapper 파일의 구조를 보면 가장 먼저 DTD 선언이 등장하고 그 밑에 <mapper> 루트 엘리먼트가 선언된다. <mapper> 엘리먼트는 namespace 속성을 가지는데, 이 네임스페이스를 이용하여 더 쉽게 유일한 SQL 아이디를 만들 수 있다. 네임스페이스가 지정된 Mapper의 SQL을 DAO클래스에서 참조할 떄는 다음과 같이 네임스페이스와 SQL 아이디를 결합하여 참조해야 한다.
Mapper XML | <mapper namespace="BoardDAO"> <delete id = "deleteBoard"> delete board where seq=#{seq} </delete> </mapper> |
DAO 클래스 | public void deleteBoard(BoardVO vo){ mybatis.delete("BoardDAO.deleteBoard",vo); } |
Mapper 파일에 SQL 명령어들을 등록할 때는 SQL 구문의 종류에 따라 적절한 엘리먼트를 사용한다. INSERT 구문은 <insert> 엘리먼트를, SELECT 구문은 <select> 엘리먼트를 사용하는 식이다. 이때, 각 엘리먼트에서 사용할 수 있는 속성들이 다르므로 그 의미와 용도를 이해해야 한다.
<select> 엘리먼트
<select> 엘리먼트는 데이터를 조회하는 SELECT 구문을 작성할 때 사용한다. <select> 엘리먼트에는 다음과 같이 parameterType과 resultType 속성을 사용할 수 있다.
<select id="getBoard" resultType="board">
SELECT * FROM board where seq=#{seq}
</select>
<select id="getBoardList" resultType="board">
SELECT * FROM board
where title like '%'||#{searchKeyword}||'%'
order by seq desc
</select>
id 속성
<select> 엘리먼트에 선언된 id 속성은 필수 속성으로, 반드시 전체 Mapper 파일들 내에서 유일한 아이디를 등록해야 한다. 그래야 나중에 DAO 클래스에서 특정 아이디로 등록된 SQL을 실행할 수 있다. 이 id 속성과 관련하여 살펴볼 것이 루트 엘리먼트인 <mapper>이다. <mapper> 엘리먼트에 설정된 네임스페이스는 <mapper> 엘리먼트 안에서 선언된 여러 아이디를 하나의 네임스페이스로 묶을 수 있다.
예를 들어, 다음 두 파일에 선언된 "getTotalCount"라는 아이디는 네임스페이스가 다르므로 다른 아이디로 처리될 수 있다.
board-mapping.xml | user-mapping.xml |
<mapper namespace="BoardDAO"> <select id="getTotalCount" resultType="int"> select count(*) from board </select> </mapper> |
<namespace="UserDAO"> <select id="getTotalCount" resultType="int"> select count(*) from users </select> </mapper> |
parameterType 속성
Mapper 파일에 등록된 SQL을 실행하려면 SQL 실행에 필요한 데이터를 외부로부터 받아야 한다. 이때 사용하는 속성이 parameterType 속성이다. parameterType 속성값으로는 일반적으로 기본형이나 VO형태의 클래스를 지정한다.
<insert id="insertBoard" parameterType="com.mybatis.pro.BoardVO">
insert into board(seq,title,writer,content)
values((select nvl(max(seq),0)+1 from board),#{title},#{writer},#{content})
</insert>
이때 Mybatis 메인 설정 파일(sql-map-config)에 등록된 <typeAlias>의 Alias를 사용하면 설정을 더 간결하게 처리할 수 있다.
<typeAliases>
<typeAlias type="com.mybatis.pro.BoardVO" alias="board"></typeAlias>
</typeAliases>
<select id="getBoard" paremeterType="board" resultType="board">
SELECT * FROM board where seq=#{seq}
</select>
parameterType으로 지정된 클래스에는 사용자가 입력한 값들을 저장할 여러 변수가 있다. 변수들을 이용하여 SQL 구문에 사용자 입력값들을 설정하는데, 이때 '#{변수명}' 표현을 사용한다. 그리고 중요한 건 parameterType 속성은 생략할 수 있으며 대부분 생략한다. 그리고 증요한 건 parameterType 속성은 생략할 수 있으며 대부분 생략한다.
resultType 속성
검색 관련 SQL 구문이 실행되면 ResultSet이 리턴되며, ResultSet에 저장된 검색 결과를 어떤 자바 객체에 매핑할지 지정해야 하는데, 이때 사용하는 것이 resultType 속성이다.
<typeAliases>
<typeAlias type="com.mybatis.pro.BoardVO" alias="board"></typeAlias>
</typeAliases>
<select id="getBoard" paremeterType="board" resultType="board">
SELECT * FROM board where seq=#{seq}
</select>
resultType 속성값으로도 Alias를 사용할 수 있는데, 만약 resultType 속성값으로 위와 같이 board를 사용했다면 SELECT 실행결과를 BoardVO 객체에 매핑하여 리턴하라는 의미이다.
resultType 속성은 당연히 쿼리 명령어가 등록되는 <select> 엘리먼트에서만 사용할 수 있으며, parameterType 속성과 달리 <select> 엘리먼트에서 절대 생략할 수 없는 속성이다. 다만 resultType 속성 대신에 나중에 살펴볼 resultMap 속성을 사용할 수는 있다.
<insert> 엘리먼트
<insert> 엘리먼트는 데이터베이스에 데이터를 삽입하는 INSERT 구문을 작성하는 요소이다.
<insert> 구문은 자식 요소로 <selectKey> 엘리먼트를 가질 수 있다. 대부분 관계형 데이터베이스에서는 기본 키 필드의 자동 생성을 지원하는데, Mybatis에서는 <insert> 요소의 자식 요소인 <selectKey> 요소를 사용하여 생성된 키를 쉽게 가져올 수 있는 방법을 제공한다.
Mapper XML | <insert id="insertBoard"> <selectKey KeyProperty="seq" resultType="int"> select board_seq.nextval as seq from dual </selectKey> insert into board(seq,title,writer,content) values(#{seq},#{title},#{writer},#{content}) </insert> |
이 설정은 "BOARD_SEQ"라는 시퀀스로부터 유일한 킷값을 얻어내어 글 등록에서 일련번호(seq) 값으로 사용하는 설정이다.
<update> 엘리먼트
<update> 엘리먼트는 데이터를 수정할 때 사용되는 UPDATE 구문을 작성하는 요소이다.
<update id="updateBoard" >
update board set title=#{title},content=#{content} where seq=#{seq}
</update>
<delete> 엘리먼트
<update> 엘리먼트는 데이터를 수정할 때 사용되는 UPDATE 구문을 작성하는 요소이다.
<delete id="deleteBoards" >
delete board where seq = #{seq}
</delete>
SQL Mapper XML 추가 설정
resultMap 속성 사용
검색 결과를 특정 자바 객체에 매핑하여 리턴하기 위해서 parameterType 속성을 사용한다. 그러나 검색 결과를 parameterType 속성으로 매핑할 수 없는 몇몇 사례가 있다. 예를 들어, 검색 쿼리가 단순 테이블 조회가 아닌 JOIN 구문을 포함할 때는 검색 결과를 정확하게 하나의 자바 객체로 매핑할 수 없다. 또는 검색된 테이블의 칼럼 이름과 매핑에 사용될 자바 객체의 변수 이름이 다를 때에 검색 결과가 정확하게 자바 객체로 매핑되지 않는다. 이럴 때 resultMap 속성을 사용하여 처리하면 된다.
resultMap 속성을 사용하려면 먼저 <resultMap> 엘리먼트를 사용하여 매핑 규칙을 지정해야 한다.
<resultMap id="boardList" type="board">
<id property = "seq" column="SEQ"/>
<result property = "title" column="TITLE"/>
<result property = "writer" column="WRITER"/>
<result property = "content" column="CONTENT"/>
<result property = "regDate" column="REGDATE"/>
<result property = "cnt" column="CNT"/>
</resultMap>
<select id="getBoardList" resultMap="boardList" >
SELECT * FROM board
where title like '%'||#{searchKeyword}||'%'
order by seq desc
</select>
위 설정에서는 boardList라는 아이디로 <resultMap>을 설정했다. <resultMap> 설정은 PK(primary Key>에 해당하는 SEQ 컬럼만 <id> 엘리먼트를 사용했고, 나머지는 <result> 엘리먼트를 이용하여 검색 결과로 얻어낸 컬럼의 값과 BoardVO 객체의 변수를 매핑하고 있다. 이렇게 설정된 <resultMap>을 getBoardList로 등록된 쿼리에서 resultMap속성으로 참조하고 있다.
CDATA Section 사용
만약 구문 내에 '<' 기호를 사용한다면 에러가 발생한다.
<select id="getBoard" resultType="board">
SELECT * FROM board where seq <=#{seq}
</select>
이는 XML파서가 XML파일을 처리할 때 '<'를 "작다"라는 의미의 연산자가 아닌 또 다른 태그의 시작으로 처리하기 때문이다. 결국 Mapper파일에 등록된 SQL 구문에서는 '<'나'>'같은 기호를 사용하면 에러가 발생한다. 하지만 다음처럼 CDATA section으로 SQL구문을 감싸주면 에러는 사라진다.
<select id="getBoard" resultType="board">
<![CDATA[
SELECT * FROM board where seq <=#{seq}
]]>
</select>
SQL 대문자로 설정하기
Mapper 파일에 등록되는 SQL 구문은 일반적으로 대문자로 작성한다. 사실 SQL 구문은 대소문자를 구분하지 않는다. 따라서 어떻게 작성하든 상관없다. 하지만 파라미터들을 바인딩할 때 대부분 칼럼명과 변수명이 같으므로 SQL구문이 조금이라고 복잡해지면 이 둘을 구분하기가 쉽지 않다. 따라서 SQL은 모두 대문자로 표현하여 좀 더 식별성을 높인다. 다음 두 소스를 비교해보면 SQL 구문이 대문자로 표현된 것이 좀 더 보기 쉽다.
<update id="updateBoard" >
update board set
title=#{title},
content=#{content}
where seq=#{seq}
</update>
<update id="updateBoard" >
UPDATE BOARD SET
TITLE=#{title},
CONTENT=#{content}
WHERE SEQ=#{seq}
</update>
Mybatis JAVA API
SqlSessionFactoryBuilder 클래스
mybatis mapper 설정이 끝났으면 이제 남은 작업은 MyBatis 프레임워크에서 제공하는 API를 이용해서 DAO 클래스를 구현하는 것이다. MyBatis로 DAO 클래스의 CRUD 메소드를 구현하려면 MyBatis에서 제공하는 SqlSession 객체를 사용해야한다. 그런데 SqlSession 객체는 SqlSessionFactory로부터 얻어야 하므로 가장 먼저 해야할 작업은 SqlSessionFactory 객체를 생성하는 일이다.
SqlSessionFactory 객체를 생성하려면 SqlSessionFactoryBuilder의 build() 메소드를 이용하는데, build() 메소드는 Mybatis 설정 파일(sql-map-config.xml)을 로딩하여 SqlSessionFactory 객체를 생성한다.
그리고 sql-map-config.xml 파일을 로딩하려면 입력 스트림인 Reader 객체가 필요하다. Reader 객체는 Resource 클래스의 getResourceReader() 메소드를 사용하여 얻어낼 수 있다. 다음은 SqlSessionFactory 객체를 생성하는 데 사용된 자바 코드다.
Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory 클래스
SqlSessionFactory는 이름에서 알 수 있듯이 SqlSession 객체에 대한 공장 역할을 수행한다. SqlSessionFactory 객체는 openSession() 이라는 메소드를 제공하며, 이 메소드를 이용해서 SqlSession 객체를 얻을 수 있다. 이렇게 얻어낸 SqlSession 객체를 통해 다음과 같이 글 등록 기능을 처리할 수 있다.
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sessionFactory.openSession();
session.insert("BoardDAO.insertBoard",vo);
Mybatis를 사용하여 DB 연동을 간단하게 처리하려면 최종적으로 Mybatis가 제공하는 SqlSession 객체를 사용해야 한다. 따라서 모든 DAO 클래스에서 좀 더 쉽게 SqlSession 객체를 획득할 수 있도록 공통으로 제공할 유틸리티 클래스를 만드는 것이다.
package com.mybatis.pro;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionFactoryBean {
private static SqlSessionFactory sessionFactory = null;
static {
try {
if(sessionFactory==null) {
Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSessionInstance() {
return sessionFactory.openSession();
}
}
유틸리티 클래스 작성
Mybatis를 사용하여 DB 연동을 간단하게 처리하려면 최종적으로 Mybatis가 제공하는 SqlSession 객체를 사용해야 한다. 따라서 모든 DAO 클래스에서 좀 더 쉽게 SqlSession 객체를 획득할 수 있도록 공통으로 제공할 유틸리티 클래스를 만드는 것이다.
package com.mybatis.pro;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionFactoryBean {
private static SqlSessionFactory sessionFactory = null;
static {
try {
if(sessionFactory==null) {
Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSessionInstance() {
return sessionFactory.openSession();
}
}
물론 지금은 이런 유틸리티 클래스를 우리가 직접 작성하지만, 나중에 Mybatis를 스프링과 연동할 때는 프레임워크에서 제공하는 클래스를 사용하면 된다.
SqlSession 객체
SqlSession 객체는 Mapper XML에 등록된 SQL을 실행하기 위한 다양한 API를 제공한다.
selectOne() 메소드
selectOnd() 메소드는 오직 하나의 데이터를 검색하는 SQL 구문을 실행할 때 사용한다. 따라서 getBoard() 같은 단 건 조회용 메소드를 구현할 때 사용할 수 있다. 쿼리가 한 개의 레코드만 리턴되는지 검사하므로 만약 쿼리의 수행 결과러 두 개 이상의 레코드가 리턴될 때는 예외가 발생한다.
public Object selectOne(String statemenet)
public Object selectOne(String statement, Object parameter)
selectOne() 메소드의 statement 매개변수는 MapperXML 파일에 등록된 SQL의 아이디이다. 이때, SQL의 아이디를 네임스페이스와 결합하여 지정해야 한다. 그리고 실행될 SQL 구문에서 사용할 파라미터 정보를 두 번째 인자로 지정하면 된다.
selectList() 메소드
selectList() 메소드는 여러 개의 데이터가 검색되는 SQL 구문을 실행할 때 사용한다. 매개변수의 의미는 selectOne() 메소드와 같다.
public List selectList(String statemenet)
public List selectList(String statement, Object parameter)
insert(), update(), delete() 메소드
insert(), update(), delete() 메소드는 각각 INSERT, UPDATE, DELETE SQL 구문을 실행할 때 사용한다. 각각의 메소드는 실행된 SQL 구문으로 인해 몇 건의 데이터가 처리되었는지를 리턴한다.
public int insert(String statement, Object parameter)
public int update(String statement, Object parameterObject) throws SQLException
public int delete(String statement, Object parameterObject) throws SQLException
다음은 SqlSession 객체를 이용하여 구현한 BoardDAO 클래스의 전체 소스다.
package com.mybatis.pro;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
public class BoardDAO {
private SqlSession mybatis;
public BoardDAO() {
mybatis = SqlSessionFactoryBean.getSqlSessionInstance();
}
public void insertBoard(BoardVO vo) {
mybatis.insert("BoardDAO.insertBoard",vo);
mybatis.commit();
}
public void updateBoard(BoardVO vo) {
mybatis.insert("BoardDAO.updateBoard",vo);
mybatis.commit();
}
public void deleteBoard(BoardVO vo) {
mybatis.insert("BoardDAO.deleteBoard",vo);
mybatis.commit();
}
public BoardVO getBoard(BoardVO vo) {
return (BoardVO)mybatis.selectOne("BoardDAO.getBoard",vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
return mybatis.selectList("BoardDAO.getBoardList",vo);
}
}
스프링과 Mybatis 연동
라이브러리 내려받기
Mybatis는 스프링 쪽에서 연동에 필요한 API를 제공하지 않으며, 오히려 Mybatis에서 스프링 연동에 필요한 API를 제공한다. 따라서 스프링과 Mybatis를 연동하려면 Mybatis에서 제공하는 다음과 같은 클래스들을 이용해서 연동해야 한다.
- org.mybatis.spring.SqlSessionFactoryBaen
- org.mybatis.spring.SqlSessionTemplate
스프링과 Mybatis 연동에 필요한 라이브러리들을 내려받으려면 pom.xml 파일에 다음과 같음 <dependency>를 추가한다.
pom.xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
<!-- Mybatis Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.4</version>
</dependency>
Mybatis 설정 파일 복사 및 수정
스프링과 Mybatis를 연동하려면 Mybatis 메인 환경설정 파일인 sql-map-config.xml과 SQL명령어들이 저장되어 있는 Mapper 파일이 필요하다. 따라서 MybatisProject에서 작성했던 XML 설정 파일들을 복사하여 BoardWeb 프로젝트의 src/main/resource 소스 폴더에 추가 한다. 이 중에서 Mybatis 메인 환경설정 파일인 'sql-map-config.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>
<!-- Alias 설정 -->
<typeAliases>
<typeAlias type="com.mybatis.pro.BoardVO" alias="board"></typeAlias>
</typeAliases>
<!-- Sql Mapper 설정 -->
<mappers>
<mapper resource="mappings/board-mapping.xml" />
</mappers>
</configuration>
데이터 소스는 스프링 프레임워크에서 이미 등록하여 사용하고 있었다. 그리고 이 데이터 소스는 DB 연동뿐만 아니라 트랜잭션 처리처럼 다양한 곳에서 사용할 수 있으므로 Mybatis 설정이 아닌 스프링 설정 파일에서 제공하는 것이 맞다. 그리고 SQL 명령어가 저장된 Mapper XML 파일은 수정 없이 그대로 재사용하면 된다.
스프링 연동 설정
스프링과 Mybatis를 연동하려면 우선 스프링 설정 파일에 SqlSessionFactoryBean 클래스를 Bean 등록해야 한다. 그래야 SqlSessionFactoryBean 객체로부터 DB 연동 구현에 사용할 SqlSession 객체를 얻을 수 있다.
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:sql-map-config.xml"/>
</bean>
MybatisProject에서는 SqlSession 객체를 얻기 위해 SqlSessionFactoryBean 클래스를 유틸리티 클래스로 직접 구현했었다. 하지만 이 클래스를 Mybatis에서 제공하므로 굳이 작성할 필요 없이 스프링 설정 파일에 <bean>등록하면 된다.
SqlSessionFactoryBean 객체가 SqlSession 객체를 생산하려면 반드시 DataSource와 SQL Mapper 정보가 필요하다. 따라서 앞에 등록된 DataSource를 Setter인젝션으로 참조하고, SQL Mapper가 등록된 sql-map-config.xml 파일도 Setter 인젝션으로 설정해야 한다. 그래야 <bean> 등록된 SqlSessionFactoryBean이 SqlSessionFactory 객체를 생성할 수 있다.
DAO 클래스 구현 - 방법 1
Mybatis를 이용하여 DAO 클래스를 구현하는 방법은 두가지 이다. 이 중에 첫 번재는 SqlSessionDaoSupport 클래스를 상속하여 구현하는 것이다.
package com.springbook.biz.board.impl;
import java.util.List;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;
import com.springbook.biz.board.BoardVO;
public class BoardDAOMybatis extends SqlSessionDaoSupport{
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public void insertBoard(BoardVO vo) {
getSqlSession().insert("boardDAO.insertBoard",vo);
}
public void updateBoard(BoardVO vo) {
getSqlSession().insert("boardDAO.updateBoard",vo);
}
public void deleteBoard(BoardVO vo) {
getSqlSession().insert("boardDAO.deleteBoard",vo);
}
public BoardVO getBoard(BoardVO vo) {
return (BoardVO)getSqlSession().selectOne("BoardDAO.getBoard", vo);
}
public List<BoardVO> getBoardList(BoardVO vo){
return getSqlSession().selectList("BoardDAO.getBoardList",vo);
}
}
SqlSessionDaoSupport 클래스를 상속한 후에 가장 먼저 한 작업이 setSqlSessionFactory() 메소드를 재정의 한 것이다.
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
재정의한 setSqlSessionFactory() 메소드 위에 @Autowired를 붙였는데 이렇게 하면 스프링 컨테이너가 setSqlSessionFactory() 메소드를 자동으로 호출한다. 이때, 스프링 설정 파일에 <bean> 등록된 SqlSessionFactoryBean객체를 인자로 받아 부모인 SqlSessionDaoSupport에 setSqlSessionFactory() 메소드로 설정해준다.
이렇게 해야 SqlSessionDaoSuport 클래스로부터 상속된 getSqlSession() 메소드를 호출하여 SqlSession 객체를 리턴받을 수 있다. 이제 SqlSession 객체의 CRUD 관련 메소드를 이용하여 DB 연동 처리하면 된다.
DAO 클래스 구현 -방법2
Mybatis를 이용하여 DAO클래스를 구현하는 두번째 방법은 SqlSessionTemplate 클래스를 <bean> 등록하여 사용하는 것이다. 스프링 설정 파일에서 SqlSessionTemplate 클래스를 SqlSessionFactoryBean 아래에 <bean> 등록한다.
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:sql-map-config.xml"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSession"></constructor-arg>
</bean>
Mybatis 연동 테스트
boardDAOMybatis 객체를 의존성 주입할 수 있도록 BoardServiceImpl 클래스를 다음과 같이 수정하고 테스트 클라이언트 프로그램을 실행하여 결과를 확인한다.
@Service("boardService")
public class BoardServiceImpl implements BoardService{
@Autowired
private BoardDAOMybatis boardDAO;
Dynamic SQL으로 검색 처리
Mybatis는 SQL의 재사용성과 유연성을 향상하고자 Dynamic SQL을 지원한다. Dynamic SQL을 사용하면 조건에 따라 다양한 쿼리를 데이터베이스에 전송할 수 있다.
Dynamic SQL 적용 전
만약 현재 상태에서 검색 기능을 추가한다고 하면 우선 다음과 같이 두 개의 검색 관련 쿼리가 필요할 것이다.
<select id="getBoardList_T" resultMap="boardList" >
SELECT * FROM board
WHERE TITLE '%'||#{searchKeyword}||'%'
ORDER BY SEQ DESC
</select>
<select id="getBoardList_C" resultMap="boardList" >
SELECT * FROM board
WHERE CONTENT '%'||#{searchKeyword}||'%'
ORDER BY SEQ DESC
</select>
제목 검색과 내용 검색을 처리하기 위해 두 개의 쿼리를 등록했으면, 이제 DAO 클래스 getBoardList() 메소드에 검섹 조건에 따른 분기 처리 로직을 추가한다.
public List<BoardVO> getBoardList(BoardVO vo){
if(vo.getSearchCondition().equals("TITLE")) {
return mybatis.selectList("BoardDAO.getBoardList_T",vo);
}else {
return mybatis.selectList("BoardDAO.getBoardList_C",vo);
}
}
Dynamic SQL 적용 후
<select id="getBoardList" resultMap="boardList" >
SELECT *
FROM BOARD
WHERE 1= 1
<if test="searchCondition == 'TITLE'">
AND TITLE LIKE '%'||#{searchKeyword}||'%'
</if>
<if test="searchCondition == 'CONTENT'">
AND CONTENT LIKE '%'||#{searchKeyword}||'%'
</if>
ORDER BY SEQ DESC
</select>
public List<BoardVO> getBoardList(BoardVO vo){
return mybatis.selectList("BoardDAO.getBoardList",vo);
}
JPA 개념
우리가 사용하는 대부분 프로그램은 사용자가 입력한 데이터나 비즈니스 로직 수행 결과로 얻은 데이터를 재사용할 수 있도록 데이터베이스에 저장한다. 하지만 자바의 객체와 데이터베이스의 테이블이 정확하게 일치하지 않는다. 따라서 둘 사이를 매핑하기 위해 많은 SQL 구문과 자바 코드가 필요할 수밖에 없다.
ORM은 이렇게 정확히게 일치하지 않는 자바 객체와 테이블 사이를 매핑해준다. 다시 말하면 ORM은 자바 객체에 저장된 데이터를 테이블의 ROW 정보로 저장하고 반대로 테이블에 저장된 Row 정보를 자바 객체로 매핑해준다. 이 과정에서 사용되는 SQL 구문과 자바 코드는 ORM 프레임워크가 자동으로 만들어준다.
ORM 프레임워크의 가장 큰 장점은 DB 연동에 필요한 SQL을 자동으로 생성한다는 것이다. 또한, 이렇게 생성되는 SQL은 DBMS가 변경될 때 자동으로 변경된다. 다만 ORM 환경설정 파일 어딘가에 DBMS가 변경되었다는 것만 수정해주면 된다.
Hibernate는 완벽한 ORM 프레임워크이며 자바 객체와 테이블의 ROW를 매핑하는 역할을 수행한다. 이렇게 편리한 Hibernate는 오랜 시간을 거치면서 기능도 추가되고 성능도 향상되어 ORM의 대표 프레임워크가 되었다. 하지만 Hibernate말고도 TopLink나 Cocobase같은 다른 ORM 프레임워크들도 하나씩 등장하기 시작했다. 그래서 이런 ORM 프레임워크들에 대한 표준화 작업이 필요했고 그런 노력의 결과가 JPA인 것이다.
JPA의 특징
JPA는 모든 ORM 구현체들의 공통 인터페이스를 제공한다. JPA를 JDBC API와 비교하여 이해하면 편하다. JDBC는 특정 DBMS에 종속되지 않는 DB 연동 로직을 구현할 때, JDBC API(java.sql)의 인터페이스들을 이용하면 실질적인 DB 연동처리는 해당 DBMS의 드라이버 클래스들이 담당하는 구조이다. 따라서 DBMS가 변경되는 상황에서도 드라이버만 변경하면 JDBC API를 이용하는 애플리케이션은 수정하지 않는다. 결국, 개발 당시에는 MySQL을 사용하다가 실제 서비스에서는 Oracle로 변경할 수도 있다.
JPA도 JDBC와 마찬가지이다. 애플리케이션을 구현할 때, JPA API를 이용하면 개발 당시에는 Hibernate를 ORM프레임워크로 사용하다가 실제 서비스가 시작될 때는 TopLink로 변경할 수 있다.
JPA 프로젝트 생성
- [File]->[New] 메뉴에서 [Maven Projec]를 차례로 선택한다.
- maven-archetype-quickstart를 선택한다.
- Group Id: com.springbook.biz.board
- Artifact Id: JPAProject
- Version: 0.0.1-SNAPSHOT
- package: com.springbook.biz.board
[Project Facets] 탭에서 다음과 같이 JPA항목을 체크하면, Maven Project가 JPA 프로젝트로 변경되고 src/main/java 소스폴더에 META-INF 폴더와 그 아래에 JPA 환경설정 파일인 persistence.xml이 생성된 것을 확인할 수 있다.
JPA 라이브러리 내려받기
프로젝트 JPA 관련 라이브러리들을 추가하기 위해서 pom.xml 파일을 수정한다. 데이터베이스에 경우 External jar로 직접 ojdbc를 추가했다.
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.springbook.biz.board</groupId>
<artifactId>JPAProject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>JPAProject</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<!-- JPA, 하이버네이트 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.1.0.Final</version>
</dependency>
<!-- 데이터베이스 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
JPA 시작하기
엔티티 클래스 매핑
JPA를 이용하는 간단한 프로젝트를 통해서 JPA를 구성하는 요소들이 어떤 것들이 있는지 확인해보도록 하자. 가장 먼저 할 일은 데이터베이스의 테이블과 매핑될 영속 클래스를 작성하여 매핑 관련 설정을 추가하는 것이다. 엔티티 클래스를 작성하는데 특별한 제약조건이나 규칙이 있는 것은 아니므로 일반적인 VO 클래스를 만들 때처럼 작성하면 된다. 하지만 될 수 있으면 이클립스에서 제공하는 JPA Entity 생성 기능을 이용하자.
Board 클래스를 생성하면 /META-INF/persistence.xml 파일에 다음과 같이 Board라는 엔티티 클래스가 자동으로 등록된 것을 확인할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="JPAProject">
<class>com.springbook.biz.board.JPAProject.Board</class>
</persistence-unit>
</persistence>
이제 엔티티 클래스에 JPA 매핑 관련 어노테이션을 설정한다. 엔티티 클래스의 모든 멤버 변수를 private으로 선언한다. 특히 일반적인 프로그램에서는 객체를 식별하기 위해서 유일 식별자를 사용하지는 않지만, 영속 객체가 테이블에 매핑될 때 객체 식별 방법이 필요하므로 유일 식별자를 소유하는 클래스로 작성한다.
package com.springbook.biz.board.JPAProject;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
/**
* Entity implementation class for Entity: Board
*
*/
@Entity
@Table(name="BOARD")
public class Board{
@Id
@GeneratedValue
private int seq;
private String title;
private String writer;
private String content;
@Temporal(TemporalType.DATE)
private Date regDate = new Date();
private int cnt;
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getRegDate() {
return regDate;
}
public void setRegDate(Date regDate) {
this.regDate = regDate;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
@Override
public String toString() {
return "Board [seq=" + seq + ", title=" + title + ", writer=" + writer + ", content=" + content + ", regDate="
+ regDate + ", cnt=" + cnt + "]";
}
}
어노테이션 | 의미 |
@Entity | @Entity가 설정된 클래스를 엔티티 클래스라고 하며, @Entity가 붙은 클래스는 테이블과 매핑된다. |
@Table | 엔티티와 관련된 테이블을 매핑한다. name 속성을 사용하여 BOARD 테이블과 매핑했는데 생략하면 클래스 이름이 테이블 이름과 매핑된다. |
@Id | 엔티티 클래스의 필수 어노테이션으로서, 특정 변수를 테이블의 기본 키와 매핑한다. 예제에서는 seq 변수를 테이블의 SEQ 컬럼과 매핑했다. @Id가 없는 엔티티 클래스는 JPA가 처리하지 못한다. |
@GeneratedValue | @Id가 선언된 필드에 기본 키를 자동으로 생성하여 할당할 때 사용한다. 다양한 옵션이 있지만 @GeneratedValue만 사용하면 데이터베이스에 따라서 자동으로 결정된다. |
Temporal | 날짜 타입의 변수에 선언하여 날짜 타입을 매핑할 때 사용한다. TemporalType의 DATE, TIME, TIMESTAMP 중 하나를 선택할 수 있다. |
매핑 정보가 없는 나머지 필드들은 자동으로 BOARD 테이블의 동일한 칼럼과 매핑된다.
persistence.xml 파일 작성
JPA는 persistence.xml 파일을 사용하여 필요한 설정 정보를 관리한다. 이 설정 파일이 META-INF 폴더 아래에 있으면 별도의 설정없이 JPA가 인식한다.
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="JPAProject">
<class>com.springbook.biz.board.JPAProject.Board</class>
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:XE"/>
<property name="javax.persistence.jdbc.user" value="database"/>
<property name="javax.persistence.jdbc.password" value="database"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<!-- 옵션 속성 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="false"/>
<property name="hibernate.id.new_generator_mappings" value="true"/>
<property name="hibernate.hbm2ddl_auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
persistence.xml 파일은 JPA에서 메인 환경설정 파일이다. persistence.xml파일에는 <persistence>를 루트 엘리먼트로 사용하며, 영속성 유닛(persistence-unit)이 설정되어 있다. 영속성 유닛은 연동할 데이터베이스당 하나의 영속성 유닛을 사용한다.
클라이언트 프로그램 작성
지금까지 작성된 JPA 관련 모든 설정을 저장하고 이 설정에 기포한 클라이언트 프로그램을 구현해보자
package com.springbook.biz.board.JPAProject;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class BoardServiceClient {
public static void main(String[] args) {
// EntityManager 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAProject");
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction tx = em.getTransaction();
try {
// transaction 시작
tx.begin();
Board board = new Board();
board.setTitle("JPA 제목");
board.setWriter("관리자");
board.setContent("JPA 등록 잘 되네요");
//글 등록
em.persist(board);
// 글 목록 조회
String jpql = "select b from board b order by b.seq desc";
List<Board> boardList = em.createQuery(jpql,Board.class).getResultList();
for(Board brd: boardList) {
System.out.println("---> "+brd.toString());
}
// Transaction commit
tx.commit();
}catch (Exception e) {
e.printStackTrace();
// Transaction
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
작성된 클라이언트 프로그램의 구조를 보면 가장 먼저 영속성 유닛(persistence-unit)을 이용하여 EntityManagerFactory 객체를 생성하고 있다. JAP를 이용하여 CRUD 기능을 구현하려면 EntityManager 객체를 사용해야 한다. 그런데 이 EntityManager 객체는 EntityManagerFactory객체로부터 얻어낼 수 있다.