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接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

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

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

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

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

  1. package org.apache.ibatis.plugin;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.util.HashMap;
  6. import java.util.HashSet;
  7. import java.util.Map;
  8. import java.util.Set;
  9. import org.apache.ibatis.reflection.ExceptionUtil;
  10. public class Plugin implements InvocationHandler {
  11. private Object target;
  12. private Interceptor interceptor;
  13. private Map<Class<?>, Set<Method>> signatureMap;
  14. private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
  15. this.target = target;
  16. this.interceptor = interceptor;
  17. this.signatureMap = signatureMap;
  18. }
  19. public static Object wrap(Object target, Interceptor interceptor) {
  20. Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  21. Class<?> type = target.getClass();
  22. Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  23. if (interfaces.length > 0) {
  24. return Proxy.newProxyInstance(
  25. type.getClassLoader(),
  26. interfaces,
  27. new Plugin(target, interceptor, signatureMap));
  28. }
  29. return target;
  30. }
  31. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  32. try {
  33. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  34. if (methods != null && methods.contains(method)) {
  35. return interceptor.intercept(new Invocation(target, method, args));
  36. }
  37. return method.invoke(target, args);
  38. } catch (Exception e) {
  39. throw ExceptionUtil.unwrapThrowable(e);
  40. }
  41. }
  42. private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  43. Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  44. if (interceptsAnnotation == null) { // issue #251
  45. throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  46. }
  47. Signature[] sigs = interceptsAnnotation.value();
  48. Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
  49. for (Signature sig : sigs) {
  50. Set<Method> methods = signatureMap.get(sig.type());
  51. if (methods == null) {
  52. methods = new HashSet<Method>();
  53. signatureMap.put(sig.type(), methods);
  54. }
  55. try {
  56. Method method = sig.type().getMethod(sig.method(), sig.args());
  57. methods.add(method);
  58. } catch (NoSuchMethodException e) {
  59. throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  60. }
  61. }
  62. return signatureMap;
  63. }
  64. private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  65. Set<Class<?>> interfaces = new HashSet<Class<?>>();
  66. while (type != null) {
  67. for (Class<?> c : type.getInterfaces()) {
  68. if (signatureMap.containsKey(c)) {
  69. interfaces.add(c);
  70. }
  71. }
  72. type = type.getSuperclass();
  73. }
  74. return interfaces.toArray(new Class<?>[interfaces.size()]);
  75. }
  76. }

我们先看一下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:

  1. package com.tiantian.mybatis.interceptor;
  2. import java.sql.Connection;
  3. import java.util.Properties;
  4. import org.apache.ibatis.executor.Executor;
  5. import org.apache.ibatis.executor.statement.StatementHandler;
  6. import org.apache.ibatis.mapping.MappedStatement;
  7. import org.apache.ibatis.plugin.Interceptor;
  8. import org.apache.ibatis.plugin.Intercepts;
  9. import org.apache.ibatis.plugin.Invocation;
  10. import org.apache.ibatis.plugin.Plugin;
  11. import org.apache.ibatis.plugin.Signature;
  12. import org.apache.ibatis.session.ResultHandler;
  13. import org.apache.ibatis.session.RowBounds;
  14. @Intercepts( {
  15. @Signature(method = "query", type = Executor.class, args = {
  16. MappedStatement.class, Object.class, RowBounds.class,
  17. ResultHandler.class }),
  18. @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
  19. public class MyInterceptor implements Interceptor {
  20. public Object intercept(Invocation invocation) throws Throwable {
  21. Object result = invocation.proceed();
  22. System.out.println("Invocation.proceed()");
  23. return result;
  24. }
  25. public Object plugin(Object target) {
  26. return Plugin.wrap(target, this);
  27. }
  28. public void setProperties(Properties properties) {
  29. String prop1 = properties.getProperty("prop1");
  30. String prop2 = properties.getProperty("prop2");
  31. System.out.println(prop1 + "------" + prop2);
  32. }
  33. }

首先看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:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <properties resource="config/jdbc.properties"></properties>
  7. <typeAliases>
  8. <package name="com.tiantian.mybatis.model"/>
  9. </typeAliases>
  10. <plugins>
  11. <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
  12. <property name="prop1" value="prop1"/>
  13. <property name="prop2" value="prop2"/>
  14. </plugin>
  15. </plugins>
  16. <environments default="development">
  17. <environment id="development">
  18. <transactionManager type="JDBC" />
  19. <dataSource type="POOLED">
  20. <property name="driver" value="${jdbc.driver}" />
  21. <property name="url" value="${jdbc.url}" />
  22. <property name="username" value="${jdbc.username}" />
  23. <property name="password" value="${jdbc.password}" />
  24. </dataSource>
  25. </environment>
  26. </environments>
  27. <mappers>
  28. <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>
  29. </mappers>
  30. </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:

  1. import java.util.HashMap;
  2. import java.util.List;
  3. import java.util.Map;
  4. /**
  5. * 对分页的基本数据进行一个简单的封装
  6. */
  7. public class Page<T> {
  8. private int pageNo = 1;//页码,默认是第一页
  9. private int pageSize = 15;//每页显示的记录数,默认是15
  10. private int totalRecord;//总记录数
  11. private int totalPage;//总页数
  12. private List<T> results;//对应的当前页记录
  13. private Map<String, Object> params = new HashMap<String, Object>();//其他的参数我们把它分装成一个Map对象
  14. public int getPageNo() {
  15. return pageNo;
  16. }
  17. public void setPageNo(int pageNo) {
  18. this.pageNo = pageNo;
  19. }
  20. public int getPageSize() {
  21. return pageSize;
  22. }
  23. public void setPageSize(int pageSize) {
  24. this.pageSize = pageSize;
  25. }
  26. public int getTotalRecord() {
  27. return totalRecord;
  28. }
  29. public void setTotalRecord(int totalRecord) {
  30. this.totalRecord = totalRecord;
  31. //在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
  32. int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
  33. this.setTotalPage(totalPage);
  34. }
  35. public int getTotalPage() {
  36. return totalPage;
  37. }
  38. public void setTotalPage(int totalPage) {
  39. this.totalPage = totalPage;
  40. }
  41. public List<T> getResults() {
  42. return results;
  43. }
  44. public void setResults(List<T> results) {
  45. this.results = results;
  46. }
  47. public Map<String, Object> getParams() {
  48. return params;
  49. }
  50. public void setParams(Map<String, Object> params) {
  51. this.params = params;
  52. }
  53. @Override
  54. public String toString() {
  55. StringBuilder builder = new StringBuilder();
  56. builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
  57. .append(pageSize).append(", results=").append(results).append(
  58. ", totalPage=").append(totalPage).append(
  59. ", totalRecord=").append(totalRecord).append("]");
  60. return builder.toString();
  61. }
  62. }

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

  1. package com.tiantian.mybatis.interceptor;
  2. import java.lang.reflect.Field;
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.util.List;
  8. import java.util.Properties;
  9. import org.apache.ibatis.executor.parameter.ParameterHandler;
  10. import org.apache.ibatis.executor.statement.RoutingStatementHandler;
  11. import org.apache.ibatis.executor.statement.StatementHandler;
  12. import org.apache.ibatis.mapping.BoundSql;
  13. import org.apache.ibatis.mapping.MappedStatement;
  14. import org.apache.ibatis.mapping.ParameterMapping;
  15. import org.apache.ibatis.plugin.Interceptor;
  16. import org.apache.ibatis.plugin.Intercepts;
  17. import org.apache.ibatis.plugin.Invocation;
  18. import org.apache.ibatis.plugin.Plugin;
  19. import org.apache.ibatis.plugin.Signature;
  20. import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
  21. import com.tiantian.mybatis.model.Page;
  22. /**
  23. *
  24. * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。
  25. * 利用拦截器实现Mybatis分页的原理:
  26. * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句
  27. * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的
  28. * prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用
  29. * StatementHandler对象的prepare方法,即调用invocation.proceed()。
  30. * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设
  31. * 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。
  32. *
  33. */
  34. @Intercepts( {
  35. @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })
  36. public class PageInterceptor implements Interceptor {
  37. private String databaseType;//数据库类型,不同的数据库有不同的分页方法
  38. /**
  39. * 拦截后要执行的方法
  40. */
  41. public Object intercept(Invocation invocation) throws Throwable {
  42. //对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
  43. //BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
  44. //SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是
  45. //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
  46. //StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
  47. //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
  48. //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候
  49. //是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
  50. RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
  51. //通过反射获取到当前RoutingStatementHandler对象的delegate属性
  52. StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
  53. //获取到当前StatementHandler的 boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
  54. //RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
  55. BoundSql boundSql = delegate.getBoundSql();
  56. //拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
  57. Object obj = boundSql.getParameterObject();
  58. //这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
  59. if (obj instanceof Page<?>) {
  60. Page<?> page = (Page<?>) obj;
  61. //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
  62. MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
  63. //拦截到的prepare方法参数是一个Connection对象
  64. Connection connection = (Connection)invocation.getArgs()[0];
  65. //获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
  66. String sql = boundSql.getSql();
  67. //给当前的page参数对象设置总记录数
  68. this.setTotalRecord(page,
  69. mappedStatement, connection);
  70. //获取分页Sql语句
  71. String pageSql = this.getPageSql(page, sql);
  72. //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
  73. ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
  74. }
  75. return invocation.proceed();
  76. }
  77. /**
  78. * 拦截器对应的封装原始对象的方法
  79. */
  80. public Object plugin(Object target) {
  81. return Plugin.wrap(target, this);
  82. }
  83. /**
  84. * 设置注册拦截器时设定的属性
  85. */
  86. public void setProperties(Properties properties) {
  87. this.databaseType = properties.getProperty("databaseType");
  88. }
  89. /**
  90. * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle
  91. * 其它的数据库都 没有进行分页
  92. *
  93. * @param page 分页对象
  94. * @param sql 原sql语句
  95. * @return
  96. */
  97. private String getPageSql(Page<?> page, String sql) {
  98. StringBuffer sqlBuffer = new StringBuffer(sql);
  99. if ("mysql".equalsIgnoreCase(databaseType)) {
  100. return getMysqlPageSql(page, sqlBuffer);
  101. } else if ("oracle".equalsIgnoreCase(databaseType)) {
  102. return getOraclePageSql(page, sqlBuffer);
  103. }
  104. return sqlBuffer.toString();
  105. }
  106. /**
  107. * 获取Mysql数据库的分页查询语句
  108. * @param page 分页对象
  109. * @param sqlBuffer 包含原sql语句的StringBuffer对象
  110. * @return Mysql数据库分页语句
  111. */
  112. private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
  113. //计算第一条记录的位置,Mysql中记录的位置是从0开始的。
  114. int offset = (page.getPageNo() - 1) * page.getPageSize();
  115. sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
  116. return sqlBuffer.toString();
  117. }
  118. /**
  119. * 获取Oracle数据库的分页查询语句
  120. * @param page 分页对象
  121. * @param sqlBuffer 包含原sql语句的StringBuffer对象
  122. * @return Oracle数据库的分页查询语句
  123. */
  124. private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
  125. //计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
  126. int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
  127. sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
  128. sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
  129. //上面的Sql语句拼接之后大概是这个样子:
  130. //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
  131. return sqlBuffer.toString();
  132. }
  133. /**
  134. * 给当前的参数对象page设置总记录数
  135. *
  136. * @param page Mapper映射语句对应的参数对象
  137. * @param mappedStatement Mapper映射语句
  138. * @param connection 当前的数据库连接
  139. */
  140. private void setTotalRecord(Page<?> page,
  141. MappedStatement mappedStatement, Connection connection) {
  142. //获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
  143. //delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
  144. BoundSql boundSql = mappedStatement.getBoundSql(page);
  145. //获取到我们自己写在Mapper映射语句中对应的Sql语句
  146. String sql = boundSql.getSql();
  147. //通过查询Sql语句获取到对应的计算总记录数的sql语句
  148. String countSql = this.getCountSql(sql);
  149. //通过BoundSql获取对应的参数映射
  150. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  151. //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
  152. BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
  153. //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
  154. ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
  155. //通过connection建立一个countSql对应的PreparedStatement对象。
  156. PreparedStatement pstmt = null;
  157. ResultSet rs = null;
  158. try {
  159. pstmt = connection.prepareStatement(countSql);
  160. //通过parameterHandler给PreparedStatement对象设置参数
  161. parameterHandler.setParameters(pstmt);
  162. //之后就是执行获取总记录数的Sql语句和获取结果了。
  163. rs = pstmt.executeQuery();
  164. if (rs.next()) {
  165. int totalRecord = rs.getInt(1);
  166. //给当前的参数page对象设置总记录数
  167. page.setTotalRecord(totalRecord);
  168. }
  169. } catch (SQLException e) {
  170. e.printStackTrace();
  171. } finally {
  172. try {
  173. if (rs != null)
  174. rs.close();
  175. if (pstmt != null)
  176. pstmt.close();
  177. } catch (SQLException e) {
  178. e.printStackTrace();
  179. }
  180. }
  181. }
  182. /**
  183. * 根据原Sql语句获取对应的查询总记录数的Sql语句
  184. * @param sql
  185. * @return
  186. */
  187. private String getCountSql(String sql) {
  188. int index = sql.indexOf("from");
  189. return "select count(*) " + sql.substring(index);
  190. }
  191. /**
  192. * 利用反射进行操作的一个工具类
  193. *
  194. */
  195. private static class ReflectUtil {
  196. /**
  197. * 利用反射获取指定对象的指定属性
  198. * @param obj 目标对象
  199. * @param fieldName 目标属性
  200. * @return 目标属性的值
  201. */
  202. public static Object getFieldValue(Object obj, String fieldName) {
  203. Object result = null;
  204. Field field = ReflectUtil.getField(obj, fieldName);
  205. if (field != null) {
  206. field.setAccessible(true);
  207. try {
  208. result = field.get(obj);
  209. } catch (IllegalArgumentException e) {
  210. // TODO Auto-generated catch block
  211. e.printStackTrace();
  212. } catch (IllegalAccessException e) {
  213. // TODO Auto-generated catch block
  214. e.printStackTrace();
  215. }
  216. }
  217. return result;
  218. }
  219. /**
  220. * 利用反射获取指定对象里面的指定属性
  221. * @param obj 目标对象
  222. * @param fieldName 目标属性
  223. * @return 目标字段
  224. */
  225. private static Field getField(Object obj, String fieldName) {
  226. Field field = null;
  227. for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
  228. try {
  229. field = clazz.getDeclaredField(fieldName);
  230. break;
  231. } catch (NoSuchFieldException e) {
  232. //这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
  233. }
  234. }
  235. return field;
  236. }
  237. /**
  238. * 利用反射设置指定对象的指定属性为指定的值
  239. * @param obj 目标对象
  240. * @param fieldName 目标属性
  241. * @param fieldValue 目标值
  242. */
  243. public static void setFieldValue(Object obj, String fieldName,
  244. String fieldValue) {
  245. Field field = ReflectUtil.getField(obj, fieldName);
  246. if (field != null) {
  247. try {
  248. field.setAccessible(true);
  249. field.set(obj, fieldValue);
  250. } catch (IllegalArgumentException e) {
  251. // TODO Auto-generated catch block
  252. e.printStackTrace();
  253. } catch (IllegalAccessException e) {
  254. // TODO Auto-generated catch block
  255. e.printStackTrace();
  256. }
  257. }
  258. }
  259. }
  260. }

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

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <properties resource="config/jdbc.properties"></properties>
  7. <typeAliases>
  8. <package name="com.tiantian.mybatis.model"/>
  9. </typeAliases>
  10. <plugins>
  11. <plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">
  12. <property name="databaseType" value="Oracle"/>
  13. </plugin>
  14. </plugins>
  15. <environments default="development">
  16. <environment id="development">
  17. <transactionManager type="JDBC" />
  18. <dataSource type="POOLED">
  19. <property name="driver" value="${jdbc.driver}" />
  20. <property name="url" value="${jdbc.url}" />
  21. <property name="username" value="${jdbc.username}" />
  22. <property name="password" value="${jdbc.password}" />
  23. </dataSource>
  24. </environment>
  25. </environments>
  26. <mappers>
  27. <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>
  28. </mappers>

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

  1. <select id="findPage" resultType="User" parameterType="page">
  2. select * from t_user
  3. </select>

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

  1. SqlSession sqlSession = sqlSessionFactory.openSession();
  2. try {
  3. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  4. Page<User> page = new Page<User>();
  5. page.setPageNo(2);
  6. List<User> users = userMapper.findPage(page);
  7. page.setResults(users);
  8. System.out.println(page);
  9. } finally {
  10. sqlSession.close();
  11. }

Mybatis拦截器介绍及分页插件的更多相关文章

  1. Mybatis拦截器介绍

    拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初 ...

  2. MyBatis 拦截器 (实现分页功能)

    由于业务关系 巴拉巴拉巴拉 好吧 简单来说就是 原来的业务是 需要再实现类里写 selectCount 和selectPage两个方法才能实现分页功能 现在想要达到效果是 只通过一个方法就可以实现 也 ...

  3. Mybatis拦截器实现分页

    本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model&g ...

  4. MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...

  5. 【公众号转载】MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...

  6. 【Mybatis】1、Mybatis拦截器学习资料汇总

    MyBatis拦截器原理探究 http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html [myBatis]Mybatis中的拦截器 ...

  7. Mybatis利用拦截器做统一分页

    mybatis利用拦截器做统一分页 查询传递Page参数,或者传递继承Page的对象参数.拦截器查询记录之后,通过改造查询sql获取总记录数.赋值Page对象,返回. 示例项目:https://git ...

  8. Mybatis拦截器(插件实现原理)

    在mybatis的mybatis.cfg.xml中插入: <plugins> <plugin interceptor="cn.sxt.util.PageIntercepto ...

  9. mybatis拦截器使用

    目录 mybatis 拦截器接口Interceptor spring boot + mybatis整合 创建自己的拦截器MyInterceptor @Intercepts注解 mybatis拦截器入门 ...

随机推荐

  1. scala学习手记25 - Curry化

    curry翻译为中文就是咖喱.意为使用curry可以让代码更有味道. scala里的curry化可以把函数从接收多个参数转换成接收多个参数列表.也就是说我们要编写的函数不是只有一个参数列表,这个参数列 ...

  2. scjp考试准备 - 8 - final关键字

    题目,如下代码的执行结果: import java.util.ArrayList; class Pizza{ ArrayList toppings; public final void addTopp ...

  3. BZOJ 3196 Tyvj 1730 二逼平衡树:线段树套splay

    传送门 题意 给你一个长度为 $ n $ 有序数列 $ a $ ,进行 $ m $ 次操作,操作有如下几种: 查询 $ k $ 在区间 $ [l,r] $ 内的排名 查询区间 $ [l,r] $ 内排 ...

  4. 【scala】IO

    1.读文件 可以使用Scala的Source类及其对象来读取文件. Source 类 需要导入 scala.io.Source 然后调用fromFile()方法来读取文件内容 import scala ...

  5. 反汇编工具 objdump的使用简介

    arm-linux-objdump -D led.elf > led_elf.dis objdump是gcc工具链中的反汇编工具,作用是由编译链接好的elf格式的可执行程序反过来得到反汇编代码 ...

  6. vi编辑器:命令模式、输入模式、末行模式

    1.命令模式(command mode)—执行命令 在该模式中,可以输入命令来执行许多种功能.控制屏幕光标的移动,字符.字或行的删除,移动复制某区段及进入Insert mode下,或者到 last l ...

  7. DRF 权限的流程

    DRF 权限的流程 django rest framework,入口是 dispatch,然后依次 --->>封装请求--->>处理版本--->>>认证--- ...

  8. windows下Perl如何读取大文件的最后一行(总结)

    Perl中读取文件最后一行的方法很多,比如 (1)将文件读入数组,取最后一个元素 open (FILE,"file.txt") or die "$!"; my ...

  9. Android数据库代码优化(1) - 从Google的数据库guide说起

    假如我们没有任何在Android上使用SQLite的经验,现在要开始在工作中用SQLite存储一些数据.OK, 我们去看google的官方培训文档吧,http://developer.android. ...

  10. Markdown博文快速转为微信文章

    介绍 技术博文在CSDN上,全是Markdown格式,最近看各位大佬又是个人网站又是个人微信公众号,突然发现: "个人博客小站 + 个人微信公众号 + CSDN + 掘金+ - = 程序员标 ...