mybatis拦截器源码分析

news2024/11/16 3:48:14

mybatis拦截器源码分析

拦截器简介

mybatis Plugins 拦截器

由于Mybatis对数据库访问与操作进行了深度的封装,让我们应用开发效率大大提高,但是灵活度很差

拦截器的作用:深度定制Mybatis的开发

抛出一个需求 :获取Mybatis在开发过程中执行的SQL语句(执行什么操作获取那条SQL语句)

​ 在JDBC中我们的sql都会直接定义出来,所以实现上面这个需求很简单.但是在Mybatis中由于深度封装导致不好进行灵活满足需求,所以Mybatis拦截器可以用来解决这一系列问题.

Mybatis拦截器作用

作用:通过拦截器拦截用户对DAO方法的调用,加入一些通用功能(等同于Spring中的AOP操作)


 client ------>  UserDAO.save ----->  处理功能
 				 mybatis拦截器

而我们通过之前的mybatis核心运行流程源码分析得知其实为我们执行增删改查操作的是SqlSession.而SqlSession是依赖Executor,StatementHandler,ParameterHandler,ResultHandler这些mybatis核心对象来进行操作的.

UserDAO.save()		--->	SqlSession.insert()				Executor
UserDAO.update()	--->	SqlSession.update()    =====>   StatementHandler
UserDAO.delete()	--->	SqlSession.delete()				ParameterHandler
UserDAO.findAll()	--->	SqlSession.select()				ResultHandler

所以我们应该拦截的是这些mybatis核心对象,准确说应该是这些对象的方法.而我们比较常用的是Executor,StatementHandler.因为增删改查操作是由StatementHandler,所以StatementHandler是最常用的

拦截器的基本开发

主要分俩步: 1.编码
			1.1 需要实现拦截器的接口(Interceptor)
			1.2 标注需要拦截的目标
		  2.配置

代码如下:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Properties;
//标注需要拦截的目标为Executor类中的query方法,因为query()方法有2个所以要将具体方法的参数也一起进行标注
@Intercepts({
        @Signature(type= Executor.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class MyMybatisInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor.class);

    @Override
    /**
     *  作用:执行的拦截功能 书写在这个方法中.
     *       放行
     */
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isDebugEnabled())
            log.debug("----拦截器中的 intercept 方法执行------  "+test);
        //执行完拦截功能继续往下执行
        return invocation.proceed();
    }

    /*
     *  把这个拦截器目标 传递给 下一个拦截器(可能存在多个拦截器)
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    /*
     *  获取拦截器相关参数的
     */
    @Override
    public void setProperties(Properties properties) {
      
    }
}

然后在将拦截器的配置写在mybatis-config.xml文件中即可(后续代码不在赘述)

<plugins>
       <plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor"></plugin>
    </plugins>

以上代码可以实现在查询操作中,执行我们的打印日志,我们如果需要在更新操作的时候进行拦截只需要在标注拦截注解上在添加一个即可

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

如果有多个需要拦截的方法,像上面这样一个个标注太过繁琐,我们可以通过query()和update()都会执行的一个方法进行拦截.这里我们拦截StatementHandler中的prepare()方法,然后将每次需要实现Interceptor中的plugin()方法通过装饰器来进行优化

拦截StatementHandler中的prepare()方法

​ 1.query()和update()都需要prepare()

​ 2.获取Connection等同于JDBC

首先定义装饰器类MyMybatisInterceptorAdapter

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Plugin;

//这里定义一个抽象类只实现Interceptor中的plugin()方法,后面只需要继承这个抽象类即可实现拦截
public abstract class MyMybatisInterceptorAdapter implements Interceptor {
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

}
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
//这样即可省略plugin()方法的实现
public class MyMybatisInterceptor2 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
         if (log.isDebugEnabled())
            log.debug("----拦截器中的 intercept 方法执行------  "+test);
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

然后继续把问题回到之前,如何获取sql语句.通过我们debug上面的代码可以发现在我们实现的intercept()方法参数中就有目标类的信息,而我们之前通过mybatis核心流程源码分析得知boundSql对象中就有一个String类型的变量来存放sql

在这里插入图片描述


import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyMybatisInterceptor2 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        
        //RoutingStatementHandler---delegate---
//        RoutingStatementHandler satementHandler = (RoutingStatementHandler) invocation.getTarget();
//        BoundSql boundSql = satementHandler.getBoundSql();
//        String sql = boundSql.getSql();

        //为什么会衍生出第二种写法?因为我们通过debug不一定知道这些对象属性是否私有化,也就是说不一定有get方法,所以通过mybatis底层为我们提供的反射对象MetaObject可以获取到对象的属性值
        MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        if (log.isDebugEnabled())
            log.debug("sql : " + sql);

        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

以上的俩种写法都可以实现在执行数据库操作中打印出sql语句

拦截器中获取拦截对象方法相关参数

通过intercept()方法中的Invocation对象,我们进入源码看看

在这里插入图片描述

通过Invocation对象中的args属性即可获取拦截方法的相关参数

详解MeataObject

metaObject —> Mybatis底层封装的反射工具类,便于Mybatis中通过反射操作对象属性

注意:只用引入myabtis依赖才可以使用

		//通过SystemMetaObject.forObject()去获取MetaObject对象,它的参数是你需要获取哪个对象的属性就将对象作为参数传入.我们这里是获取的invocation中的属性,所以传入invocation对象.这样即使对象没有提供get方法我们也能获取到属性值
		MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");

mybatis拦截器如何解决开发中的实际问题

在mybatis开发中,可能要对SQL语句进行处理时会使用到拦截器,如分页插件,乐观锁,基于表字段实现的多租户以及逻辑删除等.

分页功能

对于以往的mybatis分页功能,主要采用传参当前页和每页数通过SQL语句中的limit关键字来对数据进行分页,而这种操作会导致代码冗余并且及其不容易维护.通过Mybatis拦截器功能可以拦截要执行的SQL语句进行拼接limit来实现分页这一功能.

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageHelperInterceptor1 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        //拼接后续的分页查询(这里的0,3只是举个例子实际肯定还是使用当前页变量和每页条数变量)
        String newSql = sql + " limit 0,3";
        //将新的sql语句set进我们的invocation对象中
		metaObject.setValue("target.delegate.boundSql.sql", newSql);
        if (log.isDebugEnabled())
            log.debug("sql : " + sql);
		//然后执行后续操作
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

​ 这样就可以简单实现一个分页功能,但是也是存在一些问题的.因为我们这里拦截的方法是StatmentHandler对象中的prepare()方法,增删改查都会去进行拦截.这样就导致了我们进行除了查询的其他操作时,执行的sql语句也会将分页查询给拼接上这样就会导致sql语句语法报错无法执行正常的操作.

这里开始优化判断查询SQL语句,如下:

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageHelperInterceptor1 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyMybatisInterceptor2.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MetaObject metaObject = SystemMetaObject.forObject(invocation);
       String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("target.delegate.mappedStatement"); 
        String id = mappedStatement.getId();
        //判断当前方法是否是查询方法并且方法末尾为ByPage
        if (id.indexOf("query") != -1 && id.endsWith("ByPage")) {
             String newSql = sql + " limit 0,3 ";
           	 metaObject.setValue("target.delegate.boundSql.sql", newSql);
        }
		//然后执行后续操作
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

这里可能在判断直接使用字符串不太合理,具体判断应该由用户来进行自定义设置.

 if (id.indexOf("query") != -1 && id.endsWith("ByPage")) {
 }

我们通过setProperties()这个方法,将具体参数配置到mybatis-config.xml中支持用户自定义字符串

<plugin interceptor="com.baizhiedu.plugins.PageHelperInterceptor1">
            <property name="queryMethodPrefix" value="query"/>
            <property name="queryMethodSuffix" value="ByPage"/>
        </plugin>

然后将配置文件的值set进我们定义的成员变量中

//这里省略具体实现方法
 . . .
     
    private String queryMethodPrefix;

    private String queryMethodSuffix;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MetaObject metaObject = SystemMetaObject.forObject(invocation);

        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("target.delegate.mappedStatement");
        String id = mappedStatement.getId();
		//通过变量来进行判断,让功能实现变得更加灵活
        if (id.indexOf(queryMethodPrefix) != -1 && id.endsWith(queryMethodSuffix)) {
                String newSql = sql + " limit "+page.getFirstItem()+","+page.getPageCount();

            metaObject.setValue("target.delegate.boundSql.sql", newSql);
        }
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        //将配置文件中属性值赋值到成员变量中
        this.queryMethodPrefix = properties.getProperty("queryMethodPrefix");
        this.queryMethodSuffix = properties.getProperty("queryMethodSuffix");
    }
}

下面还是对之前的代码进行优化,主要按实际开发流程为主.封装Page对象模拟前端传入参数

public class Page {
    //当前页
    private Integer pageIndex;
    //每页条数
    private Integer pageCount;
    //总条数
    private Integer totalSize;
    //总页数
    private Integer pageSize;

    public Page(Integer pageIndex) {
        this.pageIndex = pageIndex;
        this.pageCount = 5;
    }

    public Page(Integer pageIndex, Integer pageCount) {
        this.pageIndex = pageIndex;
        this.pageCount = pageCount;
    }

    public Integer getPageIndex() {
        return pageIndex;
    }

    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }

    public Integer getPageCount() {
        return pageCount;
    }

    public void setPageCount(Integer pageCount) {
        this.pageCount = pageCount;
    }

    public Integer getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(Integer totalSize) {
        this.totalSize = totalSize;
        if (totalSize % pageCount == 0) {
            this.pageSize = totalSize / pageCount;
        } else {
            this.pageSize = totalSize / pageCount + 1;
        }
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    // limit getFirstItem,pageSize;
    public Integer getFirstItem() {
        return pageIndex - 1;
    }
}

然后通过page对象来进行对之前分页拦截器的优化

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageHelperInterceptor1 extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(PageHelperInterceptor1.class);

    private String queryMethodPrefix;

    private String queryMethodSuffix;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isInfoEnabled())
            log.info("----pageHelperInterceptor------");
        //获得sql语句 拼接字符串 limit
        MetaObject metaObject = SystemMetaObject.forObject(invocation);

        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("target.delegate.mappedStatement");
        String id = mappedStatement.getId();

        if (id.indexOf(queryMethodPrefix) != -1 && id.endsWith(queryMethodSuffix)) {
            //分页相关的操作封装 对象(vo dto)
            //获得Page对象 并设置Page对象 totalSize属性 算出总页数

            //假设 Page
            Page page = new Page(1);

            //select id,name from t_user 获得 全表有多少条数据
            // select count(*) from t_user

            //select id,name from t_user where name = ?;
            //select count(*)fromt t_user where name = ?

            //select id,name from t_user where  name = ? and id = ?;

            String countSql = "select count(*) " + sql.substring(sql.indexOf("from"));
            //JDBC操作
            //1 Connection  PreapredStatement
            Connection conn = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = conn.prepareStatement(countSql);

           /* preparedStatement.setString(1,?)
            preparedStatement.setString(2,?);*/
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("target.delegate.parameterHandler");
            parameterHandler.setParameters(preparedStatement);

            ResultSet resultSet = preparedStatement.executeQuery();
            if(resultSet.next()){
               page.setTotalSize(resultSet.getInt(1));
            }
                String newSql = sql + " limit "+page.getFirstItem()+","+page.getPageCount();

            metaObject.setValue("target.delegate.boundSql.sql", newSql);
        }
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        this.queryMethodPrefix = properties.getProperty("queryMethodPrefix");
        this.queryMethodSuffix = properties.getProperty("queryMethodSuffix");
    }
}

可以发现这里通过page对象模拟前端请求的DTO参数,通过截取字符串通过拦截器参数使用JDBC查询数据总条数,然后再拼接sql完成分页查询.

继续分析,这里我们是模拟前端new了一个Page对象来作为参数.那么真实的情况应该是这样

在这里插入图片描述

可以看到Page对象从Controller传递到Service最后到Dao,在这个过程中我们在拦截器并无法获取到Page对象.

解决方案:

​ 将Page对象作为dao方法的参数进行传递,那么在拦截器中可以通过invocation参数获取其中的parameterHandler拿到对应的Page对象.

在这里插入图片描述

//直接通过DAO方法的参数 获得Page对象
 Page page = (Page) metaObject.getValue("target.delegate.parameterHandler.parameterObject");

​ 通过将Page对象存入本地线程中,可以保证线程安全性,也可以进行多个线程的并发处理.通过请求过滤器将Page对象参数通过本地线程set进去,然后再拦截器处理时get即可.

import com.baizhiedu.util.Page;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class PageFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String pageIndexString = req.getParameter("pageIndex");
        int pageIndex = Integer.parseInt(pageIndexString);
        Page page = new Page(pageIndex);

        //Tl.set(page)

        chain.doFilter(request,response);//--- DispatcherServlet ---- Controller --- Service ----DAO
    }

    @Override
    public void destroy() {

    }
}

创建本地线程工具类进行线程中Page对象的管理,提供get和set方法进行操作

public class ThreadLocalUtils {
    private static final ThreadLocal<Page> tl = new ThreadLocal<>();

    public static void set(Page page) {
        tl.set(page);
    }

    public static Page get() {
        return tl.get();
    }
}

将之前通过invocation参数获取的代码改为

Page page = ThreadLocalUtils.get();

最后,记得处理完分页后将线程中的Page对象remove避免出现不需要分页的查询也进行了查询操作影响数据当然最终分页插件没有做到其他数据库的匹配可以通过不同类型的数据库对sql语句拼接limit那块代码进行优化,这里就不在赘述.至此mybatis分页插件完结

sql动态处理工具

使用场景: 当我们准备使用Mybatis拦截器对sql语句进行处理时,可以通过jsqlparser这个工具进行处理.

这里为了简单,就不结合拦截器来进行编码直接使用测试类来展示jsqlparser的使用方法和功能

首先引入jsqlparser依赖

		<dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>3.1</version>
        </dependency>

查询语句的处理

  @Test
    public void testSQLParser() throws JSQLParserException {
        //前俩行代码属于固定语法
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        //这里只需要把需要进行处理的sql语句字符串作为参数传入new StringReader()中,注意返回类型不要写错查询就返回Selcet对象(Select对象是jsqlparser依赖中的!)
        Select select = (Select) parserManager.parse(new StringReader("select id,name from t_user where name = 'suns' "));
		//下面分别获取了sql语句的表名,where条件和需要查询的字段名
        PlainSelect selectBody = (PlainSelect) select.getSelectBody();

        //FromItem table = selectBody.getFromItem();
        //System.out.println("table = " + table);

     /*   Expression where = selectBody.getWhere();
        System.out.println("where = " + where);*/

       /* List<SelectItem> selectItems = selectBody.getSelectItems();
        for (SelectItem selectItem : selectItems) {
            System.out.println("selectItem = " + selectItem);
        }*/
    }

修改语句的处理

@Test
    public void testSQLParser1() throws JSQLParserException {
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Update update = (Update) parserManager.parse(new StringReader("update t_user set name='suns',password='12345' where id=1 "));

        /*Table table = update.getTable();
        System.out.println("table = " + table);*/

        //这里是获取需要修改的字段(注意这里获取不了修改的值)
        List<Column> columns = update.getColumns();
        for (Column column : columns) {
            System.out.println(column);
        }
		//这里可以获取到修改到的值,这俩个内容是分开进行获取的
        List<Expression> expressions = update.getExpressions();
        for (Expression expression : expressions) {
            System.out.println(expression);
        }
    }

可以通过与Mybatis拦截器相结合进行Sql语句的动态处理

乐观锁

场景: 当多个请求(线程)并发(同一时间)访问了数据库中的相同的数据,如何保证数据安全.

悲观锁: 数据库底层提供的锁,引入悲观锁保证数据并发访问的安全.将一个并行的操作串行化,等待第一个操作完数据后第二基于第一个操作的结果进行操作,只要执行了增删改操作数据库就会为数据添加悲观锁 也称为行锁.

乐观锁: 应用锁 不涉及到数据库底层真的为数据加锁,并发效率高,安全性低.

实现原理: 版本号的比对(每一次 更新数据的时候
先要 进行版本的比对
如果版本一致 则说明没有其他事物对数据进行操作
如果版本不一致 则说明有些其他事物操作了数据 产生了并发)

如何封装乐观锁

1.保证version列初始值为0,插入操作时 sql insert vers = 0

2.每次更新的过程中与表中vers对比,获取对象version属性值,查询数据库当前这条数据的vers的值

3.如何值一致,进行更新操作并且vers+1

4.如果值不一致,抛出乐观锁异常

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class LockInterceptor extends MyMybatisInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(LockInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isInfoEnabled())
            log.info("----LockInterceptor------");

        MetaObject metaObject = SystemMetaObject.forObject(invocation);
		//获取sql语句
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        MappedStatement mappedStatement = (MappedStatement) 		metaObject.getValue("target.delegate.mappedStatement");
        //获取dao方法名如 save,selectOne等
        String id = mappedStatement.getId();

        /*
            在用户进行插入操作时,需要由拦截器 设置vers值0
            🤔 用户书写的Sql语句:insert into t_user (name) values (#{name});
               封装需要干的事    insert into t_user (name,vers) values (#{name},0)

               问题:如何获得 用户书写SQL ?
               解答:String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");

               问题:如何修改sql语句 为其添加vers 值0 ?
               解决:涉及到对原有sql语句操作,JsqlParser

         */
        //如果是插入操作
        if (id.indexOf("save") != -1) {
            //解析sql语句
            CCJSqlParserManager parserManager = new CCJSqlParserManager();
            Insert insert = (Insert) parserManager.parse(new StringReader(sql));
            //插入的列 vers  匹配对应的值 0
            //列名字 Columns
            List<Column> columns = insert.getColumns();
            columns.add(new Column("vers"));

            //列的值
            ExpressionList itemsList = (ExpressionList) insert.getItemsList();
            List<Expression> expressions = itemsList.getExpressions();
            expressions.add(new LongValue(0));
            insert.setSetExpressionList(expressions);

            //修改完成sql语句后 新的sql语句 交给Mybatis ---> 继续进行?替换
            metaObject.setValue("target.delegate.boundSql.sql", insert.toString());


        }

         /*
             update t_user set name =?,vers = vers+1 where id = ?
             如果进行update操作:
                1. 在提交update操作时,需要对比此时 对象中的version里面存储的值与数据库中vers字段中的值是否相等
                 1.1 如果不等
                       说明已经有其他用户进行了更新 (存在并发) 抛出异常
                 1.2 如果相等
                       可以进行更新操作,并把对应的vers+1

          */

        if (id.indexOf("update") != -1) {

            CCJSqlParserManager parserManager = new CCJSqlParserManager();
            Update update = (Update) parserManager.parse(new StringReader(sql));
            Table table = update.getTable();
            String tableName = table.getName();

            //id值 一定是更新操作中 User id属性存储

            Integer objectId = (Integer) metaObject.getValue("target.delegate.parameterHandler.parameterObject.id");
            Integer version = (Integer) metaObject.getValue("target.delegate.parameterHandler.parameterObject.version");

            Connection conn = (Connection) invocation.getArgs()[0];
            String selectSql = "select vers from " + tableName + " where id = ?";
            PreparedStatement preparedStatement = conn.prepareStatement(selectSql);
            preparedStatement.setInt(1, objectId);
            ResultSet resultSet = preparedStatement.executeQuery();
            int vers = 0;
            if (resultSet.next()) {
                vers = resultSet.getInt(1);
            }

            System.out.println();

            if (version.intValue() != vers) {
                throw new RuntimeException("版本不一致");
            } else {
                //vers+1
                //正常进行数据库更新
                List<Column> columns = update.getColumns();
                columns.add(new Column("vers"));

                List<Expression> expressions = update.getExpressions();
                expressions.add(new LongValue(vers + 1));
                update.setExpressions(expressions);

                metaObject.setValue("target.delegate.boundSql.sql", update.toString());
            }


        }
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

至此,mybatis拦截器完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1084852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RK3562开发板:升级摄像头ISP,突破视觉体验边界

RK3562开发板作为深圳触觉智能新推出的爆款产品&#xff0c;采用 Rockchip 新一代 64 位处理器 RK3562&#xff08;Quad-core ARM Cortex-A53&#xff0c;主频最高 2.0GHz&#xff09;&#xff0c;最大支持 8GB 内存&#xff1b;内置独立的 NPU&#xff0c;可用于轻量级人工智能…

谷歌浏览查询http被自动转化成https导致页面读取失败问题处理

原因&#xff1a; 谷歌浏览器版本升级&#xff0c;安全问题考虑自动转化https 解决方案&#xff1a; 一、打开配置页面&#xff1a; chrome://flags/ 二、禁止自动转化

vue3_setup基础_渲染函数(ref,reactive)

一、setup语法糖 是什么&#xff1a;组合式Api &#xff08;vue2为option Api&#xff09; 来解决什么问题&#xff1a;使用&#xff08;data,computed,methonds,watch&#xff09;组件选项来组织逻辑通常都很有效。然而&#xff0c;当我们组件变的更大的时候&#xff0c;逻辑…

ansible的介绍安装与模块

目录 一、ansible简介 二、ansible特点 三、Ansible核心组件与工作原理 1、核心组件 2、工作原理 四、ansible的安装 五、ansible 命令行模块 1&#xff0e;command 模块 2&#xff0e;shell 模块 3&#xff0e;cron 模块 4&#xff0e;user 模块 5&#xff0e;group 模…

01 时钟配置初始化,debug

1. 开启debug series&#xff0c;否则只能下载一次&#xff0c;再次下载要配置boot 2.f0外部时钟配置 h750 配置 实测可用

股票印花税如何征收,万一免五的低费率成本计算以及券商选择

印花税国家收的&#xff0c;不管是深市沪市都收&#xff0c;如下图所示&#xff0c;可以看到&#xff0c;证券交易印花税自2008年9月之后改为单向收取&#xff0c;今年8月份更是降到了0.05%&#xff0c;也就是万分之5&#xff0c;以现在的视角看历史&#xff0c;在最早90年的千…

【数据结构】算法的空间复杂度

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 算法空间复杂度的定义 算法的时间复杂度和空间复杂度是度量算法好坏的两个重要量度,在实际写代码的过程中,我们完全可以用空间来换时间,比如说,我们要判断某某年是不是闰年,大…

基于Vue构建的快速开发框架

一、Vue结合低代码 "低代码"是一种快速开发应用的方法&#xff0c;它使开发者能够通过图形界面和预构建的块进行设计和构建&#xff0c;而不是手动编写大量的代码。这种方法被广泛用于快速应用开发、移动应用开发、业务流程管理和数据库应用开发等领域。 Vue.js 是一…

【Proteus仿真】【51单片机】智能语音家居陪护机器人

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用OLED液晶、按键、蜂鸣器、DS18B20温度传感器、人体红外传感器、语音识别模块、继电器、风扇、LED等。 主要功能&#xff1a; 系统运行后&#xff0…

Kafka生产者使用案例

1.生产者发送消息的过程 首先介绍一下 Kafka 生产者发送消息的过程&#xff1a; 1)Kafka 会将发送消息包装为 ProducerRecord 对象&#xff0c; ProducerRecord 对象包含了目标主题和要发送的内容&#xff0c;同时还可以指定键和分区。在发送 ProducerRecord 对象前&#xff0c…

FPGA面试题(7)

一.解释一下SPI的四种模式 01时钟极性CPOL空闲状态为低电平空闲状态为高电平时钟相位CPHA在第一个跳变沿采样在第二个跳变沿采样 模式CPOLCPHA描述模式000sclk上升沿采样&#xff0c;sclk下降沿发送模式101sclk上升沿发送&#xff0c;sclk下降沿采样模式210sclk上升沿发送&…

解决nav2_bringup tb3_simulation_launch.py 无法启动Gazebo的问题

方法 1 断网再开gazebo. 评价: 方便且有效, 但来回联网很麻烦 参考: https://blog.csdn.net/James___H/article/details/116906217 方法 2 断网能打开是因为gazebo软件开启时会自动从网络下载模型&#xff0c;下载过程必然漫长, 另外你懂的, 网络问题嘛, vpn也解决不了的话…

jmeter压测记录、使用方法

jmeter压测记录、使用方法 1、非gui方式执行压测命令2、压测命令输出解读 1、非gui方式执行压测命令 sh jmeter.sh -n -t test.jmx -l result.jtl2、压测命令输出解读 Active: 10 Started: 10 Finished: 0 Active: 10 表示一共10个活动&#xff08;正在进行的压测线程&#…

基于Cl2/BCl3电感偶联等离子体的氮化镓干蚀特性

引言 氮化镓(GaN)具有六方纤锌矿结构&#xff0c;直接带隙约为3.4eV&#xff0c;目前已成为实现蓝光发光二极管(led)的主导材料。由于GaN的高化学稳定性&#xff0c;在室温下用湿法化学蚀刻来蚀刻或图案化GaN是非常困难的。与湿法蚀刻技术相比&#xff0c;干法蚀刻技术可以提供…

前端axios下载导出文件工具封装

使用示例&#xff1a; import { fileDownload } from /utils/fileDownloadfileDownload({ url: process.env.VUE_APP_BASE_URL /statistic/pageList/export, method: post, data: data })工具类&#xff1a; import store from ../store/index import {getAccessToken } fro…

【Lombok的Bug记录】前端传的有值,但是到后端就全为空了

项目场景&#xff1a; 项目背景&#xff1a;使用Data注解标注类 问题描述 前端传的有值&#xff0c;但是到后端就全为空了 原因分析&#xff1a; AName和aName生成的set方法名是一样的&#xff0c;所以换名字就行了&#xff01; 解决方案&#xff1a; 属性不要写成xXxx的形式…

为什么手机会莫名多出许多软件?

许多手机用户都曾遭遇过这样的问题&#xff0c;他们在使用手机的过程中&#xff0c;突然发现手机屏幕上出现了一些未知的软件。这些软件并非他们主动下载的&#xff0c;但它们却显现在屏幕上。这些软件从何而来&#xff1f; 其实&#xff0c;这些软件往往是在浏览网页、阅读小…

spring 注入 当有两个参数的时候 接上面

新加一个int 型的 age 记得写getset方法和构造方法 &#xff08;&#xff08;&#xff08;&#xff08;&#xff08;&#xff08;&#xff08; 构造方法的作用——无论是有参构造还是无参构造&#xff0c;他的作用都是为了方便为对象的属性初始化值 构造方法是一种特殊的方…

UnrealEngine iOS 打包 —— 签名证书(cer、p12)生成

官方文档 docs.unrealengine.com/5.3/zh-CN/setting-up-ios-tvos-and-ipados-provisioning-profiles-and-signing-certificates-for-unreal-engine-projects 打开 ProjectSettings -> Platforms -> iOS 可以看到签名证书配置 需要拓展名为 .cer 和 .p12 的一对证书和密钥…

虹科方案 | AR助力仓储物流突破困境:规模化运营与成本节约

文章来源&#xff1a;虹科数字化AR 点击阅读原文&#xff1a;https://mp.weixin.qq.com/s/xis_I5orLb6RjgSokEhEOA 虹科方案一览 HongKe DigitalizationAR 当今的客户体验要求企业在人员、流程和产品之间实现全面的连接。为了提升整个组织的效率并提高盈利能力&#xff0c;物流…