派趣吧

如何规避MyBatis使用过程中带来的全表更新风险

电视时间:刚刚阅读:1

做者:京东零售 贾玉西

一、媒介

法式员A: MyBatis用过吧?

法式员B: 用过

法式员A: 好巧,我也用过,那你碰着过什么风险没?好比全表数据被更新或者删除了。

法式员B: 咔,还没碰着过,那种情状需要跑路吗?

法式员A: 哈哈,不至于。但利用过程中,因为营业数据校验不妥,确实可能会形成全表更新或者删除。

法式员B: 喔,吓死我了,我们都是好人,不会做删库跑路类似蠢事,能展开讲讲那个风险如何形成的吗?

法式员A: 好的,你能看出下面那段代码会有风险吗?

法式员B: 日常平凡各人都如许写的,也没看出啥风险呀!

法式员A: 假设DAO层没做非空校验,relationId字段传进为空,那段代码组拆出来的是什么语句?

法式员B: update cms_relation_area_code set yn = 1 where yn = 0 我擦,全表被逻辑删除了!哥哥,我们的web利用数量多,代码行数几十万行,你怎么处置的呀,不会人力梳理代码吧?得累死......

法式员A: 昂,能够的,基于MyBatis的扩展点能够实现一款插件做到降低全表更新的风险,降低人工成本。

法式员B: 哥哥,要不讲讲MyBatis和实现的插件?

法式员A: 那必需嘞,手艺是需要分享和互补的。

不知各人在利用MyBatis有没有过法式员A哥哥碰着的事务?好巧,本人也履历过跟法式员A小哥哥一样的境遇,初始构想也是人工梳理代码,后来经由架构师点拨能不克不及开发一款SDK同一处置,要否则就扛着身体往梳理那几十万行代码了。要纷歧起聊聊那块,配合生长~

一路先看下MyBatis原理吧?当然那部门比力枯燥,本篇文章也不会大废篇幅往介绍那块,简单给各人聊下根本流程,对MyBatis原理不感兴致的同窗能够间接跳到第三章往后看。

那... 第二章我就简单起头淡笔介绍MyBatis了,在座列位老友没啥定见吧,想更深进领会进修,能够读下源码,或者阅读下京东架构-小傅哥手撸MyBatis专栏博客(地址:bugstack.cn)

二、MyBatis 原理

先来看下MyBatis施行的归纳综合施行流程,就不逐渐贴源码了,工具其实多...

//1.加载设置装备摆设文件

InputStream inputStream =Resources.getResourceAsStream(“mybatis-config.xml”);

//2.创建 SqlSessionFactory 对象(现实创建的是 DefaultSqlSessionFactory 对象)

SqlSessionFactory builder =newSqlSessionFactoryBuilder().build(inputStream);

//3.创建 SqlSession 对象(现实创建的是 DefaultSqlSession 对象)

SqlSession sqlSession = builder.openSession();

//4.创建代办署理对象

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

//5.施行查询语句

ListUser users = mapper.selectUserList();

//释放资本

sqlSession.close();

inputStream.close();

mybatis整个施行流程,能够笼统为上面5步核心流程,咱们那里只讲解XML开发的体例,注解的体例根本核心思惟一致:

第一步:读取mybatis-config.xml设置装备摆设文件。转化为流,那一步没有需要细说的。

第二步:创建SqlSessionFactory 对象。 现实创建的是DefaultSqlSessionFactory对象,那里SqlSessionFactory和DefaultSqlSessionFactory的关系为:SqlSessionFactory是一个接口,DefaultSqlSessionFactory是该接口的一个实现,也是操纵了Java的多态特征。SqlSessionFactory是MyBatis中的一个重要的对象,汉译过来能够喊做:SQL会话工场,见名知意,它是用来创建SQL会话的一个工场类,它能够通过SqlSessionFactoryBuilder来获得,SqlSessionFactory是用来创建SqlSession对象的,SqlSession就是SQL会话工场所创建的SQL会话。而且SqlSessionFactory是线程平安的,它一旦被创建,应该在利用施行期间都存在,在利用运行期间(也就是Application感化域)不要反复创建屡次,定见利用单例形式。

第三步:创建 SqlSession 对象。 现实创建的是 DefaultSqlSession 对象,那里同上步,SqlSession为接口,DefaultSqlSession为SqlSession接口的一个实现类,SqlSession的次要感化是用来操做数据库的,它是MyBatis 核心 API,次要用来施行号令,获取映射,治理事务等。SqlSession固然供给select/insert/update/delete办法,在旧版本中利用利用SqlSession接口的那些办法,但是新版的Mybatis中就会定见利用Mapper接口的办法,也就是下面要讲到的第四步操做。SqlSession对象,该对象中包罗了施行SQL语句的所有办法,类似于JDBC里面的Connection。在JDBC中,Connection不间接施行SQL办法,而是生成Statement或者PrepareStatement对象,操纵Statement或者PrepareStatement来施行增删改查办法;在MyBatis中,SqlSession能够间接施行增删改查办法,能够通过供给的 selectOne、 insert等办法,也能够获取映射器Mapper来施行增删改查操做,通过映射器Mapper来施行增删改查如第四步代码所示。那里需要重视的是SqlSession 的实例不是线程平安的,因而是不克不及被共享的,所以它的更佳的感化域是恳求或办法感化域。绝对不克不及将 SqlSession 实例的引用放在一个类的静态域。

第四步:创建代办署理对象。 SqlSession一个重要的办法getMapper,望文生义,那个办法是用来获取Mapper映射器的。什么是MyBatis映射器?MyBatis框架包罗两品种型的XML文件,一类是设置装备摆设文件,即mybatis-config.xml,别的一类是操做DAO层的映射文件,例如UserInfoMapper.xml等等。在MyBatis的设置装备摆设文件mybatis-config.xml包罗了mappers/mappers标签节点,那里就是MyBatis映射器。也能够理解为mappers/mappers标签下设置装备摆设的各类DAO操做的mapper.xml的映射文件与DaoMapper接口的一种映射关系。映射器只是一个接口,而不是一个实现类。可能初学者可能会产生一个很大的疑问:接口不是不克不及运行吗?确实,接口不克不及间接运行,但是MyBatis内部运用了动态代办署理手艺,生成接口的实现类,从而完成接口的相关功用。所以在第四步那里 MyBatis 会为那个接口生成一个代办署理对象。

第五步:施行SQL操做以及释放毗连操做。

Emmm... 再补张图吧,刚刚的介绍觉得还没起头就完毕了,通过下面那张图我们再深进领会下MyBatis整体设想(此图借鉴京东架构-小傅哥手撸MyBatis专栏)

第一步:读取Mybatis设置装备摆设文件。

第二步:创建SqlSessionFactory对象。 上面已经对SqlSessionFactory做了阐明,但SqlSessionFactoryBuilder详细还没描述,SqlSessionFactoryBuilder是构造器,见名知意,它的次要感化即是构造SqlSessionFactory实例,根本流程为根据传进的数据流创建XMLConfigBuilder,生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。XMLConfigBuilder次要感化是解析mybatis-config.xml中的标签信息,如图中列举出的两个标签信息,解析情况信息及mapper.xml信息,解析mapper.xml时,Mybatis默认XML驱动类为XMLLanguageDriver,它的次要感化是解析select、update、insert、delete节点为完全的SQL语句,也是对应SQL的解析过程,XMLLanguageDriver在解析mapper.xml时,会将解析成果存储至SqlSource的实现类中,SqlSource是一个接口,只定义了一个 getBoundSql() 办法,它掌握着动态 SQL 语句解析的整个流程,它会根据从 Mapper.xml 映射文件解析到的 SQL 语句以及施行 SQL 时传进的实参,返回一条可施行的 SQL。它有三个重要的实现类,对应图中写到的RawSqlSource、DynamicSqlSource及StaticSqlSource,此中RawSqlSource处置的长短动态 SQL 语句,DynamicSqlSource处置的是动态 SQL 语句,StaticSqlSource是BoundSql中要存储SQL语句的一个载体,上面RawSqlSource、DynamicSqlSource的SQL语句,最末城市存储到StaticSqlSource实现类中。StaticSqlSource的 getBoundSql() 办法是实正创建 BoundSql 对象的处所, BoundSql 包罗领会析之后的 SQL 语句、字段、每个“#{}”占位符的属性信息、实参信息等。那里也重点介绍下Configuration对象,Configuration 的创建会拆载一些根本属性,如事务,数据源,缓存,代办署理,类型处置器等,从那里能够看出 Configuration 也是一个大的容器,来为后面的SQL语句解析和初始化供给保障,也是Mybatis中贯串全局的存在,后续我们要提到的Mybatis降低全表更新插件,也是基于那个对象来完成。此中解析mapper.xml那步最末感化即是将解析的每一条CRUD语句封拆成对应的MappedStatement存放至Configuration中。

第三步:创建SqlSession对象。 创建过程中会创建别的两个工具,事务及施行器,SqlSession能够说只是一个前台客服,实正发扬感化的是Executor,它是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的庇护,对SqlSession办法的拜候最末城市落到Executor的响应办法上往。Executor分红两大类:一类是CachingExecutor,另一类是通俗的Executor。CachingExecutor是在开启二级缓存顶用到的,二级缓存是慎开启的,那里只介绍通俗的Executor,通俗的Executor分为三大类,SimpleExecutor、ReuseExecutor和BatchExecutor,他们是根据全局设置装备摆设来创建的。SimpleExecutor是一种常规施行器,也是默认的施行器,每次施行城市创建一个Statement,用完后封闭;ReuseExecutor是可重用施行器,将Statement存进map中,操做map中的Statement而不会反复创建Statement;BatchExecutor是批处置型施行器,专门用于施行批量sql操做。总之,Executor最末是通过JDBC的java.sql.Statement来施行数据库操做。

第四步:获取Mapper代办署理对象。 上面也已经提到了那块用到的是jdk动态代办署理手艺,那里MapperRegistry和MapperProxyFactory在解析mapper.xml已经被创建保留在了Configuration中,那步次要就是从MapperProxyFactory获取MapperProxy代办署理。此中MapperMethod次要的功用是施行SQL的相关操做,它根据供给的Mapper的接口途径,待施行的办法以及设置装备摆设Configuration做为进参来施行对应的MappedStatement操做。

第五步:施行SQL操做。 那步就是施行施行对应的MappedStatement操做,Executor最末是通过JDBC的java.sql.Statement来施行数据库操做。但其实实正负责操做的是StatementHanlder对象,StatementHanlder封拆了JDBC Statement 操做,负责对 JDBC Statement 的操做,它通过掌握差别的子类,往施行完全的一条SQL施行与解析的流程。

三、MyBatis拦截器

Mybatis一共供给了四大扩展点,也称做四大拦截器插件,它是生成层层代办署理对象的一种责任链形式。那里代办署理的实现体例是将切进的目标处置器与拦截器停止包拆,生成一个代办署理类,在施行invoke办法前先施行自定义拦截器插件的逻辑从而实现的一种拦截体例。每个处置器在Mybatis的整个施行链路中饰演的角色也差别,各人假设有设法能够基于那几个扩展点实现一款本身的拦截器插件。例如我们常用的一个分页插件pageHelper就是操纵Executor拦截器实现的,有兴致的能够自行阅读下pageHelper源码。MyBatis一共供给了四个扩展点:

•Executor (update, query, ……)

Executor根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler利用。创建JDBC的Statement毗连对象,传递给StatementHandler对象。那里Executor又称做 SQL施行器。

· StatementHandler (prepare, parameterize, ……)

StatementHandler关于JDBC的PreparedStatement类型的对象,创建的过程中,那时的SQL语句字符串是包罗若干个 “?” 占位符。那里StatementHandler又称做SQL 语法构建器。

· ParameterHandler (getParameterObject, ……)

ParameterHandler用于SQL对参数的处置,那步会通过TypeHandler将占位符替代为参数值,接着陆续进进PreparedStatementHandler对象的query办法停止查询。那里ParameterHandler又称做参数处置器。

· ResultSetHandler (handleResultSets, ……)

ResultSetHandler停止最初数据集(ResultSet)的封拆返回处置。那里ResultSetHandler又称做成果集处置器。

四、MyBatis避免全表更新插件

上面说到法式员A小哥哥碰着过汗青营业参数因校验问题形成了全表更新的风险,梳理代码成本又过高,不契合当下互联网将本增效的理念。那么有没有一种成本又低,效率又高,又能通用的产物来处理此类问题呢?

当然有了!!! 否则那篇帖子搁那凑绩效呢? 哈哈... 欠好笑欠好笑,见谅。

第三章节中,提到MyBatis为利用者供给了四个扩展点,那么我们就能够借助扩展点来实现一个Mybatis避免全表更新的插件,详细怎么实现呢?那里博主是利用StatementHandler拦截器笼统出来一个SDK供需求方接进,拦截器详细用法参考度娘,那里SDK实现流程为:获取预处置SQL及参数值 -- 替代占位符组拆完全SQL -- SQL语句规则解析 -- 校验能否为全表更新SQL。 当然还做了一些横向扩展,那里放张图吧,更清晰些。

那么那个插件能拦截哪些类型的SQL语句呢?

·无where前提:update/delete table

·逻辑删除字段:update/delete table where yn = 0 //yn为逻辑删除字段

·拼接前提语句:update/delete table where 1 = 1

·AND前提语句:update/delete table where 1 = 1 and 1 2

·OR 前提语句:update/delete table where 1 = 1 or 1 2

然后聊下怎么接进吧:

4.1 查抄项目依靠

scope为provided的请在项目中加进该jar包依靠,此插件默认引进p6spy、jsqlparser依靠,如遇版本抵触请排包

dependency

groupIdorg.slf4j/groupId

artifactIdslf4j-api/artifactId

version${slf4j.version}/version

scopeprovided/scope

/dependency

dependency

groupIdp6spy/groupId

artifactIdp6spy/artifactId

version${p6spy.version}/version

/dependency

dependency

groupIdorg.mybatis/groupId

artifactIdmybatis/artifactId

version${mybatis.version}/version

scopeprovided/scope

/dependency

dependency

groupIdorg.mybatis/groupId

artifactIdmybatis-spring/artifactId

version${mybatis-spring.version}/version

scopeprovided/scope

exclusions

exclusion

groupIdorg.mybatis/groupId

artifactIdmybatis/artifactId

/exclusion

/exclusions

/dependency

dependency

groupIdcom.github.jsqlparser/groupId

artifactIdjsqlparser/artifactId

version${jsqlparser.version}/version

/dependency

dependency

groupIdorg.springframework/groupId

artifactIdspring-core/artifactId

version${spring.core.version}/version

scopeprovided/scope

/dependency

4.2 项目中引进避免全表更新依靠SDK

dependency

groupIdcom.jd.o2o/groupId

artifactIdo2o-mybatis-interceptor/artifactId

version1.0.0-SNAPSHOT/version

/dependency

4.3 项目中添加设置装备摆设

springboot项目利用体例: 设置装备摆设类中加进拦截器设置装备摆设

@Configuration

public class MybatisConfig {

@Bean

ConfigurationCustomizer configurationCustomizer() {

return new ConfigurationCustomizer() {

@Override

public void customize(org.apache.ibatis.session.Configuration configuration) {

FullTableDataOperateInterceptor fullTableDataOperateInterceptor = new FullTableDataOperateInterceptor();

//表默认逻辑删除字段,按需设置装备摆设,update cms set name = "zhangsan" where yn = 0,yn为逻辑删除资本,此语句被认为是全表更新语句

fullTableDataOperateInterceptor.setLogicField("yn");

//白名单表,按需设置装备摆设,设置装备摆设的白名单表不拦截该表全表更新操做

fullTableDataOperateInterceptor.setWhiteTables(Arrays.asList("tableName1","tableName2"));

//个别表的逻辑删除字段映射,假设设置装备摆设此项,此表逻辑删除字段优先走该表设置装备摆设,key为表名,value为该表的逻辑删除字段名,每对key-value以英文逗号分隔设置装备摆设

MapString,String tableToLogicFieldMap = new HashMap();

tableToLogicFieldMap.put("tableName3","ynn");

tableToLogicFieldMap.put("tableName4","ynn");

fullTableDataOperateInterceptor.setTableToLogicFieldMap(tableToLogicFieldMap);

//设置装备摆设拦截器

configuration.addInterceptor(fullTableDataOperateInterceptor);

传统SSM项目利用体例: 在mybatis.xml中逃加plugin设置装备摆设

configuration

plugins

plugin interceptor="com.jd.o2o.cms.mybatis.interceptor.FullTableDataOperateInterceptor"

//表默认逻辑删除字段,按需设置装备摆设,update cms set name = "zhangsan" where yn = 0,yn为逻辑删除字段,此语句被认为是全表更新语句

property name="logicField" value="yn"/

//白名单表,按需设置装备摆设,设置装备摆设的白名单表不拦截该表全表更新操做

property name="whiteTables" value="tableName1,tableName2"/

//个别表的逻辑删除字段映射,假设设置装备摆设此项,此表逻辑删除字段优先走该表设置装备摆设,key为表名,value为该表的逻辑删除字段名,每对key-value以英文逗号分隔设置装备摆设

property name="tableToLogicFieldMap" value="key1:value1,key2:value2"/

/plugin

/plugins

/configuration

4.4 添加日记输出

该插件有四处输出error日记,详细可看源码

Logger name="com.jd.o2o.cms.mybatis.interceptor" level="error" additivity="false"

AppenderRef ref="RollingFileError"/

/Logger

4.5 性能及接进阐明

各人最关心的可能是,接进那个SDK后,对我们数据库操做的性能有多大影响,那里针对性能做下阐明:

•select:无性能影响

•insert:不敷千分之一毫秒

•update:约为0.02毫秒

•delete:约为0.02毫秒

然后就是对接进的风险的考虑,假设为该插件解析过程中的反常,该插件间接catch交由MyBatis停止下个施行链的处置,对营业流程无影响,代码为证:

上一篇:最受欢迎“女友职业排行榜”:第1名意料之中,最后一名不敢娶

派趣吧

我来回答