이 문서의 목적은 mybatis를 사용해서 여러개의 테이블에서 데이터를 가져와서 결과 값을 조합 하여 최종 데이터를 만들어 보기 위해서 시작 한 것입니다.
그리고 mybatis 가 정형화 잘 되어 있긴 해도 기억을 더듬거리면서 따라가려면 또 한 참을 헤매고 있어서, 정형화 된 순서를 한 번 만들어 봐야 하겠다고 생각한 점도 있습니다.
예전에 한 번 ibatis 설정에 대한 내용을 올리기는 했습니다.
지금은 mybatis 이네요~
my batis 내용에 대해서도 올렸는 데...
좀 부족 한 것 같아서 다시 진행 해보기로 했습니다.
0. mybatis 다운로드 받기
다운로드를 받아야 겠죠...3.5.10 다운을 받았습니다.
https://github.com/mybatis/mybatis-3/releases
1. 연습 프로그램 만들기
사실, 공식 사이트를 잘 읽어보고 연습하면 젤 좋은 데 성격도 급하고 많은 사람들이 자신만의 길을 잘 가고 있기 때문에 그 중에 몇 개를 골라서 시작 해보면 어떨까 하는 게 저의 생각입니다.
하지만, 어떤 것이 바른 길인지는 아래 사이트에서 확인은 해봐야 겠죠...
https://mybatis.org/mybatis-3/ko/getting-started.html
연습 프로그램을 만들기 위해서 참고 문서[1]에서 DBUtil 을 가지고 와서, 연결을 시킬 것입니다.
그리고 난 다음 참고 문서[4]를 스택오버플로우 글을 번역 해 보면
질문>
GWT에서 iBatis를 사용 하려고 합니다.
테이블은 Airport, Terminals 그리고 Flights 이 데이터베이스에 존재 하고 있는 시나리오 입니다.
Airport 는 다양한 터미널들을 가질 수 있으며, 터미널은 하나의 비행장과 여러대의 비행기를 수용 할 수 있습니다.
그리고 비행기는 하나의 터미널 안에 있겠죠. 그래서 테이블 구조는 아래처럼 만들었습니다.
Airport
-id
-name
-terminal_id
Terminals
-id
-name
-flight_id
Flights
-id
-airline
-terminal_id
select 구문은 다음과 같이 실행 했습니다.
SELECT airport.name AS Airport,
terminals.name AS Terminal,
flights.airline,
FROM airport,
terminals,
flights
WHERE airport.terminal_id = terminals.id
AND terminals.flight_id = flights.id;
위 질의문의 결과물을 가지는 결과에 대한 sql 매핑은 어떻게 해야 할까요?
3개 테이블에서 각각에서 만들어지는 모델 객체의 결과를 저장하는 것이 아니고, 결과 집합이 여러개 테이블에서 가져 오는 것이라 많이 헷갈립니다.
대답:
결과에 맞도록 커스텀 밸류 오브젝트(VO)를 생성 하면 됩니다.
<sqlMap namespace="Arrival">
<resultMap id="Arrival" class="com.flight.vo.Arrival">
<result property="airport" column="Airport" />
<result property="terminal" column="Terminal" />
<result property="airline" column="airline"/>
</resultMap>
<select id="retrieveAllArrivals" resultMap="Arrival.Arrival" >
select airport.name as Airport, terminals.name as Terminal, flights.airline
FROM airport, terminals, flights
WHERE airport.terminal_id = terminals.id
AND terminals.flight_id = flights.id
</select>
</sqlMap>
위의 문서를 확인 해 보았으니 그냥 따라 하면 될 것 같습니다.
한 가지 제약 사항이라면, 만약 회사 내부라면 외부 인터넷을 사용 할 수가 없을 수도 있습니다.
회사 내부라고 생각 하고 진행 합니다.
2. 환경설정 파일 만들기
환경 설정 파일은 mybatis-config 라고 이름을 짓고 나서, 해당 mybatis-config.xml 은 소스 폴더인 resources 밑에 sqlMap 폴더에다 생성 하도록 합니다.
<?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>
<!-- Import external profile -->
<properties resource="application.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${spring.datasource.driver-class-name}"/>
<property name="url" value="${spring.datasource.url}"/>
<property name="username" value="${spring.datasource.username}"/>
<property name="password" value="${spring.datasource.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- To configure Mapper.xl Path to file -->
<mapper resource="com/sky/demo/dao/AccountMapper.xml" />
<!--
<package name="com.bluesky.mybatisstudy.dao"/>
-->
</mappers>
</configuration>
위의 dtd 파일의 영향 때문인지 이클립스에서 빨간 색 오류가 많이 뜨네요... 그냥 무시 하면 될 것 같습니다.
다만 점검 해야 할 부분은 해당 파일을 UTF-8 아스키 형태의 문서가 되면 좋을 것 같네요
해당 당 dtd 파일을 직접 http://mybatis.org/dtd/mybatis-3-config.dtd 에서 받아서저장 한 다음 해당 dtd 를 mybatis-config.xml 파일과 같은 디렉토리에 복사해 넣고 난 다음, 아래 부분을
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
를
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"mybatis-3-config.dtd">
로 변경 해 주었습니다.
그리고 application.properties 파일의 형식은 다음과 같습니다.
datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
jdbc.ipaddr=xxx.xxx.xxx.xxx
jdbc.port=1521
jdbc.service=DB1
datasource.url=jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:DB1
datasource.username=user
datasource.password=userpasswd
음, 그리고 제일 중요 한 것은 datasource.url 프로퍼티 입니다. 이 프로퍼티와
jdbc.ipaddr=xxx.xxx.xxx.xxx
jdbc.port=1521
jdbc.service=DB1
위의 프로퍼티 값들이 datasource.url 프로퍼티 틀리다면, datasource.url 프로퍼티 값을 가져다가 오라클로 바인딩 할 것 입니다.
따라서, datasource.url 프로퍼티 값의 설정이 젤 중요 합니다.
2. 매퍼 파일 만들기
자, 그럼 매퍼 설정을 해 주어야 합니다. 이
솔직히, package 엘리먼트는 뭐 하는 것인지 모르겠습니다. 정확하게는 현재 제가 할려는 일과는 거리가 좀 있어서 그냥 넘기기로 하고
<mappers>
<!-- To configure Mapper.xl Path to file -->
<mapper resource="com/sky/demo/dao/AccountMapper.xml" />
<!--
<package name="com.bluesky.mybatisstudy.dao"/>
-->
</mappers>
위에서 사용 하는 CheckDataMapper.xml 파일의 내용을 만들어 보기로 합니다.
앞으로 어떻게 다시 변경 될 지 모르겠으나, 사람들의 사랑을 받고 있는 라이브러리인지라 쉽게 표준을 흔드는 일은 없을 것이라고 봅니다. ibatis 의 형태를 그대로 가져 온 것을 보면 그렇기도 하구요.
CheckDataMapper.xml 파일의 내용을 다음과 같이 작성 하도록 하겠습니다.
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"mybatis-3-mapper.dtd">
<mapper namespace="com.tobee.biz.v2.check.mapper.ICheckDataMapper">
<resultMap id="CheckData" type="com.tobee.biz.v2.check.vo.CheckData" >
<result property="Team" column="TEAM" />
<result property="Name" column="NM" />
<result property="TpCod" column="TP_COD" />
<result property="ChckNm" column="CHCK_NM" />
<result property="Cod" column="COD" />
<result property="ChckDt" column="CHCK_DT" />
<result property="No" column="NO" />
<result property="OperatorName" column="OPERT_NM" />
...
</resultMap>
<!--<parameterMap id="test_parameterMap" type="com.tobee.biz.v2.check.vo.CheckData" />-->
<!--- 1) CheckData -->
<select id="retrieveCheckData" parameterType="java.util.Map" resultMap="CheckData">
SELECT
A.COD,
A.NO,
TO_DATE (A.CHCK_DT, 'YYYY/MM/DD'),
DECODE (
(SELECT USR_NM
FROM USR
WHERE COD = #{Cod} AND EMPNO = CHCK_NM),
NULL,
CHCK_NM,
(SELECT USR_NM
FROM CMM_USR
WHERE CMP_COD = #{cmpCod} AND EMPNO = EXCV_PBCO_CHCK_1_CMPTNR_NM)
)
AS CHCK_NM,
TP_COD,
(SELECT
COD_NM
FROM
CMM_COD_DETL
WHERE
COD = #{Cod}
AND COD_ID = ID)
AS OPERT_NM
FROM
CHCK_RSLT A,
CHCK_RSLT_IEM B
WHERE
A.COD = B.COD
AND A.COD = #{Cod}
AND A.NO = B.NO
AND A.CHCK_DT BETWEEN #{dateStart,jdbcType=VARCHAR} AND #{dateEnd,jdbcType=VARCHAR}
AND A.SE_COD = B.SE_COD
AND B.RSLT_COD <![CDATA[ <> ]]>'2000'
</select>
</mapper>
parameterMap 을 사용해보려고 했으나, deprecated 된 내용이라고 하기에 그냥 주석 처리 하였고, 맵 형태로 받아 오는 것으로 변경 했습니다.
3. VO 클래스 만들기
위의 질의문을 그냥 Map 으로 받아 온다면, 데이터베이스 컬럼 명의 경우에는 보통 대문자로 쓰기 때문에 getter/setter 값의 모양이 예뻐보이지 않겠죠?
예를 들자면, VO 클래스인 CheckData 에 다음과 같이 똑같이 컬럼 이름을 선언 한 경우, 아래 처럼 만들 수 있을 것입니다.
package com.tobee.biz.v2.selfcheck.vo;
import java.io.Serializable;
public class CheckData implements Serializable {
private static final long serialVersionUID = 292424167983715579L;
private String cmpCod;
private String dateStart;
private String dateEnd;
...
private String TEAM;
private String NM;
public String getCod() {
return cmpCod;
}
public void setCod(String cod) {
this.cod = cod;
}
public String getDateStart() {
return dateStart;
}
public void setDateStart(String dateStart) {
this.dateStart = dateStart;
}
public String getDateEnd() {
return dateEnd;
}
public void setDateEnd(String dateEnd) {
this.dateEnd = dateEnd;
}
...
public String getTEAM() {
return TEAM;
}
public void setTEAM(String tEAM) {
TEAM = tEAM;
}
public String getNM() {
return NM;
}
public void setNM(String nM) {
NM = nM;
}
}
따라서 컬럼이름을 입맛대로 바꾸고 싶다면 resultMap 을 사용해 보는 것도 나쁘지 않을 것 같습니다.
그리고 프로그램 구현 부분에 있는 변수 명과의 투명성 및 일관성을 위해서 많은 고민이 있어야 하겠지만요.
그래서, resultMap 도 한번 다음과 같이 만들어 보았습니다.
<resultMap id="CheckData" type="com.tobee.biz.v2.check.vo.CheckData" >
<result property="TpCod" column="TP_COD" />
<result property="ChckNm" column="CHCK_NM" />
<result property="Cod" column="COD" />
<result property="ChckDt" column="CHCK_DT" />
<result property="No" column="NO" />
<result property="OperatorName" column="OPERT_NM" />
</resultMap>
위의 resultMap 은 아래 질의문과 상관이 있겠습니다.
SELECT
A.COD,
A.NO,
TO_DATE (A.CHCK_DT, 'YYYY/MM/DD'),
DECODE (
(SELECT USR_NM
FROM USR
WHERE COD = #{Cod} AND EMPNO = CHCK_NM),
NULL,
CHCK_NM,
(SELECT USR_NM
FROM CMM_USR
WHERE CMP_COD = #{cmpCod} AND EMPNO = EXCV_PBCO_CHCK_1_CMPTNR_NM)
)
AS CHCK_NM,
TP_COD,
(SELECT
COD_NM
FROM
CMM_COD_DETL
WHERE
COD = #{Cod}
AND COD_ID = ID)
AS OPERT_NM
그럼 위의 클래스는 다음과 같이 바뀌어야 겠죠?
package com.tobee.biz.v2.selfcheck.vo;
import java.io.Serializable;
public class CheckData implements Serializable {
...
private String team;
private String name;
private String CheckData;
private String TpCod;
private String ChckNm;
private String Cod;
private String ChckDt;
private String No;
private String OperatorName;
...
public String getOperatorName() {
return OperatorName;
}
public void setTeam(String OperatorName) {
this.OperatorName = OperatorName;
}
public String getCheckData() {
return CheckData;
}
public void setTeam(String CheckData) {
this.CheckData = CheckData;
}
public String getTpCod() {
return TpCod;
}
public void setTeam(String TpCod) {
this.TpCod = TpCod;
}
...
public String getTeam() {
return team;
}
public void setTeam(String team) {
this.team = team;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4. 매퍼 클래스 만들기
그럼 위에서 정의한 매퍼 파일의 내용 중 어떤 동작을 사용 할 것인지에 대해서 정의 해주는 매퍼 클래스를 만들어 보도록 하겠습니다.
package com.tobee.biz.v2.check.mapper;
import java.util.List;
import java.util.Map;
import com.tobee.biz.v2.check.vo.CheckData;
public interface ICheckDataMapper {
/**
* 1) CheckData
* 가져올거예요 CheckData
* @param paramMap
* @return
*/
public List<CheckData> retrieveCheckData(Map<String,String> paramMap);
}
위의 코드의 내용은 의미 적으로 아래 CheckDataMapper.xml 파일에서 가져와서 본을 뜬 것입니다.
그리고 인터페이스 선언 만으로 가능 하도록 잘 만들어져 있습니다. 대신, CheckDataMapper.xml 파일과 연관성을 잘 생각하고 작성 해야 한다는 것을 잊으면 안 됩니다.
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"mybatis-3-mapper.dtd">
<mapper namespace="com.tobee.biz.v2.check.mapper.ICheckDataMapper">
...
<select id="retrieveCheckData" parameterType="java.util.Map" resultMap="CheckData">
...
</select>
</mapper>
5. 서비스 클래스 만들기
그럼 외부에서 이 데이터를 조작 하기 위해서 인터페이스 처럼 열어 주어야 할 서비스 클래스를 만들 면 될 것입니다.
package com.tobee.biz.v2.selfcheck.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tobee.biz.v2.selfcheck.mapper.ICheckDataMapper;
import com.tobee.biz.v2.selfcheck.vo.CheckData;
import com.tobee.biz_v2.DBUtil;
public class SelfCheckService {
// obtain SqlSession object
private ISelfCheckMapper selfChkMapper= DBUtil.openSession().getMapper(ISelfCheckMapper.class);
/**
* 1) CheckData
* 가져올거예요 CheckData
* @param paramMap
* @return
*/
public List<CheckData> retrieveCheckData(Map<String,String> paramMap)
{
Map<String, String> paramMap = new HashMap<String,String>();
paramMap.put("Cod", Cod);
paramMap.put("dateStart", dateStart);
paramMap.put("dateEnd", dateEnd);
return selfChkMapper.retrieveMissingCalibration(paramMap);
}
}
위의 코드도 마찬가지로 아래 파일과 관련이 당연히 있겠죠?
WHERE
A.COD = B.COD
AND A.COD = #{Cod}
AND A.NO = B.NO
AND A.CHCK_DT BETWEEN #{dateStart,jdbcType=VARCHAR} AND #{dateEnd,jdbcType=VARCHAR}
AND A.SE_COD = B.SE_COD
AND B.RSLT_COD <![CDATA[ <> ]]>'2000'
6. 메인 클래스 만들기
마지막으로 메인 클래스를 하나 만들어 주면서 끝내겠습니다.
package com.tobee.biz.v2.main;
import java.util.Iterator;
import java.util.List;
import com.tobee.biz.v2.selfcheck.service.SelfCheckService;
import com.tobee.biz.v2.selfcheck.vo.SelfCheck;
public class BizMain {
public static void main(String[] args)
{
SelfCheckService selfChkService = new SelfCheckService();
final String Cod = "TobeeCode1234";
final String dateStart = "20220801";
final String dateEnd = "20220831";
/**
* 1) CheckData
* 가져올거예요 CheckData
* @param paramMap
* @return
*/
selfChkList = selfChkService.retrieveCheckData(Cod, dateStart, dateEnd);printSelfChkList(selfChkList);
}
private static void printSelfChkList(List<SelfCheck> selfChkList) {
SelfCheck selfChk = null;
for(Iterator<SelfCheck> iter = selfChkList.iterator(); iter.hasNext();)
{
selfChk = iter.next();
System.out.println(selfChk.getOperatorName() );
System.out.println(selfChk.getCheckData() );
System.out.println(selfChk.getTpCod() );
}
}
}
MyBatis 3 를 사용해서 여러 테이블에서 질의한 값을 하나의 VO 오브젝트로 저장한 리스트를 받아서 확인하는 과정을 진행 해 보았습니다.
이상.
참고 문서들
[1]https://javamana.com/2021/01/20210128182455104x.html
[2]https://stackoverflow.com/questions/33067816/mybatis-3-query-from-tables-without-relationship
[3]https://haenny.tistory.com/372
[4]https://stackoverflow.com/questions/4363808/how-do-you-handle-sql-mapping-for-multiple-tables-in-ibatis-and-java
[5]https://stackoverflow.com/questions/56789700/mybatis-how-to-join-multiple-columns-from-same-table
[6]https://stackoverflow.com/questions/56078196/using-mybatis-to-map-multiple-tables-to-a-single-collection
[7]https://stackoverflow.com/questions/28429324/how-to-map-multiple-beans-in-mybatis