mybatis插件原理_mybatis分页查询实现

mybatis插件原理_mybatis分页查询实现目录一、mybatis插件介绍二、mybatis插件原理1、创建2、拦截1)首先是ParameterHandler的创建,在Configuration类当中:2)interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。3、应用1)类相关代码2)插件配置,在sqlMapConfig.xml中三、自定义插件1、插件接口2、自定义插件1)相关类代码2)相关配置sqlMapConfig.xml3)mapper接口4)mapper.

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

一、mybatis 插件介绍

mybatis 插件在四大组件 (Executor、StatementHandler、ParameterHandler、ResultSetHandler) 处提供了简单易用的插件扩展机制。
mybatis 对持久层的操作就是借助于四大核心对象。
myBatis 支持用插件对四大核心对象进行拦截,对 mybatis 来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,myBatis 中的四大对象都是代理对象。

在这里插入图片描述

myBatis 所允许拦截的方法如下:

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

二、mybatis 插件原理

1、创建

在四大对象创建的时候:

  1. 每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler);
  2. 获取到所有的 Interceptor(拦截器)(插件需要实现的接口),调用 interceptor.plugin(target),返回 target 包装后的对象;
  3. 插件机制,我们可以使用插件为目标对象创建一个代理对象,AOP(面向切面)我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。

2、拦截

插件具体是如何拦截并附加额外的功能的呢?以 ParameterHandler 来说:

1)首先是 ParameterHandler 的创建,在 Configuration 类当中:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 
   
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

2)interceptorChain 保存了所有的拦截器(interceptors),是 mybatis 初始化的时候创建的。

public Object pluginAll(Object target) { 
   
  for (Interceptor interceptor : interceptors) { 
   
    target = interceptor.plugin(target);
  }
  return target;
}

调用拦截器链中的拦截器依次的对目标进行拦截或增强。
interceptor.plugin(target) 中的 target 就可以理解为 mybatis 中的四大对象。返回的 target 是被重重代理后的对象。

3、应用

如果我们想要拦截 Executor 的 query 方法,那么可以这样定义插件:

1)类相关代码

@Intercepts({ 
   
    @Signature(
        type = Executor.class,
        method = "query",
        args = { 
   MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
    )
})
public class ExeunplePlugin implements Interceptor { 
   
    //省略逻辑 
}

2)插件配置,在 sqlMapConfig.xml 中

<plugins>
    <plugin interceptor="com.lagou.plugin.ExamplePlugin">
    </plugin>
</plugins>

这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中。
待准备工作做完后,MyBatis 处于就绪状态。
我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession。
Executor 实例会在创建 SqlSession 的过程中被创建, Executor 实例创建完毕后, MyBatis 会通过 JDK 动态代理为实例生成代理类。
这样,插件逻辑即可在 Executor 相关方法被调用前执行。
以上就是 MyBatis 插件机制的基本原理。

三、自定义插件

1、插件接口

Mybatis 插件接口 —— Interceptor

  • Intercept 方法,插件的核心方法
  • plugin 方法,生成 target 的代理对象
  • setProperties 方法,传递插件所需参数

2、自定义插件

设计实现一个自定义插件。

1)相关类代码

@Intercepts(@Signature(    // 注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
        type = StatementHandler.class,    // 这是指拦截哪个接口
        method = "prepare",    // 这个接口内的哪个方法名,不要拼错了
        args = { 
   Connection.class,Integer.class}    
        // 这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
))
public class MyPlugin implements Interceptor { 
   
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 这里是每次执行操作的时候,都会进行这个拦截器的方法内
    @Override
    public Object intercept(Invocation invocation) throws Throwable { 
   
        // 增强逻辑
        System.out.println("增强了参数功能。。");
        // 执行原方法
        return invocation.proceed();
    }

    /** * 主要是为了把这个拦截器生成一个代理放到拦截器链中。 * @Description 包装目标对象,为目标对象创建代理对象。 * @Param target 为要拦截的对象 * @Return 代理对象 */
    @Override
    public Object plugin(Object target) { 
   
        System.out.println("将要包装的目标对象:"+target);
        return Plugin.wrap(target,this);
    }

    /** 获取配置文件的属性 **/ 
    // 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
    @Override
    public void setProperties(Properties properties) { 
   
        System.out.println("插件配置的初始化参数:"+properties);
    }
}

2)相关配置 sqlMapConfig.xml

<plugins>
    <plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
        <!--配置参数-->
        <property name="name" value="Bob"/>
    </plugin>
</plugins>

3)mapper 接口

public interface UserMapper { 
   
    List<User> selectUser();
}

4)mapper.xml 配置

<mapper namespace="com.lagou.mapper.UserMapper">
    <select id="selectUser" resultType="com.lagou.pojo.User">
        select id,username from user
    </select>
</mapper>

5)测试类代码

public class PluginTest { 
   
    
    @Test
    public void test() throws IOException { 
   
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> byPaging = userMapper.selectUser();
        for (User user : byPaging) { 
   
            System.out.println(user);
        }
    }
}

四、源码分析

1、执行插件逻辑

Plugin 实现了 InvocationHandler 接口,因此它的 invoke 方法会拦截所有的方法调用。
invoke 方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。该方法的逻辑如下:

// -Plugin
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
   
    try { 
   
        /* * 获取被拦截方法列表,比如: * signatureMap.get(Executor.class), 可能返回 [query, update, commit] */
        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);
    }
}

invoke 方法的代码比较少,逻辑不难理解。
invoke 方法会检测被拦截方法是否配置在插件的 @Signature 注解中,若是,则执行插件逻辑,否则执行被拦截方法。

2、执行自定义插件

@Override
public Object intercept(Invocation invocation) throws Throwable { 
   
    // 增强逻辑
    System.out.println("增强了参数功能。。");
    // 执行原方法
    return invocation.proceed();
}

插件逻辑封装在 intercept 中,该方法的参数类型为 Invocationo Invocation 主要用于存储目标类,方法以及方法参数列表。

public class Invocation { 
   

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) { 
   
    this.target = target;
    this.method = method;
    this.args = args;
  }
  
  // 省略get方法
  
  public Object proceed() throws InvocationTargetException, IllegalAccessException { 
   
    // 调用被拦截的方法
    return method.invoke(target, args);
  }
}

五、pageHelper 分页插件

myBatis 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper 是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

1、导入插件依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

2、在 mybatis 核心配置文件中配置 PageHelper 插件

<!-- 注意:分页助手的插件,配置在通用馆mapper之前 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
    <!--指定方言-->
    <property name="dialect" value="mysql"/>
</plugin>

3、测试分页代码实现

@Test
public void testPageHelper() { 
   
    
    //设置分页参数
    PageHelper.startPage(1, 2);
    List<User> select = userMapper2.select(null); 
    for (User user : select) { 
   
        System.out.println(user);
    }
    
    //其他分页的相关数据
    PageInfo<User> pageInfo = new PageInfo<User>(select); 
    System.out.println("总条数:" + pageInfo.getTotal()); 
    System.out.println("总页数:" + pageInfo.getPages ()); 
    System.out.println("当前页:" + pageInfo.getPageNum()); 
    System.out.println("每页显万长度:" + pageInfo.getPageSize()); 
    System.out.println("是否第一页:" + pageInfo.isIsFirstPage()); 
    System.out.println("是否最后一页:" + pageInfo.isIsLastPage());
}

六、通用 mapper

通用 mapper 就是为了解决单表增删改查,基于 mybatis 的插件机制。开发人员不需要编写 SQL,不需要在 DAO 中增加方法,只要写好实体类,就能支持相应的增删改查方法。

1、导入插件依赖

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>3.1.2</version>
</dependency>

2、mybatis 配置文件中完成配置

<plugins>
    <!--分页插件:如果有分页插件,要排在通用 mapper 之前-->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <property name="dialect" value="mysql"/>
    </plugin>
    <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
        <!-- 通用 mapper 接口,多个通用接口用逗号隔开 -->
        <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
    </plugin>
</plugins>

3、实体类设置主键

@Table(name = "t_user")
public class User { 
   
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
}

4、定义通用 mapper

import com.lagou.domain.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> { 
   
}

5、测试代码

public class UserTest { 
   
    
    @Test
    public void test1() throws IOException { 
   
        
        Inputstream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setId(4);

        // (1) mapper 基础接口
        
        // select 接口
        // 根据实体中的属性进行查询,只能有—个返回值
        User user1 = userMapper.selectOne(user);
        // 查询全部结果
        List<User> users = userMapper.select(null);
         // 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
        userMapper.selectByPrimaryKey(1); 
        // 根据实体中的属性查询总数,查询条件使用等号
        userMapper.selectCount(user);

        // insert 接口
        // 保存一个实体,null 值也会保存,不会使用数据库默认值
        int insert = userMapper.insert(user);
        // 保存实体,null的属性不会保存,会使用数据库默认值
        int i = userMapper.insertSelective(user); 

        // update 接口
        // 根据主键更新实体全部字段,null 值会被更新
        int i1 = userMapper.updateByPrimaryKey(user);

        // delete 接口
        // 根据实体属性作为条件进行删除,查询条件使用等号
        int delete = userMapper.delete(user);
        // 根据主键字段进行删除,方法参数必须包含完整的主键属性
        userMapper.deleteByPrimaryKey(1); 

        // (2) example 方法
        
        Example example = new Example(User.class);
        example.createCriteria().andEqualTo("id", 1);
        example.createCriteria().andLike("val", "1");
        //自定义查询
        List<User> users1 = userMapper.selectByExample(example);
    }
}

文章内容输出来源:拉勾教育Java高薪训练营;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/184936.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号