MyBatis从入门到精通笔记(7)MyBatis插件

一 简介

MyBatis提供了插件机制,我们可以在sql语句执行过程中拦截,进行自定义扩展。

常用的拦截对象

  • 执行器Executor(update、query、commit、rollback等方法)
  • 参数处理器ParameterHandler(getParameterObject、setParameters方法)
  • 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)
  • SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法)

时序图,在整个时序图中,涉及到mybatis插件部分已标红,基本上就是体现在上文中提到的四个类上,对这些类上的方法进行拦截
在这里插入图片描述

二 拦截器

2.1 接口

org.apache.ibatis.plugin.Interceptor可以实现对目标类的拦截

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    Object plugin(Object var1);

    void setProperties(Properties var1);
}

setProperties

该方法用来接收参数,我们可以在全局配置文件中配置,如下:

	<plugins>
		<plugin interceptor="tk.mybatis.simple.plugin.PageInterceptor">
			<property name="dialect" value="tk.mybatis.simple.plugin.MySqlDialect"/>
		</plugin>
	</plugins>

plugin

这个方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用。该方法的实现很简单,只需要调用MyBatis 提供的Plugin类的wrap 静态方法就可以通过Java 的动态代理拦截目标对象。这个接口方法通常的实现代码如下。

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

Plugin .wrap 方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,只有匹配的情况下才会使用动态代理拦截目标对象,因此在上面的实现方法中不必做额外的逻辑判断。

intercept

最重要的方法,自定义逻辑一般都写在这里

我们可以通过Invocation对象获取到被拦截的对象、当前被调用的方法、方法的传参或者在处理后调用原方法(就是和动态代理、Aspect类似)

当一个我们设置了多个拦截器时,MyBatis会遍历所有的拦截器,按顺序!!!执行拦截器的plugin方法。签名匹配会被代理。
如果一个对象被多个拦截器比如A、B、C代理,则执行顺序是:C>B>A>target.proceed()>A>B>C(一般学过过滤器都可以理解这种顺序)

2.2 拦截器的签名

我们可以使用签名来决定拦截器拦截哪些对象(作用类似Aspect中的@Pointcut指定连接点

如下所示:

@Intercepts({
	@Signature(type = Executor.class, method = "query", args = {
			MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
	})
})
public class ResultSetInterceptor implements Interceptor{

1 @Intercepts 注解中的属性是一个@Signature (签名)数组,可以在同一个拦截器中同时拦截不同的接口和方法。
2. @Signature 注解包含以下三个属性
+ type :设置拦截的接口,可选值是前面提到的4 个接口
+ method:设置拦截接口中的方法名,可选值是前面4 个接口对应的方法,需要和接口匹配
+ args :设置拦截方法的参数类型数组,通过方法名和参数类型可以确定唯一一个方法(处理重载)

三 拦截接口介绍

下面简单记录下前面提到的4个接口的常用方法

3.1 Executor

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

该方法会在所有SELECT 查询方法执行时被调用。通过这个接口参数可以获取很多有用的
信息,因此这是最常被拦截的一个方法。

<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

该方法只有在查询的返回值类型为Cursor 时被调用。暂时还没用过Cursor

List<BatchResult> flushStatements() throws SQLException;

该方法只在通过SqlSession 方法调用flus hStatements 方法或执行的接口方法中带有@Flush 注解时才被调用

3.2 ParameterHandler

  Object getParameterObject();

该方法只在执行存储过程处理出参的时候被调用

  void setParameters(PreparedStatement ps)
      throws SQLException;

该方法在所有数据库方法设置SQL 参数时被调用

3.3 ResultSetHandler

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

handleResultSets
该方法会在除存储过程及返回值类型为Cursor<T >以外的查询方法中被调用

handleCursorResultSets
该方法是3.4.0 版本中新增加的,只会在返回值类型为Cursor < T >的查询方法中被调用

handleOutputParameters
该方法只在使用存储过程处理出参时被调用

3.4 StatementHandler

 Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

该方法会在数据库执行前被调用,优先于当前接口中的其他方法而被执行

  void parameterize(Statement statement)
      throws SQLException;

该方法在prepare 方法之后执行,用于处理参数信息

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

执行SELECT 方法时调用

四 demo-下面线键值转小写驼峰形式插件

4.1 代码

代码直接取之参考资料😂

package tk.mybatis.simple.plugin;

import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

/**
 * MyBatis Map 类型下划线 Key 转小写驼峰形式
 *
 * @author liuzenghui
 */
@Intercepts(
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
)
@SuppressWarnings({ "unchecked", "rawtypes" })
public class CameHumpInterceptor implements Interceptor {
    
	@Override
    public Object intercept(Invocation invocation) throws Throwable {
        //先执行得到结果,再对结果进行处理
        List<Object> list = (List<Object>) invocation.proceed();
        for(Object object : list){
        	//如果结果是 Map 类型,就对 Map 的 Key 进行转换
            if(object instanceof Map){
                processMap((Map)object);
            } else {
                break;
            }
        }
        return list;
    }

    /**
     * 处理 Map 类型
     *
     * @param map
     */
    private void processMap(Map<String, Object> map) {
        Set<String> keySet = new HashSet<String>(map.keySet());
        for(String key : keySet){
        	//大写开头的会将整个字符串转换为小写,如果包含下划线也会处理为驼峰
        	if((key.charAt(0) >= 'A' && key.charAt(0) <= 'Z') || key.indexOf("_") >= 0){
        		Object value = map.get(key);
        		map.remove(key);
        		map.put(underlineToCamelhump(key), value);
        	}
        }
    }

    /**
     * 将下划线风格替换为驼峰风格
     * 处理思路就是如果一个字符的前一个位置是下划线,则将下一个字符放入sb中
     *
     * @param inputString
     * @return
     */
    public static String underlineToCamelhump(String inputString) {
        StringBuilder sb = new StringBuilder();

        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++) {
            char c = inputString.charAt(i);
            if(c == '_'){
            	if (sb.length() > 0) {
                    nextUpperCase = true;
                }
            } else {
            	if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(Character.toLowerCase(c));
                }
            }
        }
        return sb.toString();
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
}

4.2 小结

invocation . proceed ()执行的结果被强制转换为了List 类型。这是因为拦截器接口ResultSetHandler 的handleResultSets 方法的返回值为List 类型,所以才能在这里直接强制转换。
如果不知道这一点,就很难处理这个返回值。许多接口方法的返回值类型都是List ,但是还有很多其他的类型,所以在写拦截器时,要根据被拦截的方法来确定返回值的类型!!!

五 分页插件

在MyB ati s 拦截器中,最常用的一种就是实现分页插件。如果不使用分页插件来实现分页功能,就需要自己在映射文件的SQL 中增加分页条件,井且为了获得数据的总数还需要额外增加一个count 查询的SQL ,写起来很麻烦。如果要兼容多种数据库,可能要根据databaseId来写不同的分页SQL ,不仅写起来麻烦,也会让SQL 变得脆肿不堪.

下面的插件是由参考书籍的作者实现的,该插件的核心部分由两个类组成,PageInterceptor 拦截器类和数据库方言接口Dialect

5.1 PageInterceptor

package tk.mybatis.simple.plugin;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Mybatis - 通用分页拦截器
 *
 * @author liuzh
 * @version 1.0.0
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts(
	@Signature(
		type = Executor.class, 
		method = "query", 
		args = {MappedStatement.class, Object.class, 
				RowBounds.class, ResultHandler.class}
	)
)
public class PageInterceptor implements Interceptor {
    private static final List<ResultMapping> EMPTY_RESULTMAPPING
    		= new ArrayList<ResultMapping>(0);
    private Dialect dialect;
    private Field additionalParametersField;

	@Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取拦截方法的参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms.getId(), parameterObject, rowBounds)) {
        	ResultHandler resultHandler = (ResultHandler) args[3];
            //当前的目标对象
            Executor executor = (Executor) invocation.getTarget();
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            //反射获取动态参数
            Map<String, Object> additionalParameters = 
            		(Map<String, Object>) additionalParametersField.get(boundSql);
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms.getId(), parameterObject, rowBounds)){
            	//根据当前的 ms 创建一个返回值为 Long 类型的 ms
                MappedStatement countMs = newMappedStatement(ms, Long.class);
                //创建 count 查询的缓存 key
                CacheKey countKey = executor.createCacheKey(
                		countMs, 
                		parameterObject, 
                		RowBounds.DEFAULT, 
                		boundSql);
                //调用方言获取 count sql
                String countSql = dialect.getCountSql(
                		boundSql, 
                		parameterObject, 
                		rowBounds, 
                		countKey);
                BoundSql countBoundSql = new BoundSql(
                		ms.getConfiguration(), 
                		countSql, 
                		boundSql.getParameterMappings(), 
                		parameterObject);
                //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
                for (String key : additionalParameters.keySet()) {
                    countBoundSql.setAdditionalParameter(
                    		key, additionalParameters.get(key));
                }
                //执行 count 查询
                Object countResultList = executor.query(
                		countMs, 
                		parameterObject, 
                		RowBounds.DEFAULT, 
                		resultHandler, 
                		countKey, 
                		countBoundSql);
                Long count = (Long) ((List) countResultList).get(0);
                //处理查询总数
                dialect.afterCount(count, parameterObject, rowBounds);
                if(count == 0L){
                	//当查询总数为 0 时,直接返回空的结果
                	return dialect.afterPage(
                			new ArrayList(), 
                			parameterObject, 
                			rowBounds); 
                }
            }
            //判断是否需要进行分页查询
            if (dialect.beforePage(ms.getId(), parameterObject, rowBounds)){
            	//生成分页的缓存 key
                CacheKey pageKey = executor.createCacheKey(
                		ms, 
                		parameterObject, 
                		rowBounds, 
                		boundSql);
                //调用方言获取分页 sql
                String pageSql = dialect.getPageSql(
                		boundSql, 
                		parameterObject, 
                		rowBounds, 
                		pageKey);
                BoundSql pageBoundSql = new BoundSql(
                		ms.getConfiguration(), 
                		pageSql, 
                		boundSql.getParameterMappings(), 
                		parameterObject);
                //设置动态参数
                for (String key : additionalParameters.keySet()) {
                    pageBoundSql.setAdditionalParameter(
                    		key, additionalParameters.get(key));
                }
                //执行分页查询
                List resultList = executor.query(
                		ms, 
                		parameterObject, 
                		RowBounds.DEFAULT, 
                		resultHandler, 
                		pageKey, 
                		pageBoundSql);
                
                return dialect.afterPage(resultList, parameterObject, rowBounds);
            }
        }
        //返回默认查询
        return invocation.proceed();
    }

    /**
     * 根据现有的 ms 创建一个新的,使用新的返回值类型
     *
     * @param ms
     * @param resultType
     * @return
     */
    public MappedStatement newMappedStatement(
    		MappedStatement ms, Class<?> resultType) {
        MappedStatement.Builder builder = new MappedStatement.Builder(
        		ms.getConfiguration(), 
        		ms.getId() + "_Count", 
        		ms.getSqlSource(), 
        		ms.getSqlCommandType()
        );
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null 
        		&& ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(
            		keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        //count查询返回值int
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        ResultMap resultMap = new ResultMap.Builder(
        		ms.getConfiguration(), 
        		ms.getId(), 
        		resultType, 
        		EMPTY_RESULTMAPPING).build();
        resultMaps.add(resultMap);
        builder.resultMaps(resultMaps);
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

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

    @Override
    public void setProperties(Properties properties) {
        String dialectClass = properties.getProperty("dialect");
        try {
            dialect = (Dialect) Class.forName(dialectClass).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(
            		"使用 PageInterceptor 分页插件时,必须设置 dialect 属性");
        }
        dialect.setProperties(properties);
        try {
            //反射获取 BoundSql 中的 additionalParameters 属性
            additionalParametersField = BoundSql.class.getDeclaredField(
            		"additionalParameters");
            additionalParametersField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

}

方法分析

  1. 拦截器拦截了Executor 类的query 接口
  2. 如果需要进行分页,首先获取当前方法的BoundSql,这个对象中包含了要执行的SQL 和对应的参数。通过这个对象的SQL 和参数生成一个count 查询的BoundSql ,由于这种情况下的MappedStatement 对象中的resultMap 或resultType 类型为当前查询结果的类型,并不适合返回count 查询值,因此通过newMappedStatement 方法根据当前的MappedStatement 生成了一个返回值类型为Long 的对象,然后通过Executor 执行查询,得到了数据总数
  3. 如果需要进行分页,就使用dialect 获取分页查询SQL ,同count 查询类似,得到分页数据的结果后,通过dialect 对结果进行处理并返回。

5.2 Dialect 接口

这只是一个接口,不同数据库需要有不同的实现。

package tk.mybatis.simple.plugin;

import java.util.List;
import java.util.Properties;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.RowBounds;

/**
 * 数据库方言,针对不同数据库进行实现
 * 
 * @author liuzh
 */
@SuppressWarnings("rawtypes")
public interface Dialect {
	/**
	 * 跳过 count 和 分页查询
	 * 
	 * @param msId 执行的  MyBatis 方法全名
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @return true 跳过,返回默认查询结果,false 执行分页查询
	 */
	boolean skip(String msId, Object parameterObject, RowBounds rowBounds);
	
	/**
	 * 执行分页前,返回 true 会进行 count 查询,false 会继续下面的 beforePage 判断
	 * 
	 * @param msId 执行的  MyBatis 方法全名
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @return
	 */
	boolean beforeCount(String msId, Object parameterObject, RowBounds rowBounds);
	
	/**
	 * 生成 count 查询 sql
	 * 
	 * @param boundSql 绑定 SQL 对象
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @param countKey count 缓存 key
	 * @return
	 */
	String getCountSql(BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey);
	
	/**
	 * 执行完 count 查询后
	 * 
	 * @param count 查询结果总数
	 * @param parameterObject 接口参数
	 * @param rowBounds 分页参数
	 */
	void afterCount(long count, Object parameterObject, RowBounds rowBounds);
	
	/**
	 * 执行分页前,返回 true 会进行分页查询,false 会返回默认查询结果
	 * 
	 * @param msId 执行的 MyBatis 方法全名
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @return
	 */
	boolean beforePage(String msId, Object parameterObject, RowBounds rowBounds);
	
	/**
	 * 生成分页查询 sql
	 * 
	 * @param boundSql 绑定 SQL 对象
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @param pageKey 分页缓存 key
	 * @return
	 */
	String getPageSql(BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey);
	
	/**
	 * 分页查询后,处理分页结果,拦截器中直接 return 该方法的返回值
	 * 
	 * @param pageList 分页查询结果
	 * @param parameterObject 方法参数
	 * @param rowBounds 分页参数
	 * @return
	 */
	Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds);
	
	/**
	 * 设置参数
	 * 
	 * @param properties 插件属性
	 */
	void setProperties(Properties properties);
}

PageRowBounds 扩展了RowBounds

/**
 * 可以记录 total 的分页参数
 * 
 * @author liuzh
 */
public class PageRowBounds extends RowBounds{
	private long total;

	public PageRowBounds() {
		super();
	}

	public PageRowBounds(int offset, int limit) {
		super(offset, limit);
	}

	public long getTotal() {
		return total;
	}

	public void setTotal(long total) {
		this.total = total;
	}
}

5.3 MySqlDialect实现

/**
 * MySql 实现
 * 
 * @author liuzh
 */
@SuppressWarnings("rawtypes")
public class MySqlDialect implements Dialect {

	@Override
	public boolean skip(String msId, Object parameterObject, RowBounds rowBounds) {
		//这里使用 RowBounds 分页,默认没有 RowBounds 参数时,会使用 RowBounds.DEFAULT 作为默认值
		if(rowBounds != RowBounds.DEFAULT){
			return false;
		}
		return true;
	}

	@Override
	public boolean beforeCount(String msId, Object parameterObject, RowBounds rowBounds) {
		//只有使用 PageRowBounds 才能记录总数,否则查询了总数也没用
		if(rowBounds instanceof PageRowBounds){
    		return true;
    	}
		return false;
	}
	
	@Override
	public String getCountSql(BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
		//简单嵌套实现 MySql count 查询
		return "select count(*) from (" + boundSql.getSql() + ") temp";
	}
	
    @Override
    public void afterCount(long count, Object parameterObject, RowBounds rowBounds) {
    	//记录总数,按照 beforeCount 逻辑,只有 PageRowBounds 时才会查询 count,所以这里直接强制转换
    	((PageRowBounds)rowBounds).setTotal(count);
    }
    
    @Override
	public boolean beforePage(String msId, Object parameterObject, RowBounds rowBounds) {
		if(rowBounds != RowBounds.DEFAULT){
			return true;
		}
		return false;
	}
	
	@Override
	public String getPageSql(BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
		//pageKey 会影响缓存,通过固定的 RowBounds 可以保证二级缓存有效
		pageKey.update("RowBounds");
		return boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
	}

	@Override
	public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
		return pageList;
	}
	
	@Override
	public void setProperties(Properties properties) {
		
	}


}

测试代码

	@Test
	public void testSelectAllByRowBounds(){
		SqlSession sqlSession = getSqlSession();
		try {
			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//查询前两个,使用 RowBounds 类型不会查询总数
			RowBounds rowBounds = new RowBounds(0, 2);
			List<SysUser> list = userMapper.selectAll(rowBounds);
			for(SysUser user : list){
				System.out.println("用户名:" + user.getUserName());
			}
			//使用 PageRowBounds 会查询总数
			PageRowBounds pageRowBounds = new PageRowBounds(2, 2);
			list = userMapper.selectAll(pageRowBounds);
			//获取总数
			System.out.println("查询总数:" + pageRowBounds.getTotal());
			for(SysUser user : list){
				System.out.println("用户名2:" + user.getUserName());
			}
			//再次查询
			pageRowBounds = new PageRowBounds(4, 2);
			list = userMapper.selectAll(pageRowBounds);
			//获取总数
			System.out.println("查询总数:" + pageRowBounds.getTotal());
			for(SysUser user : list){
				System.out.println("用户名3:" + user.getUserName());
			}
		} finally {
			sqlSession.close();
		}
		sqlSession = getSqlSession();
		try {
			System.out.println("-----------测试二级缓存------------");

			UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
			//查询前两个,使用 RowBounds 类型不会查询总数
			RowBounds rowBounds = new RowBounds(0, 2);
			List<SysUser> list = userMapper.selectAll(rowBounds);
			for(SysUser user : list){
				System.out.println("用户名:" + user.getUserName());
			}
			//使用 PageRowBounds 会查询总数
			PageRowBounds pageRowBounds = new PageRowBounds(2, 2);
			list = userMapper.selectAll(pageRowBounds);
			//获取总数
			System.out.println("查询总数:" + pageRowBounds.getTotal());
			for(SysUser user : list){
				System.out.println("用户名2:" + user.getUserName());
			}
			//再次查询
			pageRowBounds = new PageRowBounds(4, 2);
			list = userMapper.selectAll(pageRowBounds);
			//获取总数
			System.out.println("查询总数:" + pageRowBounds.getTotal());
			for(SysUser user : list){
				System.out.println("用户名3:" + user.getUserName());
			}
		} finally {
			sqlSession.close();
		}
	}

5.4 小结

在这里插入图片描述

MyBatis的默认分页是逻辑分页
在 sql 查询出所有结果的基础上截取数据的,所以在数据量大的sql中并不适用,它更适合在返回数据结果较少的查询中使用。测试,关闭插件,查看上面的测试代码

将查询改为

			RowBounds rowBounds = new RowBounds(1, 1);

日志还是查询出了两条记录,缺点很明显

DEBUG [main] - Cache Hit Ratio [zyc.mybatis.simple.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, user_name userName, user_password userPassword, user_email userEmail, user_info userInfo, head_img headImg, create_time createTime from sys_user 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime
TRACE [main] - <==        Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2016-06-07 01:11:12
TRACE [main] - <==        Row: 1001, test, 123456, test@mybatis.tk, <<BLOB>>, <<BLOB>>, 2016-06-07 00:00:00

参考

  1. 《MyBatis 从入门到精通》
  2. 源码
  3. 深入理解Mybatis插件开发
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值