1. 程式人生 > >MyBatis物理分頁的代碼實現

MyBatis物理分頁的代碼實現

框架 images record util rop 持久 tab nco off

一.分頁

MyBatis有兩種分頁方法:內存分頁,也就是假分頁,本質是查出所有的數據然後根據遊標的方式,截取需要的記錄,如果數據量大,執行效率低,可能造成內存溢出。物理分頁,就是數據庫本身提供了分頁方式,如MySql的limit,執行效率高,不同數據庫實現不同。

MyBatis Generator使用:MyBatis Generator使用示例

Spring集成MyBatis:Spring集成MyBatis持久層框架

二.MyBatis執行流程

MyBatis執行sql流程如下圖,實現數據庫的物理分頁,需要通過攔截StatementHandler重寫的sql語句。

技術分享圖片

三.分頁實現

1.實現MyBatis的Interceptor接口,創建PageInterceptor類

@Intercepts({@Signature(type=StatementHandler.class, method = "prepare", args={Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {

    private String sqlRegEx = ".*Page";

    public Object intercept(Invocation invocation) throws
Throwable { RoutingStatementHandler handler = (RoutingStatementHandler)invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); BoundSql boundSql = delegate.getBoundSql(); MappedStatement mappedStatement
= (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement"); // 獲取參數 Object parameterObject = boundSql.getParameterObject(); // 判斷是否分頁 if (mappedStatement.getId().matches(sqlRegEx)) { Page page = (Page) ((Map<?, ?>) parameterObject).get("page"); if (page != null) { Connection connection = (Connection) invocation.getArgs()[0]; // 獲取mapper映射文件中對應的sql語句 String sql = boundSql.getSql(); // 給當前page參數設置總記錄數 this.setPageParameter(mappedStatement, connection, boundSql, page); // 獲取分頁sql語句 String pageSql = this.getPageSql(page, sql); ReflectUtil.setFieldValue(boundSql, "sql", pageSql); } } return invocation.proceed(); } private void setPageParameter(MappedStatement mappedStatement, Connection connection, BoundSql boundSql, Page page) { // 獲取mapper映射文件中對應的sql語句 String sql = boundSql.getSql(); // 獲取計算總記錄數的sql語句 String countSql = this.getCountSql(sql); // 獲取BoundSql參數映射 List<ParameterMapping> parameterMappinglist = boundSql.getParameterMappings(); // 構造查詢總量的BoundSql BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappinglist, boundSql.getParameterObject()); ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql); PreparedStatement pstmt = null; ResultSet rs = null; try { // 通過connection建立countSql對應的PreparedStatement對象 pstmt = connection.prepareStatement(countSql); parameterHandler.setParameters(pstmt); // 執行countSql語句 rs = pstmt.executeQuery(); if (rs.next()) { int totalRecord = rs.getInt(1); page.setTotalRecord(totalRecord); page.setTotalPage(totalRecord/page.getPageSize() + (totalRecord % page.getPageSize() == 0? 0: 1)); } } catch (SQLException e) { e.printStackTrace(); } } /** * 根據源sql語句獲取對應的查詢總記錄數的sql語句 * @param sql * @return */ private String getCountSql(String sql) { int index = sql.indexOf("from"); return "select count(*) " + sql.substring(index); } /** * 獲取MySql數據庫的分頁查詢語句 * @param page * @param sql * @return */ private String getPageSql(Page<?> page, String sql) { StringBuffer sqlBuffer = new StringBuffer(sql); int offset = (page.getPageNum() - 1) * page.getPageSize(); sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize()); return sqlBuffer.toString(); } /** * 只處理StatementHandler類型 * @param o * @return */ public Object plugin(Object o) { if (o instanceof StatementHandler) { return Plugin.wrap(o, this); } else { return o; } } /** * 攔截器屬性設定 * @param properties */ public void setProperties(Properties properties) { } public String getSqlRegEx() { return sqlRegEx; } public void setSqlRegEx(String sqlRegEx) { this.sqlRegEx = sqlRegEx; } }

2.保存頁面的相關信息,創建Page類

public class Page<T> {

    private int pageNum = 1;

    private int pageSize = 5;

    private int totalRecord;

    private int totalPage;

    private List<T> results;

    public int getPageNum() {
        return pageNum;
    }

    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getTotalRecord() {
        return totalRecord;
    }

    public void setTotalRecord(int totalRecord) {
        this.totalRecord = totalRecord;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public List<T> getResults() {
        return results;
    }

    public void setResults(List<T> results) {
        this.results = results;
    }
}

3.通過反射獲取對象的屬性,創建ReflectUtil工具類

public class ReflectUtil {

    public static Object getFieldValue(Object obj, String fieldName) {
        Object result = null;
        Field field = ReflectUtil.getField(obj, fieldName);
        if (null != field) {
            field.setAccessible(true);
            try {
                result = field.get(obj);
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }
        return result;
    }

    private static Field getField(Object obj, String fieldName) {
        Field field = null;
        for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                field = clazz.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
            }
        }
        return field;
    }

    public static void setFieldValue(Object obj, String fieldName, String fieldValue) {
        Field field = ReflectUtil.getField(obj, fieldName);
        if (null != field) {
            try {
                field.setAccessible(true);
                field.set(obj, fieldValue);
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }
    }
}

4.啟用分頁Interceptor,編輯applicationContext_database.xml

    <!-- mybatis配置,mapper.xml文件掃描 -->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:/mybatis/mybatis.xml"/>
        <property name="mapperLocations" value="classpath:/mybatis/mapper/*Mapper.xml"/>
        <property name="dataSource" ref="dataSource"/>

        <property name="plugins">
            <array>
                <!-- 分頁 -->
                <bean class="com.learn.spring.server.intercept.PageInterceptor">
                    <property name="properties">
                        <value>
                            sqlRegEx = ".*Page"
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

四.調用示例

1.編輯UserServiceImpl類

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDOMapper userDao;

    @Override
    public Page<UserDO> listByCondPage(Integer status, Page page) {
        Map<String, Object> param = new HashMap<>();
        param.put("status", status);
        param.put("page", page);
        List<UserDO> userDOList = userDao.selectByCondPage(param);
        page.setResults(userDOList);
        return page;
    }
}

2.編輯IndexController類,調用Service

@Controller
@RequestMapping("/server")
public class IndexController {

    @Resource
    private UserService userService;

    @ResponseBody
    @RequestMapping("/list")
    public Object list(Integer status, Integer pageNum, Integer pageSize) {
        Page<UserDO> userDOPage = new Page<>();
        userDOPage.setPageNum(pageNum);
        userDOPage.setPageSize(pageSize);
        return userService.listByCondPage(status, userDOPage);
    }
}

MyBatis物理分頁的代碼實現