https://github.com/antop-dev/example/tree/master/mybatis-rowbounds-example


Mybatis에 RowBounds 클래스를 이용해서 페이징 처리를 할 수 있다.

package org.antop.mybatis.mapper;

import org.antop.mybatis.model.Employee;
import org.apache.ibatis.session.RowBounds;

import java.util.List;

public interface EmployeeMapper {
	List<Employee> select(RowBounds rowBounds);
}

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.antop.mybatis.mapper.EmployeeMapper">
    <resultMap id="BaseResultMap" type="org.antop.mybatis.model.Employee">
        <id property="no" column="emp_no" />
        <result property="gender" column="gender" typeHandler="org.antop.mybatis.handler.GenderTypeHandler" />
        <association property="name" javaType="org.antop.mybatis.model.Name">
            <result property="first" column="first_name" />
            <result property="last" column="last_name" />
        </association>
    </resultMap>

    <select id="select" resultMap="BaseResultMap">
        select
          *
        from
          employees
        order by
          emp_no asc
    </select>
</mapper>

위와 같이 맵퍼와 XML 이 있다.

RowBounds rowBounds = new RowBounds(5, 10);
// 15건을 가져와서 앞에 5건 건너띔
List select = mapper.select(rowBounds);

결과는 10건이 나왔지만 어떻게 동작할까?

여기저기 찾아보고 소스도 대충(?) 보면 offset + limit 만큼 가져와서 offset 만큼 건너띤다.

페이징이 뒤로 갈 수록 느려지게 된다.

RowBounds rowBounds = new RowBounds(29990, 10);
// 30000건을 가져와서 앞에 29990건 건너띔
List select = mapper.select(rowBounds);

데이터의 양이 적다면 쿼리에서 페이징을 하지 않고 RowBouns를 이용하면 빠르게 개발할 수 있을 것이다.

하긴 요즘 수천만건의 데이터를 페이징에서 마지막 페이지를 볼 일이 많을까? -_-/


XML 쿼리문에 페이징을 넣지 않고 RowBounds 클래스를 사용하면 자동으로 페이징 쿼리가 실행되게 할 수 없을까?

방법이 있다! Mybatis Intercepter를 이용하면 된다. (인터셉터의 자세한 사용법은 다루지 않겠다 ㅠㅠ)

  1. Mybatis에서 쿼리를 날리기 전에 가로챈다.
  2. RowBounds가 있으면 쿼리에 페이징 문장를 적용한다.
  3. RowBounds를 제거하여 Mybatis에서 페이징 처리를 하지 않도록 한다.

아래 소스는 쿼리에 MySQL용으로 limit 문을 붙여준다.

package org.antop.mybatis.intercepter;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MysqlRowBoundsInterceptor implements Interceptor {
	private static final Logger logger = LoggerFactory.getLogger(MysqlRowBoundsInterceptor.class);

	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
		String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		RowBounds rb = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");

		logger.debug("originalSql = {}", originalSql);
		logger.debug("RowBounds = {}", rb);

		if (rb == null || rb == RowBounds.DEFAULT) {
			// RowBounds가 없으면 그냥 실행
			return invocation.proceed();
		}

		// RowBounds가 있다!
		// 원래 쿼리에 limit 문을 붙여준다.
		StringBuffer sb = new StringBuffer();
		sb.append(originalSql);
		sb.append(" limit ");
		sb.append(rb.getOffset());
		sb.append(", ");
		sb.append(rb.getLimit());

		logger.debug("sql = {}", sb.toString());

		// RowBounds 정보 제거
		metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
		metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		// 변경된 쿼리로 바꿔치기
		metaStatementHandler.setValue("delegate.boundSql.sql", sb.toString());

		return invocation.proceed();
	}

	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	public void setProperties(Properties properties) {

	}

}

SqlSessionFactory 설정하는 부분에 인터셉터를 적용하면 된다.

<?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:mybatis="http://mybatis.org/schema/mybatis-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

    <mybatis:scan base-package="org.antop.mybatis"/>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
        <property name="plugins">
            <list>
                <!-- 인터셉터 설정 -->
                <bean class="org.antop.mybatis.intercepter.MysqlRowBoundsInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="dataSource" ... />
</beans>

실행시켜 보면 쿼리문에 페이징이 적용되서 쿼리가 날라가는 것을 확인할 수 있다.

select * from employees order by emp_no asc limit 29990, 10


출처

http://www.programering.com/a/MTM5gTNwATQ.html

http://hjw1456.tistory.com/10

'Framework > Mybatis' 카테고리의 다른 글

Mybatis Interceptor + RowBounds 를 이용한 페이징 처리  (0) 2017.12.12

댓글 (Comment)

Name*

Password*

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

Criteria 사용하여 질의 하기 #2

Posted at 2009. 10. 3. 10:03 | Posted in Framework/Hibernate
JOIN

http://docs.jboss.org/hibernate/stable/core/api/org/hibernate/Criteria.html





부서와 사원의 1:n 관계에 JOIN 쿼리를 날려봅시다.

원래 하이버네이트에서는 매핑을 잘 맺어주면, 단순 getter 로도 관계를 가지는 테이블의 내용을 가져올 수 있습니다.

Criteria crit = sess.createCriteria(Emp.class);

List<Emp> emps = crit.list();
   
int size = emps.size();
for(int i=0 ; i < size ; i++) {
   Emp emp = emps.get(i);
   Dept dept = emp.getDept();

   String empName = emp.getName();
   String deptName = dept != null ? dept.getDeptName() : "";
    
   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}

이렇게하면 사원에 해당하는 부서의 정보를 가져올 수 있습니다.

로그를 보면 select문이 여러번 출력되는 것을 볼 수 있습니다.

List<Emp> emps = crit.list(); 에서 ' select empno, ename, job, hire_date from tbl_emp; ' 쿼리가 실행되고,

dept.getDeptName(); 에서 사원의 부서번호에 따라 ' select deptno, dname, loc from tbl_dept; ' 쿼리가 실행 됩니다.

부서의 종류가 엄청나게 많다면 두번째 select 쿼리가 많이 실행 되게 될 것입니다.

쿼리를 할때 최소한의 I/O로 실행되게 하고, 뭐.. 서브쿼리보다는 조인이 빠르고 흘러가면서 들은 것들이 있기 땜시... JOIN ㄱㄱ



INNER JOIN

키를 기준으로 일치하는 데이터만 가져옵니다. (다 아시졍? +_+)

// select * from tbl_emp e
Criteria crit = sess.createCriteria(Emp.class, "e");
// join tbl_dept d on this.deptno = d.deptno
crit.createCriteria("dept", "d");
   
// Result 를 Map 형태로 변환
crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

List<Map<String, Object>> joinList = crit.list();

int size = joinList.size();
for(int i=0 ; i < size ; i++) {
   Map<String, Object> map = joinList.get(i);
   Emp emp = (Emp) map.get("e");
   Dept dept = (Dept) map.get("d");

   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}


Criteria crit = sess.createCriteria(Emp.class);

Session.createCriteria(클리스명) 를 통해서 생성한 Critereria 객체에 다시 createCriteria(String associationPath, String alias) 를 사용해서 JOIN 을 하면 됩니다.

이렇게 JOIN 한 결과는 Emp 객체도 Dept 객체도 아니기 때문에, Criteria의 setResultTransformer 메소드를 사용하여 Criteria를 이용한 조회 결과를 별도 정의한 객체 형태나 Map 형태로로 전달받아야 하겠습니다.

근데 전 객체로 변형하는 법은 잘 안되서... 걍 Map 으로 ㄱㄱㄱ!

여기서 두번째 인자로 넣는 엘리어스명은 쿼리에서 쓰는 엘리어스명도 되지만 나중에 Map 에서 꺼 낼때의 키도 되기 때문에 꼭 지정해줘야 할 것입니다....

실행 되는 쿼리는 아래와 같습니다.

    select
        this_.empno as empno0_1_,
        this_.ename as ename0_1_,
        this_.job as job0_1_,
        this_.hire_date as hire4_0_1_,
        this_.deptno as deptno0_1_,
        d1_.deptno as deptno1_0_,
        d1_.dname as dname1_0_,
        d1_.loc as loc1_0_
    from
        tbl_emp this_
    inner join
        tbl_dept d1_
            on this_.deptno=d1_.deptno


이러면 당연히 부서가 없는 'TOM' 은 목록에 나오지가 않겠죠? 만약 사원 목록을 본다면 부서가 없는 사원이 나오면 안되겠죠?



LEFT JOIN

두개의 테이블을 조인할 때 왼쪽 테이블을 기분으로 오른쪽 테이블을 조인 시키는 LEFT JOIN 을 해봅시다...

createCriteria(String associationPath, String alias, int joinType) 를 사용하면 됩니다.

// select * from tbl_emp e
Criteria crit = sess.createCriteria(Emp.class, "e");
// join tbl_dept d on this.deptno = d.deptno
crit.createCriteria("dept", "d", Criteria.LEFT_JOIN);
   
// Result 를 Map 형태로 변환
crit.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

List<Map<String, Object>> joinList = crit.list();

int size = joinList.size();
for(int i=0 ; i < size ; i++) {
   Map<String, Object> map = joinList.get(i);
   Emp emp = (Emp) map.get("e");
   Dept dept = (Dept) map.get("d");

   System.out.println("row: " + (i+1));
   System.out.println(emp);
   System.out.println(dept);
}

실행되는 쿼리는 아래와 같습니다. 엘리어스 사용에 주의 하세요~

    select
        this_.empno as empno0_1_,
        this_.ename as ename0_1_,
        this_.job as job0_1_,
        this_.hire_date as hire4_0_1_,
        this_.deptno as deptno0_1_,
        d1_.deptno as deptno1_0_,
        d1_.dname as dname1_0_,
        d1_.loc as loc1_0_
    from
        tbl_emp this_
    left outer join
        tbl_dept d1_
            on this_.deptno=d1_.deptno


※ RIGHT JOIN 은 없네요 테이블의 좌 우 를 바꾸셔요~ 히힛

간단하게 사원명과 부서명으로 조회하는 페이지 만들어 봤어요~








Pagination

많은 자료를 조회할 때 일정한 양의 자료를 한 페이지만 보여주도록 하는 기능입니다.

꼭 필요한 기능이고, 그다지 복잡할 것도 없지만, 막상 쿼리 날리려면 DBMS 마다 쿼리도 틀리고... 쉽지 안쵸 ㅠ_ㅠ

하지만 하이버네이트에서는

setFirstResult(int firstResult) 과 setMaxResults(int maxResults) 메소드 두개면 해결 됩니다! ㅋㅋㅋ

전부터 해오던 우편번호 테이블 가지고 해봅시다...


Criteria crit = sess.createCriteria(Zipcode.class);
crit.setFirstResult(40);   // 시작 로우 라인 번호(?)
crit.setMaxResults(10);    // 시작 로우 라인 번호로 부터 몇개?!

List<Zipcode> list = crit.list();
   
int size = list.size();
for(int i=0 ; i < size ; i++) {
   Zipcode z = list.get(i);
   System.out.println(z.toString());
}

한 페이지에 10개씩 보여준다고 하고 4페이지의 내용을 보여주게 됩니다. 


페이징 처리가 되있는 우편번호 검색 페이지를 만들어봤습니다.




몇개의 DMBS 에서 돌려보니까 아래와 같은 쿼리가 나오네요


MySQL 5.1.38
    select
        this_.seq as seq0_0_,
        this_.zipcode as zipcode0_0_,
        this_.sido as sido0_0_,
        this_.gugun as gugun0_0_,
        this_.dong as dong0_0_,
        this_.ri as ri0_0_,
        this_.bldg as bldg0_0_,
        this_.st_bunji as st8_0_0_,
        this_.ed_bunji as ed9_0_0_
    from
        tbl_zipcode this_ limit ?,
        ?


Oracle 10g Express Edition
    select
        *
    from
        ( select
            row_.*,
            rownum rownum_
        from
            ( select
                this_.seq as seq0_0_,
                this_.zipcode as zipcode0_0_,
                this_.sido as sido0_0_,
                this_.gugun as gugun0_0_,
                this_.dong as dong0_0_,
                this_.ri as ri0_0_,
                this_.bldg as bldg0_0_,
                this_.st_bunji as st8_0_0_,
                this_.ed_bunji as ed9_0_0_
            from
                tbl_zipcode this_ ) row_
        where
            rownum <= ?
        )
    where
        rownum_ > ?


Microsoft SQL Server 2005
    select
        top 20 this_.seq as seq0_0_,
        this_.zipcode as zipcode0_0_,
        this_.sido as sido0_0_,
        this_.gugun as gugun0_0_,
        this_.dong as dong0_0_,
        this_.ri as ri0_0_,
        this_.bldg as bldg0_0_,
        this_.st_bunji as st8_0_0_,
        this_.ed_bunji as ed9_0_0_
    from
        tbl_zipcode this_


MSSQL 은 일단 페이지번호 * 갯수 만큼 가져와서 내부적으로 처리를 하나봐요.....




http://blog.naver.com/oyukihana?Redirect=Log&logNo=60002506896

'Framework > Hibernate' 카테고리의 다른 글

PK 두개 이상시 매핑  (2) 2009.12.24
Reverse Engineering  (0) 2009.12.24
Hibernate 시작하기  (0) 2009.12.23
Criteria 사용하여 질의 하기 #2  (1) 2009.10.03
Criteria 사용하여 질의 하기 #1  (3) 2009.09.10
다대다(n:m) 관계 설정하기  (0) 2009.09.02
일대다(1:n) 관계 설정하기  (1) 2009.08.31
일대일(1:1) 관계 설정하기  (0) 2009.08.27
하이버네이트(Hibernate) 사용하기  (6) 2009.08.24
  1. 좋은 정보 감사합니다.
    퍼갈께요. 원하지 않으시면 삭제 하겠습니다.(출처표시함)

댓글 (Comment)

Name*

Password*

Link (Your Website)

Comment

SECRET | 비밀글로 남기기