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.


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高薪训练营;

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

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

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • QQ农场开挂_农场游戏源码

    QQ农场开挂_农场游戏源码  QQ空间\校友农场外挂源代码12009年08月18日星期二11:41{///////////////////////////////////////////////////////////////////////////////                           声明//本代码不针对任何团体和个人,只为学习\交流\促进游戏的发展而发布//…

    2022年9月12日
  • 如何开发一个 PyCharm 插件[通俗易懂]

    如何开发一个 PyCharm 插件[通俗易懂]PyCharm是很多Python开发者优先选择的IDE,功能强大,跨平台,提供免费社区版,非常良心。如果你想自己给PyCharm添加一些功能怎么办呢?有两个办法:通过提需求实现,到JetBrains的github去提issue或者自己发PullRequest请他们merge。通过安装插件实现,你可以查找现有的插件仓…

    2022年6月24日
  • Navicat Premium 12激活成功教程激活

    Navicat Premium 12激活成功教程激活下载NavicatPremium12并安装;蓝奏云下载:NavicatPremium12注册机重要提示:该注册机来源于DeltaFoX。一般来说,由于注册机会修改.exe文件或.dll文件,加壳并且没有数字签名,所以杀毒软件会报毒。如需使用本注册机或者下载后找不到文件,需要关闭杀毒软件或将本注册机添加至杀毒软件白名单。自行决定是否使用本注册机。以管理员身份运行此注册机:…

    2022年10月25日
  • goland 2021 激活码破解方法

    goland 2021 激活码破解方法,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月15日
  • hp中国区总裁孙振耀职场圣经

    hp中国区总裁孙振耀职场圣经

    2021年5月4日
  • 3042b什么意思_XC3042

    3042b什么意思_XC3042Loj #3042. 「ZJOI2019」麻将

    2022年4月22日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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