Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 웹개발 기초
- 프로그래밍 기초
- 깃허브
- oracle
- 마크다운
- java
- 도커
- docker 소개
- VS Code
- dql
- ORACLE 기초
- 쿠버네티스
- view
- 쿠버네티스 기본 개념
- DB 모델링
- 기초 선택자
- 정보처리기사
- DB
- Flutter
- MVC 패턴
- github
- SQL
- DB 개요
- 기본 API
- docker
- java 기초
- 필기
- mybatis
- 데이터베이스
- DDL
Archives
- Today
- Total
핑구
02. MyBatis 본문
📅 2021.12.09 ~ 2021.12.14
MyBatis
- 데이터의 입력, 조회, 수정, 삭제(CRUD)를 보다 편하게 하기 위해 xml로 구조화한 Mapper 설정 파일을 통해 JDBC를 구현한 영속성 프레임 워크이다. 기존 JDBC를 이용해 구현했던 코드와 파라미터 설정 및 결과 매핑을 xml설정을 통해 쉽게 구현할 수 있게 하였다.
- 특징: 쿼리의 태그화, 간단한 페이징 처리, 동적 쿼리
MyBatis 흐름
- 전용 라이브러리를 이용하여 JDBC Template을 대체하여 동작한다.
Controller → Service → DAO → DB
↓ ↑
Mybatis 설정파일
MyBatis 동작 구조 ★★★
- MyBatis의 경우 DB에 접근하기 위해 최종적으로 필요한 객체는 SqlSession이다. 해당 객체는 다음과 같은 순서로 생성된다.
SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession - SqlSessionFactoryBuilder를 이용하여 SqlSessionFactory를 생성하는 경우에는 build() 메소드가 필요하며, 생성 시 mybatis-config.xml 파일을 읽어 해당 정보를 이용하여야 하기 때문에 해당 메소드는 매개변수로 inputStream을 사용하여야 한다.
- SqlSessionFactory를 이용하여 SqlSession을 생성하는 경우에는 openSession() 메소드를 사용하여야 한다.
- 쿼리를 실행하는 경우에는 SqlSession의 메소드를 사용하여 실행하는데, 이때 mapper.xml 파일을 참조하여야 한다. 이때 사용되는 메소드는 다음과 같다.
- selectOne: 1개 혹은 0개의 결과만 select하는 경우 사용된다.
- selectList: 0개 혹은 여러 개의 결과를 select하는 경우 사용된다.
- selectMap: select 결과를 Map 방식으로 받아오는 경우 사용된다.
- insert: insert 쿼리에 사용된다.
- update: update 쿼리에 사용된다.
- delete: delete 쿼리에 사용된다.
- MyBatis에서 사용되는 파일
- mybatis-config.xml: MyBatis의 전체 설정 정보를 가지고 있는 파일로 Class의 별칭 설정, DB 연결 설정, sql 구문 경로 설정이 가능하다.
- mapper.xml: sql 쿼리문의 인자 값, 결과 값, 데이터 타입 등을 설정할 수 있는 파일이다. 각 패키지마다 존재하여야 한다.
MyBatis 참고 사항
- Apache project에서 ibatis를 운영하던 팀이 2010년 5월 9일에 Google팀으로 이동하면서 MyBatis로 이름을 바꾸었다. MyBatis는 기존 ibatis의 한계점이었던 동적 쿼리와 어노테이션 처리를 보강하여 더 나은 기능을 제공한다. ibatis는 현재 비활성화 상태이며, 기존에 ibatis로 만들어진 애플리케이션의 지원을 위해 라이브러리만 제공하고 있다. 따라서 둘은 차이점이 존재한다.
ibatis | MyBatis | |
Java 요구 버전 | JDK 1.4 이상 | JDK 1.5 이상 |
패키지 구조 | com.ibatis.* | org.apache.ibatis.* |
사용 용어 | SqlMapConfig sqlMap resultClass |
Configuration Mapper resultType |
동적 쿼리/어노테이션 | X |
mybatis-config.xml
- MyBatis 내장 별칭
Mybatis 타입 Java 자료형 Mybatis 타입 Java 자료형 _byte byte double Double _long long float Float _short short boolean Boolean _int / _integer int date Date _float float map Map _boolean boolean hashmap Hashmap string String list List byte Byte arraylist ArrayList long Long collection Collection short Short iterator Iterator int / integer Integer - xml 파일을 생성하면, 제일 먼저 해당 파일이 config 파일이라는 것을 명시하여야 한다. 아래와 같이 파일 타입을 명시하지 않는 경우에는 해당 파일을 config 파일로 인식하지 못한다.
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
- config 타입으로 지정 후에는 <configuration> 태그를 사용할 수 있으며, 해당 태그에 아래 순서로 태그를 넣어 사용할 수 있다.
(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)
만약 해당 순서를 지키지 않는 경우에는 The content of element type "configuration" must match 에러가 발생한다. - <properties>: 외부에 있는 프로퍼티 파일을 읽어오는 태그로 resource 속성을 이용하여 해당 파일의 경로를 지정해 주어야 한다.
<properties resource="/driver.properties"/>
- <typeAliases>: 타입에 대한 별칭을 지정하는 태그로 내부에 <typeAlias> 태그를 사용하여 타입의 별칭을 지정해 편하게 사용할 수 있다.
- <typeAlias>: type 속성에 별칭을 지정할 타입의 패키지명을 포함한 이름을 작성하고, alias 속성에 별칭으로 사용할 문자열을 작성한다.
- <environments>: DB 연결에 설정에 대한 정보를 설정하는 태그로 <environments> 태그 내부에 1개 이상의 <environment> 태그를 사용할 수 있다. 기본적으로 연결할 <environment>의 id를 default 속성에 설정해 주어야 한다. 그렇지 않는 경우 제대로 연결되지 아않아 오류가 발생한다.
- <environment>: id 속성을 지정하여 구분하며, Database에 연결할 정보가 실질적으로 담기게 되는 태그이다.
- <transactionManager>: 트랜잭션을 관리할 관리자에 대한 설정으로 type 속성을 설정해 주어야 한다. type 속성으로 설정될 수 있는 값은 다음 두 가지가 있다.
- JDBC: JDBC가 commit과 rollback을 직접 처리하는 옵션으로 수동으로 commit을 하게 된다.
- MANAGED: commit이나 rollback을 직접적으로 하지 않는 대신 컨테이너가 트랜잭션의 모든 생명주기를 관리한다. 자동으로 commit이 된다.
- <dataSource>: 실제 DB 접속에 관한 정보들을 넣는 태그로 type 속성은 ConnectionPool 사용 여부에 관한 설정이다. 설정 값으로 입력할 수 있는 값은 다음과 같다.
- POOLED: 사용자의 요청이 들어왔을 때 DB에 연결하여 명령을 실행한다. 명령이 종료되어도 연결이 끊기지 않고 pool에 저장되고, 요청이 올 때마다 pool에서 커넥션을 꺼내 재사용한다. 커넥션 객체를 미리 만들어 놓고 사용, 관리하기 때문에 객체를 만들 수 있는 시간을 줄일 수 있다. 따라서 웹 서비스에서 주로 사용한다.
- UNPOOLED: DB 요청이 올 때마다 실시간으로 커넥션 객체를 만들고 닫아 준다. 미리 커넥션 객체를 만들어 놓는 방식이 아니기 때문에 조금 느릴 수 있다. 따라서 데이터 조회/수정/삭제/삽입 요청 시 딜레이가 생겨도 문제 없는 간단한 애플리케이션의 경우에 적당하다.
- <property>: DB와 연결될 driver, url 등의 정보를 넣을 수 있다. 프로퍼티를 이용하여 값을 가지고 오는 경우 value에 ${프로퍼티 키 값} 형태로 입력해 주어야 한다. 이때 ${}는 해당 xml에서 사용할 수 있는 expression language이며, JSP의 EL과 형태는 같으나 호환되는 것은 아니다.
- <mappers>: mapper 파일을 등록하는 태그로 내부에 <mapper> 태그를 작성할 수 있다.
- <mapper>: resource 속성에 mapper.xml 경로를 지정하여 해당 mapper를 등록한다. 만약 등록이 진행되지 않는 경우에는 어떤 mapper인지 찾을 수 없기 때문에 Mapped Statements collection does not contain value for ~ 오류가 발생한다.
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>
<properties resource="/driver.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="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="/mappers/member-mapper.xml"/>
</mappers>
</configuration>
mapper.xml
- mapper.xml 또한 mybatis-config.xml과 동일하게 타입을 지정해 주어야 한다.
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper>: 가장 상위 태그이며, Mapper를 하나만 사용한다는 보장이 없기 때문에 구분을 위해 namespace를 사용하여 부를 이름을 지정한다.
- <select>: select 쿼리를 작성하는 태그이며, 엔터와 들여쓰기를 사용할 수 있다. 프로퍼티 파일에 쿼리를 작성하는 경우에도 엔터를 사용할 수 있으나, 이전 줄의 끝 부분에 \를 추가로 작성해 주어야 하기 때문에 번거롭다. <select> 태그에서 사용할 수 있는 속성은 다음과 같다.
- id: 외부(DAO)에서 참조하기 위한 쿼리문의 고유 아이디로 구분자의 역할을 한다. DAO에서 selectOne() 메소드를 사용하는 경우 해당 구분자를 이용하여 쿼리를 불러온다.
- parameterType: 받아와 쿼리에서 사용하는 데이터의 타입을 명시해주는 속성으로 패키지 경로를 포함한 전체 클래스명 혹은 별칭을 작성해 주어야 한다. 이때 가지고 와야 하는 부분에는 #{필드명} 형태로 작성한다. ${}도 사용 가능하나, 오류가 발생할 위험성이 있다.
- resultType: 해당 쿼리의 반환 값을 명시해 주는 속성으로, 기존에는 ResultSet을 이용하여 값을 받아온 후, 객체에 저장하는 별도의 과정을 거쳤으나. MyBatis의 경우에는 자동으로 형변환을 진행해 준다. 단, ResultSet의 컬럼명과 객체의 필드명이 완벽하게 일치하여야 사용할 수 있다.
- resultMap: resultType과 동일하게 조회된 결과를 자바에서 사용할 수 있게 하는 속성이며, 컬럼명과 객체의 필드명이 다른 경우 매핑시켜 주어야 할 때 사용한다. 이때 컬럼명과 필드명이 동일한 부분도 작성해 주어야 오류를 방지할 수 있다.
<?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="memberMapper"> <select id="loginMember" parameterType="member.model.vo.Member" resultMap="memberResultSet"> select * from member where user_id = #{userId} and user_pwd = #{userPwd} and m_status = 'Y' </select> <resultMap type="member.model.vo.Member" id="memberResultSet"> <id column="USER_ID" property="userId"/> <!-- 기본 키 --> <result column="USER_PWD" property="userPwd"/> <!-- 일반 컬럼 --> <result column="USER_NAME" property="userName"/> <result column="NICKNAME" property="nickName"/> <result column="EMAIL" property="email"/> <result column="BIRTHDAY" property="birthDay"/> <result column="GENDER" property="gender"/> <result column="PHONE" property="phone"/> <result column="ADDRESS" property="address"/> <result column="ENROLL_DATE" property="enrollDate"/> <result column="UPDATE_DATE" property="updateDate"/> <result column="M_STATUS" property="mStatus"/> </resultMap> </mapper>
- DML 태그의 경우에는 무조건 int 값을 반환하기 때문에 resultType 속성을 int로 정의하면 오류가 발생한다. 따라서 resultType 속성을 작성하면 안 된다.
MyBatis 활용
- SqlSession은 Template 클래스를 만들어 해당 클래스에서 생성 후 사용한다. MyBatis의 경우 commit, rollback, close를 제공하기 때문에 별도의 메소드를 만들지 않고도 사용 가능하다. 따라서 SqlSession만 생성해 주면 된다.
openSession()의 매개변수를 false로 설정하는 경우 자동 커밋을 하지 않겠다는 의미가 된다.public static SqlSession getSqlSession() { SqlSession session = null; try { SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder(); InputStream stream = Resources.getResourceAsStream("/mybatis-config.xml"); SqlSessionFactory ssf = ssfb.build(stream); session = ssf.openSession(false); } catch (IOException e) { e.printStackTrace(); } return session; }
- session.selectOne()은 매개변수가 하나인 것과 두 개인 것이 존재한다. 이때 첫 번째 매개변수는 어느 Mapper에 있는 어떤 쿼리(id)에 접근할 것인지를 의미하고, 두 번째 매개변수는 쿼리로 보낼 데이터를 작성한다. 따라서 보낼 데이터가 존재하지 않는 경우에는 매개변수가 하나인 메소드를 사용하면 된다.
Member member = session.selectOne("memberMapper.loginMember", m);
- 객체를 리턴하여 해당 객체가 null인 경우에 로그인 실패 분기로 넘어갈 수 있으나, throw를 이용하여 에러를 강제 발생시켜 위임하는 방법을 사용할 수도 있다. 에러를 강제 발생시키는 경우에는 Exception 객체를 생성해 주어야 한다. 또한 에러가 발생한 경우에는 비정상 종료가 되기 때문에 session.close() 부분을 실행할 수 없으므로 오류를 강제 발생시키기 전, 먼저 session 객체를 닫아 주어야 한다.
xml 타입 설정
- window → preferences → xml catalog → user specified → add 경로에서 xml 타입 지정을 등록해 놓을 수 있다. 등록하는 경우 xml 파일을 만들 때마다 불러와서 사용할 수 있다.
파일을 등록해서 사용하는 경우에는 <cache-ref namespace=""/> 태그가 자동으로 생성되는데, 해당 태그의 namespace는 mapper의 namespace와 관련이 없다.
페이징 처리
- 여러 페이지에서 페이징 처리를 하는 경우 같은 소스 코드를 반복하여 작성해야 하므로 번거롭다. 따라서 Pagination 클래스를 생성하여 해당 클래스에 페이징 처리 공식을 작성한 후 사용하면 효율적이다.
- MyBatis의 경우 1 페이지 클릭 시 0개를 건너뛰고 boardLimit만큼의 글을 가지고 오는 방식으로 페이징 처리를 진행한다. 이때 건너 뛸 글의 개수와 가져올 글의 개수를 RowBounds 객체에 넣어 준 후 selectList()의 매개변수로 보내 주면 자동으로 해당 범위의 글만 불러오게 된다.
startRow와 endRow를 따로 설정할 필요가 없기 때문에 간편하다.
RowBounds를 생성할 때 필요한 변수로는 offset과 limit가 있다. offset은 몇 개의 게시글을 건너뛸 것인지에 대한 변수이고, limit는 몇 개의 게시글을 가지고 올 것인지에 대한 변수이다.int offset = (pi.getCurrentPage() - 1) * pi.getBoardLimit(); RowBounds rowBounds = new RowBounds(offset, pi.getBoardLimit()); ArrayList<Board> list = (ArrayList)session.selectList("boardMapper.selectBoardList", null, rowBounds);
게시글 상세 보기
- 게시글 상세 보기에서 댓글을 List 형태로 불러와야 하는 경우 left join이 두 번 진행되어야 한다. 또한 resultMap에서는 여러 개의 값을 받아와야 하기 때문에 <result>가 아닌 <collection>을 사용하여 값을 받아온다. 이때 Reply 객체에 대한 <resultMap>은 따로 생성해 주어야 한다.
<select id="selectBoardDetail" parameterType="_int" resultMap="boardResultSet2"> select b.*, bn.nickname b_nick, r.*, rn.nickname r_nick from board b join member bn on(bwriter = bn.user_id) left outer join reply r on(bid = ref_bid) left outer join member rn on(rwriter = rn.user_id) where bid = #{bId} and b_status = 'Y' order by rid desc </select> <resultMap type="Board" id="boardResultSet2"> <id column="BID" property="bId"/> <result column="BTYPE" property="bType"/> <result column="BTITLE" property="bTitle"/> <result column="BCONTENT" property="bContent"/> <result column="BWRITER" property="bWriter"/> <result column="B_NICK" property="nickName"/> <result column="BCOUNT" property="bCount"/> <result column="B_CREATE_DATE" property="bCreateDate"/> <result column="B_MODIFY_DATE" property="bModifyDate"/> <result column="B_STATUS" property="bStatus"/> <collection property="replyList" resultMap="replyResultSet" javaType="arrayList"/> </resultMap> <resultMap type="Reply" id="replyResultSet"> <id column="RID" property="rId"/> <result column="RCONTENT" property="rContent"/> <result column="REF_BID" property="refBid"/> <result column="RWRITER" property="rWriter"/> <result column="R_NICK" property="nickName"/> <result column="R_CREATE_DATE" property="rCreateDate"/> <result column="R_MODIFY_DATE" property="rModifyDate"/> <result column="R_STATUS" property="rStatus"/> </resultMap>
MyBatis 동적 쿼리
- 일반적으로 검색 기능이나 다중 입력 처리 등을 수행해야 하는 경우에 SQL을 실행하는 DAO를 여러 번 호출하거나 batch 기능을 이용하여 버퍼에 담아 한 번에 실행시키는 방식으로 쿼리를 구현한다. MyBatis에서는 이를 동적으로 제어할 수 있는 구문을 제공하여 좀 더 쉽게 쿼리를 구현할 수 있도록 하는 기능을 지원한다.
- <if>: 조건식은 test 속성에 들어가며, else 또는 else if 가 존재하지 않기 때문에 필요로 하는 조건이 1개 이상인 경우에는 if문을 여러 개 사용하여야 한다.
<if test="condition == 'content'"> and bcontent like '%' || #{value} || '%' </if>
- <choose>: switch와 유사한 역할을 하며, switch의 case는 <when>으로, default는 <otherwise>로 작성한다. 주어진 구문 중 한 가지만 실행하고자 하는 경우 사용한다.
<choose> <when test="condition == 'writer'"> and nickname=#{value} </when> </choose>
- 동적 SQL을 작성하는 경우 모든 경우의 수에 맞춰 작성하지 않으면 키워드 누락 등의 오류가 발생할 수 있다. 따라서 모든 경우에 수에 대해 오류가 발생하지 않도록 작성해 주어야 하는데, 이 과정이 번거롭기 때문에 <trim>, <where>, <set> 태그를 사용하여 이를 방지할 수 있다.
- <where>: where을 추가하는 태그이며, 만일 태그 안의 내용이 AND나 OR로 시작할 경우 해당 키워드를 제거한다. 따라서 키워드를 경우에 따라 추가하도록 따로 작성하지 않아도 된다.
- <set>: UPDATE하고자 하는 컬럼을 동적으로 포함시키기 위해 사용하며, set 키워드를 붙이고 불필요한 콤마(,)를 제거하는 역할을 한다.
- <trim>: 태그 안의 내용을 잘라 주는 역할을 하며, 속성을 사용한 설정을 통해 where 또는 set과 같은 형태로 사용할 수 있다.
- where 구현: 태그 안의 내용이 완성될 때 처음 시작할 단어를 prefix로, 시작 시 제거해야 할 단어를 prefixOverrides로 명시한다. 이때 prefixOverrides 속성을 AND|OR로 설정하면 where와 같은 내용으로 구현이 가능하다.
- set 구현: 태그 안의 내용이 완성될 때 처음 시작할 단어를 prefix로, 끝에서 제거해야 할 단어를 suffixOverrides로 명시한다. 이때 suffixOverrides 속성을 ','로 설정하면 set과 같은 내용으로 구현이 가능하다.
- <foreach>: 동적 쿼리를 구현할 때 collection에 대한 반복 처리를 제공하며, 아래와 같은 속성을 사용할 수 있다.
- items: 반복하는 경우 접근이 가능한 각 객체를 담는 변수를 의미한다.
- index: 반복되는 횟수를 가리키는 변수로 0부터 순차적으로 증가한다.
- collection: 전달받은 인자로 반복에 쓰이는 객체를 의미한다. Collection, List, Array 형태의 경우에만 사용할 수 있다.
- open: 해당 구문이 시작하는 경우 삽입할 문자열을 의미한다.
- separator: 반복되는 객체를 나열하는 경우 사용할 구분자를 의미한다.
- close: 해당 구문이 종료될 때 삽입할 문자열을 의미한다.
- <bind>: 특정 문장을 미리 생성하여 쿼리에 적용해야 하는 경우에 사용하며, 주로 LIKE처럼 '%' 같은 문자를 붙여야 하는 경우 사용한다. 이때 해당 문자열은 name 속성의 변수명에 저장되며, 받아오는 값은 _parameter를 이용하여 전달받는다.
게시글 검색
- 게시글 검색 후 페이지를 이동하는 경우에 serachCondition과 searchValue를 넘겨받지 않았다면 오류가 발생한다. 이때 500 에러가 발생하는 경우에는 mybatis-config.xml에서 jdbcTypeForNull 세팅을 해 주어야 한다.
- <settings>: 설정을 하는 경우에 사용하며 내부에 <setting> 태그 여러 개를 사용할 수 있다.
- <setting>: 해당 경우 name 속성에 jdbcTypeForNull을 넣어 설정을 진행해 주어야 하는데, 이는 값이 들어오지 않는 경우 빈칸으로 넣지 않고 무조건 NULL로 넣겠다는 설정이다. 설정 시 value 속성도 지정해 주어야 하는데, 해당 속성은 대문자 NULL이 들어가야 하며, 소문자 null을 넣는 경우 인지하지 못한다.
- jdbcTypeForNull 설정을 하는 경우 500 에러가 발생하지는 않으나, 검색 목록에서 페이지 이동을 하는 경우 검색된 목록이 아니라 전체 목록을 출력하게 된다. 따라서 페이징 버튼 처리 시 serachCondition과 searchValue를 받아와 쿼리스트링으로 삽입해 주어야 한다.
<c:if test="${ pi.currentPage >= pi.maxPage }">[다음]</c:if> <c:if test="${ pi.currentPage < pi.maxPage }"> <c:url value="${ loc }" var="blistNext"> <c:param name="currentPage" value="${ pi.currentPage + 1 }"/> <c:if test="${ searchValue ne null }"> <c:param name="searchCondition" value="${ searchCondition }"/> <c:param name="searchValue" value="${ searchValue }"/> </c:if> </c:url> <a href="${ blistNext }">[다음]</a> </c:if>
'JAVA 웹 개발 > 12. MyBatis' 카테고리의 다른 글
01. Framework (0) | 2022.08.13 |
---|