Java EE 정리 27
04 Apr 2019
Reading time ~6 minutes
Java EE 정리 27 - MyBatis(2)
MyBatis 복습
- 영쓰 XML 사용 정리
- 설정 XML에서 Driver, DBServerURL, ID, Password를 설정하고 이미 만들어준 Mapper들을 연결 * 설정정보는 Properties로 넣거나 직접 Hard Coding
- MyBatis 사용을 위해 설정 XML과 연결된 스트림을 만듦
- SqlSessionFactoryBuilder 객체를 만들 새로 만들고 설정 XML과 연결된 스트림으로 SqlSessionFactory 객체를 build()로 생성
- 그리고 만들어진 SqlSessionFactory 객체를 이용, MyBatis Handler인 SqlSession을 openSession()으로 얻는다.
- SqlSession을 사용하여 insert, update, delete, select 등 작업을 수행
MyBatis Logging
- XML안에 쿼리가 있기 때문에 쿼리를 볼 수 없다.
- logging library를 사용하면 Log를 찍어볼 수 있다.
- MyBatis에서 지원하는 Log4j Logging을 사용
- classpath에
log4j.properties
란 파일을 생성
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
# log4j.logger.org.mybatis.example.BlogMapper=TRACE 추척할 녀석인데 안써도 됨
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- Log4J 설치
- LOG 레벨
- 로그를 출력하게 되는 시점
- TRACE > DEBUG > INFO > WARNING > ERROR > FATAL
- TRACE - 쿼리가 실행만 되면 출력
- DEBUG - Exception 떨어졌을 때 쿼리를 출력
- FATAL - 심각한 오류가 있을 때 출력
- 로그 사용 시 장점은 프로그램과 에러 출력 코드를 분리할 수 있다.
- 코드가 간결해진다.
- 출력 시점을 마음대로 변경할 수 있다.
# Global logging configuration
log4j.rootLogger=TRACE, stdout
# 로그 사용 시 장점 : 프로그램과 에러 출력 코드를 분리할 수 있다
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- 사용할땐 useLog4JLogging() 호출
public class UseMyBatis {
public UseMyBatis() { // 생성자에 로깅객체를 생성
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
}
...
- 로그를 파일로 저장하는 Logging 설정파일
# Global logging configuration
log4j.rootLogger=TRACE, file,stdout
# MyBatis logging configuration...
#log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=C:/dev/mybatis.log
log4j.appender.Threshold=DEBUG
log4j.appender.file.Append=true
log4j.appender.file.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p], %d{yyyy-MM-dd HH:mm:ss}, %m %n
log4j.appender.file.encoding=UTF-8
MyBatis Properties
- XML에 접속 정보를 집어넣어 사용하면 값을 value에 넣게 됨(Hard Coding)
- 배포되고 운용할 때 “를 지우는 등 잘못 변경해서 문제가 발생하면 개발자가 직접 찾아가서 고쳐줘야한다..
- Properties를 사용하면 사용자가 코드를 변경하지 않고 properties파일만 변경하면 됨
- 접속정보를 저장할 database_properties 파일 생성 후 UTF-8로 변경
- properties는 “이름=값”의 형태만 가진 설정파일
- 설정 정보를 가장 간단하게 저장할 수 있는 형식
- 공백을 설정하면 값을 가져가는 곳에도 공백이 포함되므로 절대 공백을 사용하지 않는다
- data_properties
- 사용자는 설정파일(mybatis_config.xml)을 안건드리고 properties 파일만 변경하면 된다.
driver=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
userid=scott
userpass=tiger
- mybatis_config.xml
- DTD를 확인하면 properties는 가장 먼저 오고 ?(0 또는 1개 존재)
...
<configuration>
<properties resource="properties.database_properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userid}"/>
<property name="password" value="${userpass}"/>
</dataSource>
</environment>
</environments>
...
MyBatis를 사용한 다양한 SELECT 예(1)
- MyBatis SELECT 예제들
- 컬럼 하나에 레코드 하나 조회할 때
- 컬럼 여러개 레코드 하나 조회할 때
- 컬럼 하나에 레코드 여러개 조회할 때
- 컬럼 여러개 레코드 여러개 조회할 때
- > 의 조회
- < 의 조회
- 동일 쿼리의 처리
- LIKE 의 조회
- subquery 의 조회
- union 의 조회
- join 의 조회
- join + subquery 의 조회
- 컬럼명 또는 테이블명이 동적일 때 조회
- dynamic query : if, choose, foreach
- 예제를 위한 패키지 생성 후 DAO, mybatis_config.xml을 생성
- mybatis_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 resource="properties.database_properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userid}"/>
<property name="password" value="${userpass}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="kr/co/sist/exam/mapper/exam_mapper1.xml"/>
<mapper resource="kr/co/sist/exam/mapper/exam_mapper2.xml"/>
</mappers>
</configuration>
- DAO는 싱클턴으로 객체를 만들고 SqlSessionFactory를 반환하는 메소드를 만들어 둔다.
public class MyBatisDAO {
private static MyBatisDAO mb_dao; // 싱글턴
private SqlSessionFactory ssf;
public static MyBatisDAO getInstance() {
if(mb_dao == null) {
org.apache.ibatis.logging.LogFactory.useLog4JLogging(); // 로깅 사용
mb_dao = new MyBatisDAO();
}
return mb_dao;
}
public synchronized SqlSessionFactory getSessionFactory() {
// SqlSessionFactory는 Thread에 한개만 생성되서 사용, synchronized로 동시접근을 막는다.
Reader r = null;
try {
// 1. 설정용 XML파일 연결 스트림 생성
r = Resources.getResourceAsReader("kr/co/sist/exam/dao/mybatis_config.xml");
// 2. SqlSessionFactoryBuilder 생성
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
// 3. 연결스트림을 이용, DB와 연동 된 SqlSessionFactory 객체를 받는다.
ssf = ssfb.build(r);
if (r != null) { r.close(); } // SqlSessionFactory와 연결 시킨 스트림은 끊는다.
} catch (IOException e) {
e.printStackTrace();
}
return ssf;
}
}
- MyBatis를 이용하면 DBCP와는 다르게 Tomcat없이 단위테스트를 할 수 있다.
// MyBatisDAO
...
public static void main(String[] args) { // 단위테스트용
System.out.println(MyBatisDAO.getInstance().getSessionFactory());
}
}
- 기존 예제 메뉴바를 뜯어고치고..
- SELECT 수행 결과를 보여줄 main.jsp를 만듦
- page 파람값에 따라 다른 페이지를 import
<!-- main.jsp -->
...
<c:if test="${ not empty param.page }">
<c:import url="/date0404/${ param.page }.jsp"/>
</c:if>
...
컬럼 하나에 레코드 하나 조회할 때
- MyBatis에서는 Java의 데이터형(기본형, 참조형)을 그대로 사용할 수 있다.
<!-- exam_mapper1.xml -->
<mapper namespace="kr.co.sist.exam1">
<!-- 컬럼 하나에 레코드 하나 조회할 때 -->
<select id="singleColumn" resultType="String">
SELECT dname
FROM dept
WHERE deptno=#{deptno}
</select>
...
// MyBatisDAO
...
public String selectSingleColumn(int deptno) {
String dname = "";
// MyBatis Handler를 사용하여 Mapper(xml)에 있는 ID를 찾고 Parsing 하여
// 조회된 결과를 얻는다.
MyBatisDAO.mb_dao = MyBatisDAO.getInstance();
SqlSession ss = mb_dao.getSessionFactory().openSession();
dname = ss.selectOne("singleColumn", deptno);
ss.close();
return dname;
}
...
public class MyBatisService { // Controller에서 Serivce 메서드들을 사용
public String deptName(int deptno) {
MyBatisDAO mb_dao = MyBatisDAO.getInstance();
String dname ="";
dname = mb_dao.selectSingleColumn(deptno)+"부서";
return dname;
}
}
<!-- single_column.jsp -->
<%@page import="kr.co.sist.exam.service.MyBatisService"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
MyBatisService mbs = new MyBatisService();
int deptno = 10;
String dname = mbs.deptName(deptno);
%>
<%=deptno%>번 부서는 <strong><%=dname %></strong>입니다.
컬럼 하나에 레코드 여러개 조회할 때
<!-- exam_mapper1.xml -->
...
<select id="multiRow" resultType="integer">
SELECT deptno
FROM dept
</select>
...
//MyBatisDAO
...
public List<Integer> multiRow() {
List<Integer> list = null;
SqlSession ss = getInstance().getSessionFactory().openSession();
list = ss.selectList("multiRow");
ss.close();
return list;
}
...
//MyBatisService
...
public List<Integer> multiRow() {
List<Integer> list = null;
MyBatisDAO mb_dao = MyBatisDAO.getInstance();
list = mb_dao.multiRow();
return list;
}
...
<!-- main_menu.jsp -->
...
<li><a href="main.jsp?page=single_column">컬럼 하나에 레코드 여러개 조회</a></li>
...
<!-- multi_row.jsp -->
<%@page import="java.util.List"%>
<%@page import="kr.co.sist.exam.service.MyBatisService"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
MyBatisService mbs = new MyBatisService();
List<Integer> deptnoList = mbs.multiRow();
pageContext.setAttribute("deptnoList", deptnoList);
%>
<label>부서번호</label>
<select name="deptno">
<c:forEach var="deptno" items="${ deptnoList }">
<option value="${ deptno }"><c:out value="${ deptno }" escapeXml="false"/></option>
</c:forEach>
</select>
컬럼 여러개에 레코드 하나 조회할 때
- typeAlias 노드 : Domain이나 VO를 미리 등록해두고 짧은 이름으로 사용할 때 사용
- 여러번 사용될 때 좋음, 한번만 사용되면 안쓰는게 좋다
- iBATIS에선 mapper에 정의
- MyBatis에선 설정파일(mybatis_config.xml)에 정의
<!-- mybatis_config.xml -->
...
<configuration>
<properties resource="properties/database_properties"></properties>
<typeAliases>
<typeAlias type="kr.co.sist.exam.domain.DeptInfo" alias="di" />
</typeAliases>
...
- Framework 사용 시 생성자 안만들고 getter/setter만 만든다
public class DeptInfo {
private String dname, loc;
// 생성자 안만들고 getter랑 setter만 만듦
...
- 조회되는 컬럼은 대소문자를 구분하지 않지만 setter method는 대소문자를 구분
- iBATIS에서는 컬럼명 또는 컬럼명 as setter명
- resultType값은 패키지명.Domain명 또는 typeAlias의 id가 사용됨
<mapper namespace="kr.co.sist.exam1">
...
<!-- 컬럼 하나에 레코드 여러개 조회할 때 -->
<select id="multiColumn" resultType="di">
SELECT dname, loc
FROM dept
WHERE deptno=10
</select>
...
// MyBatisDAO
...
public DeptInfo multiColumn() {
DeptInfo di = null;
MyBatisDAO.mb_dao = MyBatisDAO.getInstance();
SqlSession ss = mb_dao.getSessionFactory().openSession();
di = ss.selectOne("multiColumn");
ss.close();
return di;
}
...
// MyBatisService
...
public DeptInfo deptInfo() {
MyBatisDAO mb_dao = MyBatisDAO.getInstance();
DeptInfo di = mb_dao.selectMultiColumn();
return di;
}
}
<!-- main_menu.jsp -->
...
<li><a href="http://localhost:8080/mybatis_prj/date0404/main.jsp?page=multi_column">컬럼 여러개에 레코드 하나 조회</a></li>
...
<!-- multi_column.jsp -->
<%@page import="kr.co.sist.exam.domain.DeptInfo"%>
<%@page import="kr.co.sist.exam.service.MyBatisService"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
MyBatisService mbs = new MyBatisService();
DeptInfo di = mbs.deptInfo();
%>
10번 부서정보<br/>
부서명 : <strong><%= di.getDname() %></strong>
위치 : <strong><%= di.getLoc() %></strong>