mybatis拦截器源码分析

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();}/**  把这个拦截器目标 传递给 下一个拦截器(可能存在多个拦截器)*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target,this);}/**  获取拦截器相关参数的*/@Overridepublic 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 {@Overridepublic 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);@Overridepublic Object intercept(Invocation invocation) throws Throwable {if (log.isDebugEnabled())log.debug("----拦截器中的 intercept 方法执行------  "+test);return invocation.proceed();}@Overridepublic 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);@Overridepublic 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();}@Overridepublic 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);@Overridepublic 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();}@Overridepublic 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);@Overridepublic 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();//判断当前方法是否是查询方法并且方法末尾为ByPageif (id.indexOf("query") != -1 && id.endsWith("ByPage")) {String newSql = sql + " limit 0,3 ";metaObject.setValue("target.delegate.boundSql.sql", newSql);}//然后执行后续操作return invocation.proceed();}@Overridepublic 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;@Overridepublic 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();}@Overridepublic 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;@Overridepublic Object intercept(Invocation invocation) throws Throwable {if (log.isInfoEnabled())log.info("----pageHelperInterceptor------");//获得sql语句 拼接字符串 limitMetaObject 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属性 算出总页数//假设 PagePage 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  PreapredStatementConnection 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();}@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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}@Overridepublic 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>

查询语句的处理

  @Testpublic 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);}*/}

修改语句的处理

@Testpublic 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);@Overridepublic 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//列名字 ColumnsList<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();}@Overridepublic void setProperties(Properties properties) {}
}

至此,mybatis拦截器完结

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

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

相关文章

ChatGPT,AIGC 制作按年份选择的动态条形图

在数据分析与可视化中,条形图是用来进行对比分析,在正负条形图中都有哪些好处与优点呢? 正负条形图是一种常用的数据可视化方式,它的优点和好处包括: 1.明确展示:正负条形图可以清晰地显示出数据中的正负差异,使读者能直观地看出数据的变化和趋势。 2.直观比较:正负条…

苹果电脑其他内存怎么清理?

苹果电脑中的应用程序大部分是可以通过将其拖拽至废纸篓并倾倒来卸载的。但是部分程序在卸载后仍有残留文件&#xff0c;比如support文件和pref设置等文件的。小编今天介绍下苹果电脑清理内存怎么清理卸载残留以及好用的清理技巧分享。 一、苹果电脑清理内存怎么清理 苹果电脑…

简单实现一个todoList(上移、下移、置顶、置底)

演示 html部分 <!DOCTYPE html> <html> <head><title>表格示例</title> </head> <body><table border"1"><thead><tr><th>更新时间</th><th>操作</th></tr></thead…

thinkphp5.1 获取缓存cache(‘cache_name‘)特别慢,php 7.0 unserialize 特别慢

thinkphp5.1 获取缓存cache(‘cache_name’)特别慢&#xff0c;php 7.0 unserialize 特别慢 场景&#xff1a; 项目中大量使用了缓存&#xff0c;本地运行非常快&#xff0c;二三百毫秒&#xff0c;部署到服务器后 一个表格请求就七八秒&#xff0c;最初猜想是数据库查询慢&am…

KY258 日期累加

KY258 日期累加 int main() {int n 0; //样例个数cin >> n;//for循环处理n个样例for (int i 0; i < n; i){int y, m, d, num;int days[12] { 31,28,31,30,31,30,31,31,30,31,30,31 };//输入年月日 要加的天数cin >> y >> m >> d >>…

完整教程:Java+Vue+Websocket实现OSS文件上传进度条功能

引言 文件上传是Web应用开发中常见的需求之一&#xff0c;而实时显示文件上传的进度条可以提升用户体验。本教程将介绍如何使用Java后端和Vue前端实现文件上传进度条功能&#xff0c;借助阿里云的OSS服务进行文件上传。 技术栈 后端&#xff1a;Java、Spring Boot 、WebSock…

【Java学习之道】GUI开发的基本概念

引言 在这一章&#xff0c;我们将一起走进Java的图形用户界面&#xff08;GUI&#xff09;开发的世界。在你阅读完这篇文章后&#xff0c;你将能够了解什么是GUI&#xff0c;以及如何使用Java进行GUI的开发。 一、什么是GUI 首先&#xff0c;让我们来解答一个许多初学者都会…

AN基础工具——变形工具

【AN基础工具——变形工具】 基本使用方法&#xff1a;任意变形工具基础动画 本篇内容&#xff1a;学会使用变形工具 重点内容&#xff1a;变形工具 工 具&#xff1a;Adobe Animate 2022 基本使用方法&#xff1a; 任意变形工具 《任意变形工具&#xff08;快捷键Q&#xff0…

深度学习系列51:hugging face加速库optimum

1. 普通模型 Optimum是huggingface transformers库的一个扩展包&#xff0c;用来提升模型在指定硬件上的训练和推理性能。Optimum支持多种硬件&#xff0c;不同硬件下的安卓方式如下&#xff1a; 如果是国内安装的话&#xff0c;记得加上-i https://pypi.tuna.tsinghua.edu.c…

八大排序算法(含时间复杂度、空间复杂度、算法稳定性)

文章目录 八大排序算法(含时间复杂度、空间复杂度、算法稳定性)1、&#xff08;直接&#xff09;插入排序1.1、算法思想1.2、排序过程图解1.3、排序代码 2、希尔排序3、冒泡排序3.1、算法思想3.2、排序过程图解3.3、排序代码 4、&#xff08;简单&#xff09;选择排序4.1、算法…

Springcloud笔记(2)-Eureka服务注册中心

Eureka服务注册 Eureka作为一个微服务的治理中心&#xff0c;它是一个服务应用&#xff0c;可以接收其他服务的注册&#xff0c;也可以发现和治理服务实例。 服务治理中心是微服务&#xff08;分布式&#xff09;架构中最基础和最核心的功能组件&#xff0c;它主要对各个服务…

kafka生产者发送消息报错 Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected

报这个错误是因为kafka里的配置要修改下 在config目录下 server.properties配置文件 这下发送消息就不会一直等待&#xff0c;就可以发送成功了

CTFHub SSRF 题目

文章目录 CTFHub SSRF 通关教程1. 内网访问&#xff0c;伪协议利用1.1 内网访问1.2 伪协议读取文件1.3 端口扫描 2. POST请求&#xff0c;上传文件&#xff0c;FastCGI协议&#xff0c;Redis协议2.1 POST请求2.2 上传文件2.3 FastCGI协议2.4 Redis协议 3. Bypass系列3.1 URL By…

Macos数字音乐库:Elsten Software Bliss for Mac

Elsten Software Bliss for Mac是一款优秀的音乐管理软件&#xff0c;它可以帮助用户自动化整理和标记数字音乐库&#xff0c;同时可以自动识别音乐信息并添加标签和元数据。 此外&#xff0c;Bliss还可以修复音乐库中的问题&#xff0c;例如重复的音乐文件和缺失的专辑封面等…

Apache Dubbo 首个 Node.js 3.0-alpha 版本正式发布

作者&#xff1a;蔡建怿 关于Apache Dubbo3 Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架&#xff0c;同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。经过近几年发展&#xff0c;Dubbo3 已在阿里巴巴集团各条业务线实现全面…

关于如何进行ChatGPT模型微调的新手指南

微调是指在预训练的模型基础上&#xff0c;通过进一步的训练来调整模型以适应特定任务或领域。预训练的模型在大规模的文本数据上进行了广泛的学习&#xff0c;从中获得了一定的知识和语言理解能力。然而&#xff0c;由于预训练并不针对具体任务&#xff0c;因此需要微调来使模…

深耕全面预算管理 拥抱企业数字未来

随着世界数字未来的不断发展&#xff0c;我国也正经历着一场更大范围、更深层次的科技变革。企业面对构建内部生态平衡体系的艰巨任务&#xff0c;对于其信息化部署也提出了更高的要求。增强预算编制的全面性&#xff0c;启动预算管理一体化改革成为了我国企业提高数字化水平的…

实验3:左右循环LED灯

获取流水灯工程&#xff1a; 方式一&#xff1a; keilproteus 完成最小系统&#xff0c;点亮led 灯实验_吴小凹的博客-CSDN博客 方式二&#xff1a; Flowing_led.zip - 蓝奏云直接下载。 原理图修改&#xff1a; 无须修改只需要使用流水灯的工程即可&#xff0c;解压到桌面…

SQL sever中的索引

目录 一、索引定义 二、索引结构 2.1. B-树索引结构&#xff1a; 2.2. 哈希索引结构&#xff1a; 三、索引作用 四、索引与约束区别 五、索引级别 六、索引分类 6.1. 聚集索引&#xff08;Clustered Index&#xff09;&#xff1a; 6.2. 非聚集索引&#xff08;Noncl…

2023年09月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 Python编程&#xff08;1~6级&#xff09;全部真题・点这里 第1题&#xff1a;生日相同 在一个有180人的大班级中&#xff0c;存在两个人生日相同的概率非常大&#xff0c;现给出每个学生的名字&#xff0c;出生月日。试…