`
234390216
  • 浏览: 10218684 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
博客专栏
A5ee55b9-a463-3d09-9c78-0c0cf33198cd
Oracle基础
浏览量:462010
Ad26f909-6440-35a9-b4e9-9aea825bd38e
springMVC介绍
浏览量:1774434
Ce363057-ae4d-3ee1-bb46-e7b51a722a4b
Mybatis简介
浏览量:1397500
Bdeb91ad-cf8a-3fe9-942a-3710073b4000
Spring整合JMS
浏览量:394705
5cbbde67-7cd5-313c-95c2-4185389601e7
Ehcache简介
浏览量:679507
Cc1c0708-ccc2-3d20-ba47-d40e04440682
Cas简介
浏览量:530293
51592fc3-854c-34f4-9eff-cb82d993ab3a
Spring Securi...
浏览量:1180702
23e1c30e-ef8c-3702-aa3c-e83277ffca91
Spring基础知识
浏览量:466013
4af1c81c-eb9d-365f-b759-07685a32156e
Spring Aop介绍
浏览量:151029
2f926891-9e7a-3ce2-a074-3acb2aaf2584
JAXB简介
浏览量:67733
社区版块
存档分类
最新评论

Mybatis拦截器介绍及分页插件

阅读更多

Mybatis拦截器介绍及分页插件

1.1    目录

1.1 目录

1.2 前言

1.3 Interceptor接口

1.4 注册拦截器

1.5 Mybatis可拦截的方法

1.6 利用拦截器进行分页

1.2     前言

       拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,之后可以选择是否继续执行原来的query方法。

1.3     Interceptor接口

       对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

package org.apache.ibatis.plugin;
 
import java.util.Properties;
 
public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  Object plugin(Object target);
 
  void setProperties(Properties properties);
 
}

 

       我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法,这点将在后文讲解。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

       定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

       对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。这里我们先来看一下Plugin的源码:

package org.apache.ibatis.plugin;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
 
import org.apache.ibatis.reflection.ExceptionUtil;
 
public class Plugin implements InvocationHandler {
 
  private Object target;
  private Interceptor interceptor;
  private Map<Class<?>, Set<Method>> signatureMap;
 
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
 
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
 
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) { // issue #251
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());     
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }
 
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }
 
}

 

       我们先看一下Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

       这就是Mybatis中实现Interceptor拦截的一个思想,如果用户觉得这个思想有问题或者不能完全满足你的要求的话可以通过实现自己的Plugin来决定什么时候需要代理什么时候需要拦截。以下讲解的内容都是基于Mybatis的默认实现即通过Plugin来管理Interceptor来讲解的。

       对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型。来看一个自定义的简单Interceptor:

package com.tiantian.mybatis.interceptor;
 
import java.sql.Connection;
import java.util.Properties;
 
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
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;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
 
@Intercepts( {
       @Signature(method = "query", type = Executor.class, args = {
              MappedStatement.class, Object.class, RowBounds.class,
              ResultHandler.class }),
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {
 
    public Object intercept(Invocation invocation) throws Throwable {
       Object result = invocation.proceed();
       System.out.println("Invocation.proceed()");
       return result;
    }
 
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }
 
    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }
 
}

 

       首先看setProperties方法,这个方法在Configuration初始化当前的Interceptor时就会执行,这里只是简单的取两个属性进行打印。

       其次看plugin方法中我们是用的Plugin的逻辑来实现Mybatis的逻辑的。

       接着看MyInterceptor类上我们用@Intercepts标记了这是一个Interceptor,然后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法;第二个@Signature我们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。

       最后再来看一下intercept方法,这里我们只是简单的打印了一句话,然后调用invocation的proceed方法,使当前方法正常的调用。

       对于这个拦截器,Mybatis在注册该拦截器的时候就会利用定义好的n个property作为参数调用该拦截器的setProperties方法。之后在新建可拦截对象的时候会调用该拦截器的plugin方法来决定是返回目标对象本身还是代理对象。对于这个拦截器而言,当Mybatis是要Executor或StatementHandler对象的时候就会返回一个代理对象,其他都是原目标对象本身。然后当Executor代理对象在执行参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理对象在执行参数类型为Connection的prepare方法时就会触发当前的拦截器的intercept方法进行拦截,而执行这两个接口对象的其他方法时都只是做一个简单的代理。

1.4    注册拦截器

       注册拦截器是通过在Mybatis配置文件中plugins元素下的plugin元素来进行的。一个plugin对应着一个拦截器,在plugin元素下面我们可以指定若干个property子元素。Mybatis在注册定义的拦截器时会先把对应拦截器下面的所有property通过Interceptor的setProperties方法注入给对应的拦截器。所以,我们可以这样来注册我们在前面定义的MyInterceptor:

 

<?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="config/jdbc.properties"></properties>
    <typeAliases>
       <package name="com.tiantian.mybatis.model"/>
    </typeAliases>
    <plugins>
       <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
           <property name="prop1" value="prop1"/>
           <property name="prop2" value="prop2"/>
       </plugin>
    </plugins>
    <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC" />
           <dataSource type="POOLED">
              <property name="driver" value="${jdbc.driver}" />
              <property name="url" value="${jdbc.url}" />
              <property name="username" value="${jdbc.username}" />
              <property name="password" value="${jdbc.password}" />
           </dataSource>
       </environment>
    </environments>
    <mappers>
       <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

 

1.5    Mybatis可拦截的方法

       Mybatis拦截器只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。这是在Mybatis的Configuration中写死了的,如果要支持拦截其他接口就需要我们重写Mybatis的Configuration。Mybatis可以对这四个接口中所有的方法进行拦截。

1.6     利用拦截器进行分页

       下面将介绍一个Mybatis拦截器的实际应用。Mybatis拦截器常常会被用来进行分页处理。我们知道要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前也会产生一个包含Sql语句的Statement对象,而且对应的Sql语句是在Statement之前产生的,所以我们就可以在它成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()。更改Sql语句这个看起来很简单,而事实上来说的话就没那么直观,因为包括sql等其他属性在内的多个属性都没有对应的方法可以直接取到,它们对外部都是封闭的,是对象的私有属性,所以这里就需要引入反射机制来获取或者更改对象的私有属性的值了。对于分页而言,在拦截器里面我们常常还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。先来看一个我们对分页操作封装的一个实体类Page:

import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * 对分页的基本数据进行一个简单的封装
 */
public class Page<T> {
 
    private int pageNo = 1;//页码,默认是第一页
    private int pageSize = 15;//每页显示的记录数,默认是15
    private int totalRecord;//总记录数
    private int totalPage;//总页数
    private List<T> results;//对应的当前页记录
    private Map<String, Object> params = new HashMap<String, Object>();//其他的参数我们把它分装成一个Map对象
 
    public int getPageNo() {
       return pageNo;
    }
 
    public void setPageNo(int pageNo) {
       this.pageNo = pageNo;
    }
 
    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;
       //在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
       int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
       this.setTotalPage(totalPage);
    }
 
    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;
    }
   
    public Map<String, Object> getParams() {
       return params;
    }
   
    public void setParams(Map<String, Object> params) {
       this.params = params;
    }
 
    @Override
    public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
              .append(pageSize).append(", results=").append(results).append(
                     ", totalPage=").append(totalPage).append(
                     ", totalRecord=").append(totalRecord).append("]");
       return builder.toString();
    }
 
}

 

       对于需要进行分页的Mapper映射,我们会给它传一个Page对象作为参数,我们可以看到Page对象里面包括了一些分页的基本信息,这些信息我们可以在拦截器里面用到,然后我们把除分页的基本信息以外的其他参数用一个Map对象进行包装,这样在Mapper映射语句中的其他参数就可以从Map中取值了。接着来看一下我们的PageInterceptor的定义,对于PageInterceptor我就不做过多的说明,代码里面附有很详细的注释信息:

package com.tiantian.mybatis.interceptor;
 
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
 
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
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;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
 
import com.tiantian.mybatis.model.Page;
 
/**
 *
 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。
 * 利用拦截器实现Mybatis分页的原理:
 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句
 * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的
 * prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用
 * StatementHandler对象的prepare方法,即调用invocation.proceed()。
 * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设
 * 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。
 *
 */
@Intercepts( {
       @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })
public class PageInterceptor implements Interceptor {
 
    private String databaseType;//数据库类型,不同的数据库有不同的分页方法
   
    /**
     * 拦截后要执行的方法
     */
    public Object intercept(Invocation invocation) throws Throwable {
       //对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
       //BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
       //SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是
       //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
       //StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
       //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
       //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候
       //是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
       RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
       //通过反射获取到当前RoutingStatementHandler对象的delegate属性
       StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
       //获取到当前StatementHandler的 boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
       //RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
       BoundSql boundSql = delegate.getBoundSql();
       //拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
       Object obj = boundSql.getParameterObject();
       //这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
       if (obj instanceof Page<?>) {
           Page<?> page = (Page<?>) obj;
           //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
           MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
           //拦截到的prepare方法参数是一个Connection对象
           Connection connection = (Connection)invocation.getArgs()[0];
           //获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
           String sql = boundSql.getSql();
           //给当前的page参数对象设置总记录数
           this.setTotalRecord(page,
                  mappedStatement, connection);
           //获取分页Sql语句
           String pageSql = this.getPageSql(page, sql);
           //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
           ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
       }
       return invocation.proceed();
    }
 
 
    /**
     * 拦截器对应的封装原始对象的方法
     */
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }
 
    /**
     * 设置注册拦截器时设定的属性
     */
    public void setProperties(Properties properties) {
       this.databaseType = properties.getProperty("databaseType");
    }
   
    /**
     * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle
     * 其它的数据库都 没有进行分页
     *
     * @param page 分页对象
     * @param sql 原sql语句
     * @return
     */
    private String getPageSql(Page<?> page, String sql) {
       StringBuffer sqlBuffer = new StringBuffer(sql);
       if ("mysql".equalsIgnoreCase(databaseType)) {
           return getMysqlPageSql(page, sqlBuffer);
       } else if ("oracle".equalsIgnoreCase(databaseType)) {
           return getOraclePageSql(page, sqlBuffer);
       }
       return sqlBuffer.toString();
    }
   
    /**
     * 获取Mysql数据库的分页查询语句
     * @param page 分页对象
     * @param sqlBuffer 包含原sql语句的StringBuffer对象
     * @return Mysql数据库分页语句
     */
    private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
       //计算第一条记录的位置,Mysql中记录的位置是从0开始的。
       int offset = (page.getPageNo() - 1) * page.getPageSize();
       sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
       return sqlBuffer.toString();
    }
   
    /**
     * 获取Oracle数据库的分页查询语句
     * @param page 分页对象
     * @param sqlBuffer 包含原sql语句的StringBuffer对象
     * @return Oracle数据库的分页查询语句
     */
    private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
       //计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
       int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
       sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
       sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
       //上面的Sql语句拼接之后大概是这个样子:
       //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
       return sqlBuffer.toString();
    }
   
    /**
     * 给当前的参数对象page设置总记录数
     *
     * @param page Mapper映射语句对应的参数对象
     * @param mappedStatement Mapper映射语句
     * @param connection 当前的数据库连接
     */
    private void setTotalRecord(Page<?> page,
           MappedStatement mappedStatement, Connection connection) {
       //获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
       //delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
       BoundSql boundSql = mappedStatement.getBoundSql(page);
       //获取到我们自己写在Mapper映射语句中对应的Sql语句
       String sql = boundSql.getSql();
       //通过查询Sql语句获取到对应的计算总记录数的sql语句
       String countSql = this.getCountSql(sql);
       //通过BoundSql获取对应的参数映射
       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
       //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
       BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
       //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
       ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
       //通过connection建立一个countSql对应的PreparedStatement对象。
       PreparedStatement pstmt = null;
       ResultSet rs = null;
       try {
           pstmt = connection.prepareStatement(countSql);
           //通过parameterHandler给PreparedStatement对象设置参数
           parameterHandler.setParameters(pstmt);
           //之后就是执行获取总记录数的Sql语句和获取结果了。
           rs = pstmt.executeQuery();
           if (rs.next()) {
              int totalRecord = rs.getInt(1);
              //给当前的参数page对象设置总记录数
              page.setTotalRecord(totalRecord);
           }
       } catch (SQLException e) {
           e.printStackTrace();
       } finally {
           try {
              if (rs != null)
                  rs.close();
               if (pstmt != null)
                  pstmt.close();
           } catch (SQLException e) {
              e.printStackTrace();
           }
       }
    }
   
    /**
     * 根据原Sql语句获取对应的查询总记录数的Sql语句
     * @param sql
     * @return
     */
    private String getCountSql(String sql) {
       
       return "select count(1) from (" + sql + ")";
    }
   
    /**
     * 利用反射进行操作的一个工具类
     *
     */
    private static class ReflectUtil {
       /**
        * 利用反射获取指定对象的指定属性
        * @param obj 目标对象
        * @param fieldName 目标属性
        * @return 目标属性的值
        */
       public static Object getFieldValue(Object obj, String fieldName) {
           Object result = null;
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              field.setAccessible(true);
              try {
                  result = field.get(obj);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
           return result;
       }
      
       /**
        * 利用反射获取指定对象里面的指定属性
        * @param obj 目标对象
        * @param fieldName 目标属性
        * @return 目标字段
        */
       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) {
                  //这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
              }
           }
           return field;
       }
 
       /**
        * 利用反射设置指定对象的指定属性为指定的值
        * @param obj 目标对象
        * @param fieldName 目标属性
         * @param fieldValue 目标值
        */
       public static void setFieldValue(Object obj, String fieldName,
              String fieldValue) {
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              try {
                  field.setAccessible(true);
                  field.set(obj, fieldValue);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
        }
    }
 
}

 

       接着我们在Mybatis的配置文件里面注册该拦截器:

<?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="config/jdbc.properties"></properties>
    <typeAliases>
       <package name="com.tiantian.mybatis.model"/>
    </typeAliases>
    <plugins>
       <plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">
           <property name="databaseType" value="Oracle"/>
       </plugin>
    </plugins>
    <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC" />
           <dataSource type="POOLED">
              <property name="driver" value="${jdbc.driver}" />
              <property name="url" value="${jdbc.url}" />
               <property name="username" value="${jdbc.username}" />
              <property name="password" value="${jdbc.password}" />
           </dataSource>
       </environment>
    </environments>
    <mappers>
       <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>
    </mappers>

 

       这样我们的拦截器就已经定义并且配置好了,接下来我们就来测试一下。假设在我们的UserMapper.xml中有如下这样一个Mapper映射信息:

    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>

 

       那我们就可以这样来测试它:

       SqlSession sqlSession = sqlSessionFactory.openSession();
       try {
           UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
           Page<User> page = new Page<User>();
           page.setPageNo(2);
           List<User> users = userMapper.findPage(page);
           page.setResults(users);
           System.out.println(page);
       } finally {
           sqlSession.close();
       }

 

 

34
3
分享到:
评论
28 楼 234390216 2013-08-20  
foxer 写道
是呀,我这个就是利用top拼结成的,所以造成了两个原生SQL语句的问题,网上SQL2000分页的原理都是这样的。

想办法变通一下,像我刚刚那样用就是可以的。
27 楼 foxer 2013-08-20  
是呀,我这个就是利用top拼结成的,所以造成了两个原生SQL语句的问题,网上SQL2000分页的原理都是这样的。
26 楼 234390216 2013-08-20  
foxer 写道
楼主,那要在SQL2000上分页应如何解决呢?
MS-SQL2000的分页语句是太差了,没有mySQL方便有limit关键字。
或者说能否在拦截器中处理一下:把原来的参数复制一组出来,再赋值给BoundSQL?

Oracle也没有limit啊,但是它有rownum,SqlServer我不怎么了解,不过看你写的它有个关键字top,就可以利用这个top来进行分页了。假设有一个表tt,要查它的第31-40条记录,那就相当于先取顺序的前40条,再取逆序的前10条,如:
select top 10 * from (select top 40 * from tt where name = ? order by id) order by id desc
,仅供参考,具体的你可以Google一下,我对SqlServer不了解,或者如果可以的话改用Mysql。
25 楼 foxer 2013-08-20  
楼主,那要在SQL2000上分页应如何解决呢?
MS-SQL2000的分页语句是太差了,没有mySQL方便有limit关键字。
或者说能否在拦截器中处理一下:把原来的参数复制一组出来,再赋值给BoundSQL?
24 楼 234390216 2013-08-20  
foxer 写道
 <select id="findUsersPage2"  resultMap="userMap_select"  parameterType="Page" >  
    select * from vincent_user  where  name like #{params.name}  
  </select> 


以上设置运行,跟踪sql命令为:

SELECT TOP 3  *  FROM   
( select * from vincent_user   where  name like  ? )  t
WHERE        id NOT IN 
(    SELECT TOP 0 [id]    FROM 
      ( select * from vincent_user   where  name like ? ) t      
     ORDER BY       [id] DESC
)    ORDER BY [id] DESC

很奇怪,这里把where  子句去掉,这个分页语句是可以运行的:

 <select id="findUsersPage2"  resultMap="userMap_select"  parameterType="Page" >  
    select * from vincent_user   
  </select> 

输出结果如下:
SELECT TOP 3  * 
FROM   
    ( select * from vincent_user   )  t
WHERE        id NOT IN
       (    SELECT TOP 0 [id]  
              FROM        ( select * from vincent_user       ) t       
              ORDER BY       [id] DESC
        )  
ORDER BY [id] DESC

------------list<parameterMapping>----------------

totalPage:4
a11
a10
a9

好像是加where子句,就不能识别参数了.

使用了where也是没有问题的,你的问题在于你在拦截到SQL的时候使用了两遍原生SQL,导致SQL中包含两个变量,即两个占位符“?”,这个时候你的SQL是这样的,
SELECT TOP 3  *  FROM    
( select * from vincent_user   where  name like  ? )  t 
WHERE        id NOT IN  
(    SELECT TOP 0 [id]    FROM  
      ( select * from vincent_user   where  name like ? ) t       
     ORDER BY       [id] DESC 
)    ORDER BY [id] DESC 
,但是最开始的时候Mybatis保留的参数就只有一个,而且是会赋值给第一个占位符,这样就导致第二个占位符没有被赋值,出现你上述的异常:
引用
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 没有为参数号 2 设置值。

23 楼 foxer 2013-08-20  
 <select id="findUsersPage2"  resultMap="userMap_select"  parameterType="Page" >  
    select * from vincent_user  where  name like #{params.name}  
  </select> 


以上设置运行,跟踪sql命令为:

SELECT TOP 3  *  FROM   
( select * from vincent_user   where  name like  ? )  t
WHERE        id NOT IN 
(    SELECT TOP 0 [id]    FROM 
      ( select * from vincent_user   where  name like ? ) t      
     ORDER BY       [id] DESC
)    ORDER BY [id] DESC

很奇怪,这里把where  子句去掉,这个分页语句是可以运行的:

 <select id="findUsersPage2"  resultMap="userMap_select"  parameterType="Page" >  
    select * from vincent_user   
  </select> 

输出结果如下:
SELECT TOP 3  * 
FROM   
    ( select * from vincent_user   )  t
WHERE        id NOT IN
       (    SELECT TOP 0 [id]  
              FROM        ( select * from vincent_user       ) t       
              ORDER BY       [id] DESC
        )  
ORDER BY [id] DESC

------------list<parameterMapping>----------------

totalPage:4
a11
a10
a9

好像是加where子句,就不能识别参数了.
22 楼 foxer 2013-08-20  
234390216 写道
234390216 写道
234390216 写道
foxer 写道
楼主,最近也在学myBatis,楼主这个例子:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>


如果要查询name like '%ja%'应如何传参?
如何把params中的name值传进去?

我这样写不行:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  where  name like #{params.name}
    </select>

参考这篇http://haohaoxuexi.iteye.com/blog/1333271文章,或者是这篇http://haohaoxuexi.iteye.com/blog/806288

我记得以前用的时候是写的params['name']形式,然后你说这样不行,用params.name也不行,我就纳闷了,怎么会呢?然后刚刚测试了一下,发现写SQL的时候用params['name']是不行的,但是用于if判断时还是有用的,如:
<if test="params['name'] != '' and params['name'] != null">
				name like #{params.name}
			</if>

经过测试发现使用params.name是没有问题的,如果你使用params.name的时候有问题,请把你的java代码和相关的错误信息贴给我看看。

用于if判断时写
			<if test="params.name != '' and params.name != null">
				name like #{params.name}
			</if>
也是没有问题的,对于mybatis映射文件中那些动态标签中所使用的表达式,mybatis是以OGNL来解析的,而对于那些动态的变量mybatis主要是通过“.”来分割的,记得以前看它的源码时好像它也支持以中括号的形式来表示一个元素,不过好像只适用于集合类。


我的代码如下:
 @Test
  public void findUsersByPage2(){
      SqlSession sqlSession = sqlSessionFactory.openSession();
      try {
          UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
          Map<String, Object> params=new HashMap<String, Object>();
          params.put("name", "%an%");
          
          Page<User> page = new Page<User>();
          page.setPageNo(1);
          page.setPageSize(3);
          page.setParams(params);
          
          List<User> users = userMapper.findUsersPage2(page);
          System.out.println("--------------------------------------------------");
          System.out.println("totalPage:"+page.getTotalPage());
          if (users!=null){
	           for (User user : users) {
	        	   System.out.println(user.getName());
	           }
          }
      } finally {
          sqlSession.close();
      }
  }

//这里我加多了一个MS-SQL2000的分页语句,因为我用的是SQL2000数据库

 private String getPageSql(Page<?> page, String sql) {
       StringBuffer sqlBuffer = new StringBuffer(sql);
       if ("mysql".equalsIgnoreCase(databaseType)) {
           return getMysqlPageSql(page, sqlBuffer);
       } else if ("oracle".equalsIgnoreCase(databaseType)) {
           return getOraclePageSql(page, sqlBuffer);
       }else if("mssql".equalsIgnoreCase(databaseType)) {
    	   return getMsSQLPageSql(page, sqlBuffer);
		   
       }
       return sqlBuffer.toString();
    }

 /**
     * 获取MS-SQL数据库分页查询语句
     * @param page
     * @param sqlBuffer
     * @return
     */
    private String getMsSQLPageSql(Page<?> page, StringBuffer sqlBuffer) {
    	
    	int pageSize=page.getPageSize();
    	int pageNo=page.getPageNo();
    	String sqlCmd=sqlBuffer.toString();
    	String cmdString="SELECT TOP "+pageSize+"  * "+
    					 "	FROM   "+      
    					 "  ( " +
    					 		sqlCmd+
    					 "   )  t "+
    					 " WHERE        id NOT IN "+
    					 " ( "+
    					 "   SELECT TOP "+((pageNo-1)*pageSize)                         +" [id] "+
    					 "   FROM  "+ 
    					 "      ( "+
    					 		   sqlCmd+
    					 "       ) t  "+
    					 "      ORDER BY       [id] DESC "+
    					 "	)  "+
    					 "  ORDER BY [id] DESC ";
    	
    	System.out.println(cmdString);
    	return cmdString;
    	
    }


    <select id="findUsersPage2"  resultMap="userMap_select"  parameterType="Page" >
		select * from vincent_user  where  name like #{params.name}
     </select>

以上代码运行就是不行,提示:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 没有为参数号 2 设置值。
### The error may involve Mapper.UserMapper.findUsersPage2-Inline
### The error occurred while setting parameters
### Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 没有为参数号 2 设置值。
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:8)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:81)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:73)
at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:100)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:70)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:25)
at $Proxy7.findUsersPage2(Unknown Source)
at test.TestDemo.findUsersByPage2(TestDemo.java:312)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: 没有为参数号 2 设置值。
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(Unknown Source)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.buildParamTypeDefinitions(Unknown Source)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.buildPreparedStrings(Unknown Source)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doPrepExec(Unknown Source)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.execute(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:45)
at $Proxy9.execute(Unknown Source)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:39)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:44)
at $Proxy8.query(Unknown Source)
at org.apache.ibatis.executor.ReuseExecutor.doQuery(ReuseExecutor.java:39)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:243)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:117)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:79)
... 30 more


21 楼 234390216 2013-08-19  
234390216 写道
234390216 写道
foxer 写道
楼主,最近也在学myBatis,楼主这个例子:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>


如果要查询name like '%ja%'应如何传参?
如何把params中的name值传进去?

我这样写不行:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  where  name like #{params.name}
    </select>

参考这篇http://haohaoxuexi.iteye.com/blog/1333271文章,或者是这篇http://haohaoxuexi.iteye.com/blog/806288

我记得以前用的时候是写的params['name']形式,然后你说这样不行,用params.name也不行,我就纳闷了,怎么会呢?然后刚刚测试了一下,发现写SQL的时候用params['name']是不行的,但是用于if判断时还是有用的,如:
<if test="params['name'] != '' and params['name'] != null">
				name like #{params.name}
			</if>

经过测试发现使用params.name是没有问题的,如果你使用params.name的时候有问题,请把你的java代码和相关的错误信息贴给我看看。

用于if判断时写
			<if test="params.name != '' and params.name != null">
				name like #{params.name}
			</if>
也是没有问题的,对于mybatis映射文件中那些动态标签中所使用的表达式,mybatis是以OGNL来解析的,而对于那些动态的变量mybatis主要是通过“.”来分割的,记得以前看它的源码时好像它也支持以中括号的形式来表示一个元素,不过好像只适用于集合类。
20 楼 234390216 2013-08-19  
234390216 写道
foxer 写道
楼主,最近也在学myBatis,楼主这个例子:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>


如果要查询name like '%ja%'应如何传参?
如何把params中的name值传进去?

我这样写不行:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  where  name like #{params.name}
    </select>

参考这篇http://haohaoxuexi.iteye.com/blog/1333271文章,或者是这篇http://haohaoxuexi.iteye.com/blog/806288

我记得以前用的时候是写的params['name']形式,然后你说这样不行,用params.name也不行,我就纳闷了,怎么会呢?然后刚刚测试了一下,发现写SQL的时候用params['name']是不行的,但是用于if判断时还是有用的,如:
<if test="params['name'] != '' and params['name'] != null">
				name like #{params.name}
			</if>

经过测试发现使用params.name是没有问题的,如果你使用params.name的时候有问题,请把你的java代码和相关的错误信息贴给我看看。
19 楼 foxer 2013-08-19  
楼主不行呀,报以下错误,还要自定义类型转换? 麻烦帮看一下.


org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: There was no TypeHandler found for parameter params['name'] of statement com.kdd.mapper.BlogMapper.selectBlog
### The error may involve com.dyy.mapper.BlogMapper.selectBlog-Inline
### The error occurred while setting parameters
### Cause: org.apache.ibatis.executor.ExecutorException: There was no TypeHandler found for parameter params['name'] of statement com.dyy.mapper.BlogMapper.selectBlog
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:8)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:81)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:73)
	at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:100)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:70)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:25)
	at $Proxy5.selectBlog(Unknown Source)
	at com.dyy.test.BlogTest.selectBlog(BlogTest.java:118)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.apache.ibatis.executor.ExecutorException: There was no TypeHandler found for parameter params['name'] of statement com.kdd.mapper.BlogMapper.selectBlog
	at org.apache.ibatis.executor.parameter.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:71)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:61)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:43)
	at org.apache.ibatis.executor.ReuseExecutor.prepareStatement(ReuseExecutor.java:63)
	at org.apache.ibatis.executor.ReuseExecutor.doQuery(ReuseExecutor.java:38)
	at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:243)
	at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:117)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:79)
	... 29 more






18 楼 foxer 2013-08-19  

    <select id="findPage" resultType="User" parameterType="Page">
       select * from t_user  where name like #{params[name]}
    </select>

楼主是这样吗?
17 楼 234390216 2013-08-19  
foxer 写道
iteye帖发错了怎么不能编辑呀,有待改进呀,重发如下:

       SqlSession sqlSession = sqlSessionFactory.openSession();
       try {
           UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
           Page<User> page = new Page<User>();
           page.setPageNo(2);
//这个map是我要额外查询的条件
Map    p=new HashMap<String,Object>();
p.put("name","%张三%");
page.setParams(p);    

           List<User> users = userMapper.findPage(page);
           page.setResults(users);
           System.out.println(page);
       } finally {
           sqlSession.close();
       }



楼主,我现在的问题是不知道如何传参,因为我们在Mapper映射文件中传的是page参数,但我们的额外查询条件是保存在Page类的params属性中的(page.getParams()),在

[color=red]
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  //不知这里的where条件如何加,请代码明示,谢谢
    </select>

[/color]

这样写params[name]
16 楼 foxer 2013-08-18  
iteye帖发错了怎么不能编辑呀,有待改进呀,重发如下:

       SqlSession sqlSession = sqlSessionFactory.openSession();
       try {
           UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
           Page<User> page = new Page<User>();
           page.setPageNo(2);
//这个map是我要额外查询的条件
Map    p=new HashMap<String,Object>();
p.put("name","%张三%");
page.setParams(p);    

           List<User> users = userMapper.findPage(page);
           page.setResults(users);
           System.out.println(page);
       } finally {
           sqlSession.close();
       }



楼主,我现在的问题是不知道如何传参,因为我们在Mapper映射文件中传的是page参数,但我们的额外查询条件是保存在Page类的params属性中的(page.getParams()),在

[color=red]
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  //不知这里的where条件如何加,请代码明示,谢谢
    </select>

[/color]
15 楼 foxer 2013-08-18  
楼主理解错我意思了,我以你这个分页类作测试,假如:

       SqlSession sqlSession = sqlSessionFactory.openSession();
       try {
           UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
           Page<User> page = new Page<User>();
           page.setPageNo(2);

Map    p=new HashMap<String,Object>();
p.put("name","%张三%");

           List<User> users = userMapper.findPage(page);
           page.setResults(users);
           System.out.println(page);
       } finally {
           sqlSession.close();
       }

14 楼 234390216 2013-08-18  
foxer 写道
楼主,最近也在学myBatis,楼主这个例子:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>


如果要查询name like '%ja%'应如何传参?
如何把params中的name值传进去?

我这样写不行:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  where  name like #{params.name}
    </select>

参考这篇http://haohaoxuexi.iteye.com/blog/1333271文章,或者是这篇http://haohaoxuexi.iteye.com/blog/806288
13 楼 foxer 2013-08-18  
楼主,最近也在学myBatis,楼主这个例子:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user
    </select>


如果要查询name like '%ja%'应如何传参?
如何把params中的name值传进去?

我这样写不行:
    <select id="findPage" resultType="User" parameterType="page">
       select * from t_user  where  name like #{params.name}
    </select>
12 楼 234390216 2013-06-24  
234390216 写道
zhulongxing_sz 写道
太麻烦了吧,为了一个分页,做如此多的工作???

这就要看你是怎么看待这个问题的,比如我看到的就是很多通用的东西都可以通过这么一个插件来解决。

偶然又看到这个评论了,回复一下。利用拦截器进行分页可以减少对数据库的依赖,比如使用Mysql进行分页的时候是用的limit关键字,而使用Oracle进行分页时是通过rownum进行的。那么假如你的一个项目在开发的时候是基于Mysql的,这个时候你把这些对数据库有特殊依赖的分页语句直接写在Mapper映射文件里面,那么当以后你要把它移植到Oracle数据库的时候就需要把这些Mapper映射文件一个个改过来,如果你的Mapper映射文件比较少的时候,那改改问题也不大,但当你的Mapper文件有几十甚至上百个的时候那改起来就比较麻烦了。而使用分页拦截器的话就比较简单了,我们可以根据不能的数据库在分页拦截器里面执行不同的逻辑。
11 楼 234390216 2013-06-24  
sgq0085 写道
楼主,能上传一份基于Maven的项目么?

Maven主要是用来管理依赖的,跟怎么用Mybatis是没有关系的。工作中的真实项目我不能直接上传到网上的。
如果确实有需要,后续我可以整一个基于Maven的小项目发给你。先简单的给你看一些使用Maven的依赖吧,这些都是在Maven项目的pom.xml中定义的,如有其它问题请留言。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.7.0_21</version>
<scope>system</scope>
<systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
10 楼 sgq0085 2013-06-24  
楼主,能上传一份基于Maven的项目么?
9 楼 earls 2013-04-28  
234390216 写道
earls 写道
这种方式对sql的性能影响很大,尤其是大数据量的情况下。
在mysql下,select count(*) from (select id from table)比select count(id) from table的性能损耗高出几十甚至百倍

博文中在计算总记录数的时候没有用到子查询,确实count(*)比count(column)要慢一些,上面博文介绍的只是一种思想而已,没有考虑一些优化的问题。


这里有不够严谨的地方,经实际数据库简单测试,结果如下:
1、使用5000数据量的表。查询id,二者性能不差上下,查询*,前者耗时比后者是10倍左右。
2、使用400万数据量的表,查询*,前者耗时是后者的350倍左右。
耗时主要集中在“Sending data”和“removing tmp table”两项上。可见随着数据量的不同,耗时还是有很大区别的。该分页方法在大数据量表上使用会出现严重性能问题。


我目前采用的做法是写一个DaoHelper包装Paging,然后由它执行count和list操作,完成总数统计和分页查询。代码如下:
<select id="findCounter" resultType="int">
    SELECT COUNT(id) FROM user_info WHERE created_time >= #{createdTime};
  </select>
 
  <select id="findSelector" resultType="User">
    SELECT * FROM user_info WHERE created_time >= #{createdTime} LIMIT #{offset}, #{limit};
  </select>


public void paging(Paging paging, HashMap<String, Object> params, String sqlName){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        if(paging.isCounted()){
            paging.setTotal(((Number)sqlSession.selectOne(sqlName + "Counter", params)).intValue());
        }
       
        if(paging.getTotal() > paging.firstResult() || !paging.isCounted()){
            params.put("offset", paging.firstResult());
            params.put("limit", paging.getPageSize());
            paging.setResults(sqlSession.selectList(sqlName + "Selector", params));
        }
    }


这种方式实现的其实不如拦截器优雅,但效率高,而且sql语句可以重用。对于DaoHelper的实现可以使用Dialect对其分页操作进行数据库适配。

相关推荐

    Mybatis拦截器介绍及分页插件示例

    ### Mybatis拦截器介绍及分页插件 #### 1.2 前言 在Mybatis框架中,拦截器(Interceptor)是一个重要的组件,它允许开发者在不修改框架核心逻辑的情况下,添加自定义的行为。例如,可以在SQL执行前后添加日志记录...

    Mybatis拦截器介绍及分页插件.pdf

    ### Mybatis拦截器介绍及分页插件 #### 1.1 前言与概述 在探讨Mybatis拦截器及其分页插件之前,我们首先需要了解什么是拦截器以及为何要在Mybatis中使用它。 **拦截器**是一种设计模式,它允许在执行某个操作前或...

    MyBatis拦截器及分页插件

    MyBatis拦截器及分页插件 MyBatis拦截器是MyBatis框架中一个强大的功能,它允许用户在执行某些方法之前或之后执行某些逻辑。拦截器的使用可以极大地扩展MyBatis的功能,使其更加灵活和强大。 拦截器的设计初衷是...

    mybatis使用拦截器实现分页操作

    MyBatis拦截器类似于Java的AOP(面向切面编程)中的拦截器,它可以在特定的方法调用前后插入自定义的行为。在MyBatis中,我们可以通过实现`org.apache.ibatis.plugin.Interceptor`接口并重写`intercept`方法来创建一...

    MyBatis拦截器分页与动态修改SQL及其参数值

    在"MyBatis拦截器分页与动态修改SQL及其参数值"的主题中,我们可以深入理解以下几个关键知识点: 1. **MyBatis拦截器**:MyBatis提供了一种插件机制,即拦截器(Interceptor),它基于Java的动态代理,可以在SQL...

    springboot+mybatis拦截器实现自动分页

    MyBatis本身并不直接支持分页,但可以通过拦截器或者自定义插件的方式来实现。这里我们将使用拦截器。 1. **创建拦截器**: 首先,我们需要创建一个实现了`Interceptor`接口的类。在`intercept`方法中,我们可以获取...

    mybatis 分页拦截器及拦截器配置

    MyBatis 分页拦截器是实现数据库查询分页效果的一种高效解决方案。在传统的SQL查询中,我们通常需要手动编写...在实际项目中,根据业务需求选择合适的分页插件,并正确配置拦截器,能够有效提升开发效率和代码质量。

    Mybatis分页拦截器

    总的来说,Mybatis分页拦截器是提升项目效率的一个重要工具,它通过插件化的方式,使开发者能方便地为Mybatis添加自定义的分页功能,同时保持代码的整洁和可维护性。在不同版本的Mybatis中,正确理解和使用拦截器,...

    mybatis分页拦截器(自动封装版)剖析.pdf

    在分页拦截器内部,一般会利用MyBatis提供的插件机制,通过实现`Interceptor`接口并重写`intercept`方法来插入分页逻辑。在`intercept`方法中,拦截器会检查被拦截的SQL语句,并根据PageBean中的信息动态拼接分页...

    spring+springMVC+mybatis拦截器分页 源码

    综上所述,"spring+springMVC+mybatis拦截器分页"项目结合了三大框架的优势,通过SpringMVC的拦截器实现业务逻辑的扩展,利用MyBatis的分页插件处理后台数据,再由EasyUI提供友好的用户界面。这样的组合为高效且可控...

    mybatis拦截器分页

    - MyBatis 还提供了 PageHelper 插件,它也实现了分页功能,但与自定义拦截器相比,可能在灵活性和定制性上稍有不足。 - 使用拦截器可以与现有的分页库(如 MyBatis-Plus)无缝集成,或者完全独立实现,从而更好地...

    mybatis代码生成器和分页插件.zip

    在这个“mybatis代码生成器和分页插件.zip”压缩包中,包含了两个重要的工具,即MyBatis的代码生成器和分页插件,它们极大地提高了开发效率并优化了数据库操作。 1. MyBatis 代码生成器(MyBatis Generator, MBG) ...

    mybatis分页插件的使用

    - **PageHelper**: 分页插件的核心类,实现了Mybatis拦截器接口,负责处理分页逻辑。 - **PageInfo**: 包装类,提供了丰富的分页属性信息,如总记录数、当前页码等。 - **SqlParser**: 可选组件,用于生成高效的...

    mybatis分页拦截器

    MyBatis拦截器是基于AOP(面向切面编程)的一种机制,它允许我们在特定的执行点(如SQL语句的执行、参数的设置等)插入自定义的行为。分页拦截器就是在执行SQL之前或之后,对SQL语句进行修改,添加必要的分页条件,...

    mybatis拦截器与分页插件实例教程

    所以下面这篇文章主要给大家介绍了关于mybatis拦截器与分页插件的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    Mybatis拦截器实现分页

    【Mybatis拦截器实现分页】 在Mybatis框架中,拦截器是一种强大的工具,它允许我们在执行SQL之前或之后进行额外的操作,如日志记录、权限检查等。本篇文章将探讨如何使用Mybatis拦截器来实现高效且方便的分页功能。 ...

    Mybatis PageHelper分页插件是一个应用于Mybatis中的分页插件系统.rar

    分页插件PageHelper是通过mybatis的拦截器实现分页功能的,拦截sql查询请求,添加分页语句,最终实现分页查询功能。 一、分页插件PageHelper支持的数据库类型? Oracle,MySql,MariaDB,SQLite等 二、分页插件...

Global site tag (gtag.js) - Google Analytics