-
MyBatis源码学习—MyBatis结果处理器ResultSetHandler详解
上一篇博文MyBatis源码学习—MyBatis数据仓库会话器StatementHandler详解我们通过Statement将SQL发送到了数据库,并返回了ResultSet,接下来就是将结果集ResultSet自动映射成实体类对象。这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中。这可大大降低开发的工作量,提高工作效率。1、ResultSetHandler简介回想一下,一条SQL的请求在MyBatis中会经过哪几个步骤?(1)首先会经过Executor,一般默认的配置是SimpleExecutor,Eexcutor负责创建并维护一个StatementHandler对象,之后直接调用StatementHandler中的增删改查方法即可;(2)StatementHandler有三种类型的:简单类型的SimpleStatementHandler、预编译SQL类型的PrepraedStatementHandler以及执行存储过程的CallabletatenmentHandler,他们内部分别维护了一个Statement对象、PrepraedStatementHandler对象以及CallableStatementHandler对象,另外在ParameterStatementHandler和CallabletatenmentHandler还维护一个ParameterHandler对象用于解析参数。说白了,MyBatis最终还是通过JDBC中的Statement对象、PrepraedStatement对象以及CallableStatement对象和数据库交互的。(3)查询数据库返回结果之后就需要把查询的结果通过某个东东映射成为实体类对象并且以Mapper方法指定的类型返回结果集,这个东东就是今天我们要了解的神器**ResultSetHandler**下图所示就是ResultSetHnalder接口的方法。ResultSetHandler接口,和ParameterHandler一样,ResultSetHandler也只有一个默认实现类DefaultResultSetHandler2、ResultSetHandler对象的创建ResultSetHandler对象是在创建StatementHandler对象的同时被创建,由Configuration对象负责创建,以BaseStatementHandler为例具体创建逻辑在Configuration类中的newResiltSetHandler方法中,如下图所示。可以看到,关键过程非常简单,就是直接new了一个默认ResultSetHandler实现类DefaultResultSetHandler3、ResultSetHandler处理结果映射(1)映射结果处理过程我们来看看上次看源码的位置,如下图所示源码就是SimpleStatementHandler中query方法源码。在StatementHanlder借助Statement查询到结果后,直接调用了ResultSetHandler的handleResultSets方法来处理返回的结果集。从上面的源码可以看到,在StatementHandler中还维护了一个ResultSetHandler对象。上面我说到过,ResultSetHandler只有一个默认实现类DefaultResultSetHandler,ResultSetHandler主要负责处理两件事:处理Statement执行后产生的结果集,生成结果列表处理存储过程执行后的输出参数按照Mapper文件中配置的ResultType或ResultMap来封装成对应的对象,最后将封装的对象返回即可。来看一下主要的源码:在实际运行过程中,通常情况下一个Sql语句只返回一个结果集,对多个结果集的情况不做分析。实际很少用到。继续看handleResultSet方法通过handleRowValues映射ResultSet结果,最后映射的结果会在defaultResultHandler的ResultList集合中,最后将结果加入到multipleResults中就可以返回了,我们继续跟进handleRowValues这个核心方法我们可以通过resultMap.hasNestedResultMaps()判断查询语句是否是嵌套查询,如果resultMap中包含和且其select属性不为空,则为嵌套查询。本文先分析简单的映射:(2)ResultListMyBatis默认提供了RowBounds用于分页,从上面的代码中可以看出,这并非是一个高效的分页方式,是查出所有的数据,进行内存分页。除了使用RowBounds,我们可以使用一些第三方分页插件进行分页,比如PageHelper。重要的逻辑已经注释出来了。分别如下:创建实体类对象自动映射结果集中有的column,但resultMap中并没有配置根据节点中配置的映射关系进行映射创建实体类对象说明:创建代理类,默认使用Javassist框架生成代理类,但是深入到createProxy方法中最终会发现是通过CGLib实现创建代理对象的。createResultObject重载方法的逻辑:一般情况下,MyBatis会通过ObjectFactory调用默认构造方法创建实体类对象。看看是如何创建的很简单,就是通过反射创建对象(3)结果集映射映射结果集分为两种情况:一种是自动映射(结果集有但在resultMap里没有配置的字段),在实际应用中,都会使用自动映射,减少配置的工作。自动映射在Mybatis中也是默认开启的。第二种是映射ResultMap中配置的,这两中映射我们分开来看自动映射自动映射就是MyBatis根据实体类中的字段,把查询的结果自动的映射到字段属性上,然后封装成一个对象返回数据。首先是获取**UnMappedColumnAutoMapping**集合,然后遍历该集合,并通过TypeHandler从结果集中获取数据,最后再将获取到的数据设置到实体类对象中。UnMappedColumnAutoMapping是DefaultResultSetHandler的内部类,用于记录没有在配置在节点中的映射关系。它的代码如下:UnMappedColumnAutoMapping仅仅就是用于记录一些未在resultMap标签中配置的自动映射行(即实体类中的属性)。下面看一下获取UnMappedColumnAutoMapping集合的过程,如下:先来看看从**ResultSetWrapper**中获取未配置在中的列名来看看那loadMappedAndUnmappedColumnNames方法是如何加载列名的。首先是从当前数据集中获取列名集合,然后获取中配置的列名集合。之后遍历数据集中的列名集合,并判断列名是否被配置在了节点中。若配置了,则表明该列名已有映射关系,此时该列名存入mappedColumnNames中。若未配置,则表明列名未与实体类的某个字段形成映射关系,此时该列名存入unmappedColumnNames中。映射result节点接下来分析一下MyBatis是如何将结果集中的数据填充到已配置ResultMap映射的实体类字段中的。从ResultMap获取映射对象ResultMapping集合。然后遍历ResultMapping集合,再此过程中调用getPropertyMappingValue获取指定指定列的数据,最后将获取到的数据设置到实体类对象中。这里和自动映射有一点不同,自动映射是从直接从ResultSet中获取指定列的值,但是通过ResultMap多了一种情况,那就是关联查询,也可以说是延迟查询,此关联查询如果没有配置延迟加载,那么就要获取关联查询的值,如果配置了延迟加载,则返回DEFERED参考资料[1]https://www.cnblogs.com/java-chen-hao/p/11760777.html[2]https://www.cnblogs.com/cxuanBlog/p/11318861.html
LoveIT 2020-07-13MyBatis -
MyBatis源码学习—MyBatis参数处理器ParameterHandler详解
MyBatis的四大组件我们已经了解过两种了:一个是Executor,它在创建SqlSession的时候会被初始化,它是MyBatis解析SQL请求首先会经过的第一道关卡,它的主要作用在于创建缓存,管理StatementHandler的调用,为StatementHandler提供Configuration环境等。StatementHandler组件最主要的作用在于创建Statement对象与数据库进行交流,还会使用ParameterHandler进行参数配置,使用ResultSetHandler把查询结果与实体类进行绑定。这篇文章我们就来学习一下ParameterHandler。1、ParameterHandler简介ParameterHandler相比于其他的组件就简单很多了,ParameterHandler>ParameterHandler相比于其他的组件就简单很多了,ParameterHandler译为参数处理器,负责为PreparedStatement的sql语句参数动态赋值,这个接口很简单只有两个方法ParameterHandler只有一个实现类DefaultParameterHandler,它实现了这两个方法:getParamenterObject():用于读取参数setParamenters():用于对PreparedStatement参数赋值2、ParameterHandler对象的创建参数处理器对象是在创建StatementHandler对象的同时被创建的,由Configuration对象负责创建,以BaseStatementHandler为例在创建ParameterHandler时,需要传入mappedStatement对象,用于读取参数和SQL语句注意:一个BoundSql对象,就代表了一次sql语句的实际执行,而SqlSource对象的责任,就是根据传入的参数对象,动态计算这个BoundSql,也就是Mapper文件中节点的计算,是由SqlSource完成的,SqlSource最常用的实现类是DynamicSqlSourceConfiguration.java上面是Configuration创建ParameterHandler的过程,它实际上是交由LanguageDriver来创建具体的参数处理器,LanguageDriver默认的实现类是XMLLanguageDriver,由它调用DefaultParameterHandler中的构造方法完成ParameterHandler的创建工作3、ParameterHandler解析绑定参数的流程上面我们了解了参数处理器的创建过程,创建完成之后,该进行具体的解析工作,那么ParameterHandler如何解析SQL中的参数呢?SQL中的参数从哪里来的?在PraparedStatementHandler的parameterize方法调用参数解析器的setParameters给PreparedStatement的SQL语句设置参数DefaultParameterHandler.java至此我们可以知道,每一次的调用查询(数据库查询,不走Mybatis缓存),Executor在执行doQuery的时候都会创建一个StatementHandler实例,每个StatementHandler在实例化的时候,都会创建并持有两个处理器即ParameterHandler和ResultSetHandler这样StatementHandler就可以利用ParameterHandler完成预处理语句的参数化设置,以及结果查询出来以后再利用ResultSetHandler处理结果集下图展示的就是一条SQL语句从Executor到ParameterHandler的关键流程:
LoveIT 2020-07-12MyBatis -
MyBatis源码学习—MyBatis数据仓库会话器StatementHandler详解
1、StatementHandler对象的创建过程在上一节MyBatis源码学习—MyBatis执行器Executor详解中我们了解MyBatis执行器的产生以及执行SQL的大致过程。StatementHandler对象是在SqlSession对象接收到SQL执行命令时,由Configuration对象中的newStatementHandler负责调用的,也就是说Configuration中的newStatementHandler是由执行器中的查询、更新(插入、更新、删除)方法来提供的,StatementHandler其实就是由Executor负责管理和创建的。以查询为例,在SimpleExecutor中最终落到了doQuery()这个方法,如下图所示。在执行查询逻辑中比较重要的异步操作就是StatementHandlerhandler=configuration.newStatementHandler(......)这一句代码,从字面意思看,它就是创建了一个StatementHandler实例对象,一个用来执行SQL查询的处理器。追进去看看newStatementHandler方法干了什么,如下图所示Configuration类的newStatementHandler方法。可以看到他直接为我们new了一个RoutingStatementHandler对象,然后返回了这个对象。RoutingStatementHandler在下面会详细介绍,别着急!!!2、StatementHandler继承结构和Executor体系的继承体系类似,最顶层的接口是StatementHandler,往下有2个直接实现类:BaseStatementHandler和RoutingStatementHandler,BaseStatementHandler又有3个子类:PreparedStatementHandler、SimpleStatementHandler以及CallableStatementHandlerStatementHandler:MyBatis四大组件中最重要的一个对象,负责操作Statement对象(JDBC)与数据库进行交流,在工作时还会使用ParameterHandler和ResultSetHandler对参数进行映射,对结果进行实体类的绑定。StatementHandler其实就是对JDBC中Statement的包装,为了一探究竟我们直接打开MyBatis源码来看一下。下图所示是StatementHandler接口的方法。prepare():用于创建一个具体的Statement对象的实现类实例parameterize():用于初始化Statement实例对象以及对SQL语句中的占位符进行数据填充update():用于通知Statement对象将insert、update、delete操作发送给数据库query():用于通知Statement对象将select操作发送到数据库RoutingStatementHandler:从它的名字就可以窥见它的作用,它的作用就是选择合适的StatementHandler实现类的,具体就是根据StatementType参数来创建一个代理,代理的就是对应Handler的三种实现类看到这里,是不是对RoutingStatementHandler这个类的作用恍然大悟~~他就类似与一个路由器,把一条SQL请求路由到一个具体的StatementHandler实现类去BaseStatementHandler:是StatementHandler接口的另一个实现类,本身是一个抽象类,用于简化StatementHandler接口实现的难度,属于适配器设计模式体现,它主要有三个实现类:SimpleStatementHandler:用于处理不需要预编译的SQL语句,类似于JDBC中普通的StatementPraparedStatementHandler:用于处理不需要预编译的SQL语句,类似于JDBC中的PraparedStatementCallableStatementHandler:用于处理存储过程,类似JDBC中的CallableStatement3、StatementHandler核心方法源码分析prepare方法调用流程分析执行器Executor在执行SQL语句的时候会创建StatementHandler对象,进而经过一系列的StatementHandler类型的判断并初始化。再拿到StatementHandler返回的statementhandler对象的时候,会调用其prepareStatement()方法,下面就来一起看一下preparedStatement()方法从Executor的doQuery方法开始,首先实例化一个Statement对象,其实就是RoutingStatementHandler,在RoutingStatementHandler内部维护了一个真实干活的StatementHandler对象。实例化完成后,调用了执行器的prepareStatement方法获取数据库连接Connetion,紧接着调用了StatementHandler的prepare方法,这个调用首先会到RoutingStatementHandler中的同名方法,之后会调到BaseStatementHandler中,这个方法只在BaseStatementHandler有具体实现,在这个实现中,它又调用了**instantiateStatement**,这个方法在BaseStatementHandler没有具体实现,实际执行的是三种StatementHandler中的一种,我们还以SimpleStatementHandler为例从上面代码我们可以看到,instantiateStatement()最终返回的是Statement对象,经过一系列的调用会把Statement对象返回到SimpleExecutor执行器中,为parametersize方法所用。也就是说,prepare方法负责生成Statement实例对象,而parameterize方法用于处理Statement实例对应的参数。parametersize方法调用流程分析在上面的prepareStatement方法中执行器拿到具体的Statement对象实例之后就会调用parametersize方法用处理Statement实例的参数对于这个SimpleStatementHanlder在前面我们说过他就是用来处理简单SQL语句的,因此它的实现方法是空的,如下图所示。因此具体的原理我们着重看PraparedStatementHandler的同名方法的实现,如下图所示。可以看到,具体给SQL语句参数赋值的操作MyBatis有把它交给了下一个组件ParameterHandler(参数处理器),具体内容请参考我的下一篇博文:MyBatis源码学习—MyBatis参数处理器ParameterHandler详解query方法调用流程分析StatementHandler中query方法是用于执行SQL查询语句的方法,它的具体实现在BaseStatementHandler中没有实现,我们以SimpleStatementHandler为例来分析一下。下图所示是SimpleStatementHandler的query方法。流程非常简单暴力:获取到sql语句=>调用Statement的execute方法执行sql=>调用结果处理器封装查询结果。具体查询结果集封装返回值的类容请参考我的博文:MyBatis源码学习—MyBatis结果处理器ResultHandler详解update方法调用流程分析StatementHandler的update方法是用于SQL语句的insert、update、delete语句的执行,我们以SimpleStatementHandler为例来分析一下。下图所示是SimpleStatementHandler的update方法。简单描述一下update方法的执行过程:MyBatis接收到update请求后会先找到CachingExecutor缓存执行器查询是否需要刷新缓存,然后找到BaseExecutor执行update方法;BaseExecutor基础执行器会清空一级缓存,然后交给再根据执行器的类型找到对应的执行器,继续执行update方法;具体的执行器会先创建Configuration对象,根据Configuration对象调用newStatementHandler方法,返回statementHandler的句柄;具体的执行器会调用prepareStatement方法,找到本类的prepareStatement方法后,再有prepareStatement方法调用StatementHandler的子类BaseStatementHandler中的prepare方法BaseStatementHandler中的prepare方法会调用instantiateStatement实例化具体的Statement对象并返回给具体的执行器对象由具体的执行器对象调用parameterize方法给参数进行赋值。用一幅图来表示一下这个调用过程:参考资料[1]https://www.cnblogs.com/cxuanBlog/p/11295488.html
LoveIT 2020-07-09MyBatis -
MyBatis源码学习—MyBatis 执行器Executor详解
从前面分析我们知道了sql的具体执行是通过调用SqlSession接口的对应的方法去执行的,而SqlSession最终都是通过调用了自己的Executor对象的query和update去执行的。本文就分析下sql的执行器-----Executor。1、Executor继承体系下图展示的是MyBatis的执行器的核心类的继承体系图。Executor是执行器的顶层接口,它定义了查询、更新事务提交、回滚等一系类和执行SQL有关的方法。他有两个直接实现类:BaseExecutor和CachingExecutorBaseExecutor:是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式的体现,是Executor的默认实现,实现了大部分Executor接口定义的功能,降低了接口实现的难度。BaseExecutor有三个子类,分别对应三种执行器类型,在ExecutorType这个枚举类中有定义,如下图所示。SIMPLE对应SimpleExecutor,是一种常规执行器,每次执行都会创建一个statement,用完后关闭,默认值。REUSE对应ReuseExecutor,它是一个可重用Statement对象的执行器,不会关闭statement,而是把statement放到缓存中(Map中)。缓存的key为sql语句,value即为对应的statement。也就是说不会每一次调用都去创建一个Statement对象,而是会重复利用以前创建好的(如果SQL相同的话),这也就是在很多数据连接池库中常见的PSCache概念。BATCH对应BatchExecutor,一个用于执行存储过程的和批量操作的执行器,他也会重用Statement对象。在MyBatis的三种执行器,我们可以在配置文件中通过设定settings中的defaultExecutorType属性的值来选择,通常来说:SimpleExecutor比ReuseExecutor的性能要差,因为SimpleExecutor没有做PSCache。为什么做了PSCache性能就会高呢,因为当SQL越复杂占位符越多的时候预编译的时间也就越长,创建一个PreparedStatement对象的时间也就越长。猜想中BatchExecutor比ReuseExecutor功能强大性能高,实际上并非如此,BatchExecutor是没有做PSCache的。BatchExecutor与SimpleExecutor和ReuseExecutor还有一个区别就是,BatchExecutor的事务是没法自动提交的。因为BatchExecutor只有在调用了SqlSession的commit方法的时候,它才会去执行executeBatch方法。CachingExecutor:缓存执行器,MyBatis二级缓存的实现依赖他实现,先从缓存中查询结果,如果存在,就返回结果;如果不存在,再委托给Executordelegate去数据库中取,delegate可以是上面任何一个执行器,默认是SimpleExecutor。2、Executor创建过程工作原理上面说到过,在配置文件中可以通过设定settings中的defaultExecutorType属性的值来选择执行器类型,如果没有设置,则默认会选择SimpleExecutor。其实创建Executor只是创建SqlSession的一个子步骤,在我的另一篇博文:MyBatis源码学习—SqlSession的运行过程中有过分析,这里再来分析一下。下图所示就是创建SqlSession的核心代码,在try块倒数2行就是创建执行器的逻辑,我们看到它调用了Configuration对象的newExecutor方法追踪到newExecutor方法内部看看执行器创建具体创建流程,如下图所示。上面代码的逻辑大致如下:根据形参executorType是否null获得即将要创建的执行器的类型,如果executorType==null,那就创建SimpleExecutor,否则创建指定类型的执行器;根据执行器类型executorType直接new指定类型的执行器;判断是否配置开启了二级缓存(cacheEnable==true?),如果开启了的话,那就在new一个CachingExecutor包装一下上面的简单执行器executor;在创建Executor成功之后,MyBatis会执行下面一行代码:这里用到了责任链模式,主要就是MyBatis配置插件,这里它将为我们构建一层层的动态代理对象。在调度真实的Executor方法之前执行配置插件的代码可以修改。到这里,执行器Executor实例就被创建出来了,伴随着SqlSession对象实例也即将创建成功。接下来我们分析一个Executor中主要方法的执行流程。3、Executor接口的主要方法Executor接口的方法还是比较多的,这里我们就几个主要的方法和调用流程来做一个简单的描述。Executor中的大部分方法的调用链其实是差不多的,下面都是深入源码分析执行过程,下图所示是MyBatis的一般执行过程。query()方法query方法有两种形式,一种是直接查询;一种是从缓存中查询,下面来看一下源码当有一个查询请求访问的时候,首先会经过Executor的实现类CachingExecutor,先从缓存中查询SQL是否是第一次执行,如果是第一次执行的话,那么就直接执行SQL语句,并创建缓存,如果第二次访问相同的SQL语句的话,那么就会直接从缓存中提取CachingExecutor类中对query()方法实现首先会由第一个query()方法执行,他会创建缓存的key以及从MappedStetament中获取到SQL基本信息,然后调用第二个query()方法,这个方法中首先会从缓存中查询,没有数据调用delegate执行器(BaseExecutor下的三个子执行器实例)的query()方法。SimpleExecutor的doQuery()方法经过一系列的调用,最终来到了实例类的doQuery()方法,这里首先以SimpleExecutor为例分析,如下图所示。显然MyBatis根据Configuration对象构建了一个StatementHandler对象,然后使用prepareStatement方法,对SQL编译并对参数进行初始化,我们在看它的实现过程,它调用了StatementHandler的prepare()进行了预编译和基础设置,然后通过StatementHandler的parameterize(来设置参数并执行,resultHandler再组装查询结果返回给调用者来完成一次查询。ReuseExecutor的doQuery()方法几乎和SimpleExecutor完成的工作一样,其内部不过是使用一个Map来存储每次执行的查询语句,为后面的SQL重用作准备(Map中存缓存的其实是Statement)。上面的源码可以看出,Executor执行器所起的作用相当于是管理StatementHandler的整个生命周期的工作,包括创建、初始化、解析、关闭。BatchExecutor的doQuery()方法工作原理和SimpleExecutor基本类似,不过要注意的是BatchExecutor支持一次性将多条SQL语句送到数据库执行。update()方法还是一样,在开启缓存的情况下会首先执行CachingExecutor的的update()方法,如下图所示。首先还是会调用到BaseExcutor的doUpdate()方法,之后调用到具体实例子类执行器的doUpdate()SimpleExecutor的doUpdate()方法逻辑大致和query()方法的逻辑类似,图中有注释这里不再解释。queryCursor()方法我们查阅其源码的时候,在执行器的执行过程中并没有发现其与query方法有任何不同之处,但是在doQueryCursor()方法中我们可以看到它返回了一个cursor对象,网上搜索cursor的相关资料并查阅其基本结构,得出来的结论是:用于逐条读取SQL语句,应对数据量flushStatements()方法flushStatement()的主要执行流程和query,update的执行流程差不多,flushStatement()的主要作用,flushStatement()主要用来释放statement,或者用于ReuseExecutor和BatchExecutor来刷新缓存在SimpleExecutor中它就是返回了一个空集合对象在ReuseExecutor中它用于释放缓存的Statement,同样也是返回了空集合createCacheKey()方法createCacheKey()方法用于创建缓存数据的key的,要想了解这个方法的原理,我们需要先研究一下CacheKey这个类,下图是Cachekey的属性和构造方法在cache中唯一确定一个缓存项需要使用缓存项的key,Mybatis中因为涉及到动态SQL等多方面因素,其缓存项的key不能仅仅通过一个String表示,所以MyBatis提供了CacheKey类来表示缓存项的key,在一个CacheKey对象中可以封装多个影响缓存项的因素。key的hashCode17是质子数中一个“不大不小”的存在,如果你使用的是一个如2这样较小的质数,那么得出的乘积会在一个很小的范围,很容易造成哈希值的冲突。而如果选择一个100以上的质数,得出的哈希值会超出int的最大范围,这两种都不合适。而如果对超过50,000个英文单词(由两个不同版本的Unix字典合并而成)进行hashcode运算,并使用常数31,33,37,39和41作为乘子(cachekey使用37),每个常数算出的哈希值冲突数都小于7个(国外大神做的测试),那么这几个数就被作为生成hashCode值得备选乘数了最终一个CacheKey中的key的hashcode=乘子{31,33,37,39或41}*oldHashCode+objectHashCodeExecutor的现实抽象在上面的分析过程中我们了解到,Executor执行器是MyBatis中很重要的一个组件,Executor相当于是外包的boss,它定义了甲方(SQL)需要干的活(Executor的主要方法),这个外包公司是个小公司,没多少人,每个人都需要干很多工作,boss接到开发任务的话,一般都找项目经理(CachingExecutor),项目经理几乎不懂技术,它主要和技术leader(BaseExecutor)打交道,技术leader主要负责框架的搭建,具体的工作都会交给下面的程序员来做,程序员的技术也有优劣,高级程序员(BatchExecutor)、中级程序员(ReuseExecutor)、初级程序员(SimpleExecutor),它们干的活也不一样。一般有新的项目需求传达到项目经理这里,项目经理先判断自己手里有没有现成的类库或者项目直接套用(Cache),有的话就直接让技术leader拿来直接套用就好,没有的话就需要搭建框架,再把框架存入本地类库中,再进行解析。参考资料[1]https://www.cnblogs.com/cxuanBlog/p/11178489.html#executor%E6%8E%A5%E5%8F%A3%E7%9A%84%E4%B8%BB%E8%A6%81%E6%96%B9%E6%B3%95[2]https://blog.csdn.net/xl3379307903/article/details/80517841
LoveIT 2020-07-08MyBatis -
MyBatis源码学习—SqlSession的运行过程
SqlSession的运行过程是整个MyBatis最难以理解的部分。SqlSession是一个接口,使用它并不复杂。我们构建SqlSessionFactory之后就可以轻易地拿到SqlSession了。SqlSession接口给出了查询、插入、更新、删除的方法,在旧版本的MyBatis或iBatis中常常使用这些接口方法,而在新版的MyBatis-3中我们建议使用Mapper,SqlSession作为MyBatis最为常用和重要的接口之一,我们有必要学习一下其中的设计思路。1、构建SqlSession对象首先我们先来看一段代码:在我的上一篇博文:MyBatis源码学习—SqlSessionFactory的构建过程中讨论了MyBatis的SqSessionFactory的构建过程,有了SqlSeesionFactory我们就可以很轻松的使用sqlSessionFactory.openSession()一句代码构建一个SqlSession对象了,那么SqlSession的构建过程中有发生了那些事情呢?我们继续追踪源码~~跟随源码,首先我们来到SqlSeesionFactory接口的默认实现类DefaultSqlSessionFactory内部。如下图所示。openSession()方法调用了另一个方法openSessionFromDataSource,也即使用过数据库数据源配置获取SqlSession对象;除此之外,DefaultSessionFactory还提供了另一种获取SqlSession对象的方法:openSessionFromConnection,即我们还可以给MyBatis一个和数据库建立好的连接Connection对象来创建SqlSession对象,如下图所示。好的,回到主题来。我继续追到openSessionFromDataSource方法中看看MyBatis为我们做了哪些工作,如下图所示。方法传入三个参数:ExecutorType、TransactionIsolationLevel和一个boolean类型的表示autoCommitExecutorType:执行器的类型,是个枚举类,定义了三种类型的执行器:SIMPLE、REUSE、BATCH,分别对应MyBatis中三种不同类型的执行器。详细内容请参考我的另一篇博文:MyBatis源码学习—MyBatis中的ExecutorType。TransactionIsolationLevel:事务隔离级别。MyBatis中定义的事物隔离级别和MySQL、Spring的事物隔离级别是一致的,详细内容请参考我的另一篇博文:Spring从入门到精通—Spring事务详解。参数autoCommit:指示是否开启自动提交事务,默认是关闭的,也即不自动提交事务。方法的执行流程大致如下:通过configuration对象获取到配置信息Enviroment使用Enviromenth获取创建事务管理工厂,并通过事务工厂获取到事物管理器JdbcTransaction根据执行器类型ExecutorType创建指定执行器,默认的执行器是SimpleExecutor获得执行器对象之后,MyBatis直接为我们new了一个SqlSeesion的默认实现类DefaultSqlSessionExecutor创建细节简单分析MyBatis通过调用configuration对象的newExecutor方法获取到执行器实例,如下图所示。方法开始,检查如果没有指定执行器类型,如果没有指定执行器类型,MyBatis就会选择使用默认的执行器SimpleExecutor,之后MyBatis就会通过一个参数cacheEnabled来判断是否开启二级缓存(不清楚的老铁才可以参考我的另一篇博文:深入理解MyBatis缓存机制),因此new了一个CacheingExecutor包装了一下executor。顺便提一嘴,对于一级缓存/session级缓存MyBatis默认是开启的,并且不可控制,它是在BaseExecutor中使用PerpetualCache类型的localCache字段控制的,如下图所示。2、映射器的动态代理到这里,我们就获得了一个SqlSession对象,通过SqlSession对象我们就可以调用getMapper()获取到Mapper接口的“实例”,进而执行sql查询了,如下图所示。正如我们看到的那样,在使用MyBatis的时候我们只需要一个Mapper接口,然后使用SqlSession的getMapper()方法就可以获得这个Mapper接口的实例,进而可以实现对数据库的CRUD,但Mapper仅仅是一个接口,而不是一个包含逻辑的实现类。我们知道一个接口是没有办法去执行的,那么它是怎么运行的呢?这不是违反了教科书所说的接口不能运行的道理吗?相信有很多同学对此有疑惑,并且在面试中被面试官这样问过。答案就是动态代理。我们不妨先来看看Mapper是个什么东西。如下图所示。原来是MyBatis为Mapper接口自动生成了一个代理对象。原理就是使用jdk动态代理通过反射为我们创建了代理对象,具体看源码吧。从我们代码的getMapper进去,这里封装了一下,直接调用了configuration对象的getMapper方法configuration对象的getMapper方法也是进行了封装,直接调用了MapperRegistry类的getMapper方法。注:(MapperRegistry是一个专门管Mapper注册的一个类,这一步在构建SqlSessionFactory的时候就初始化了,详情参考MyBatisXMLConfigBuilder类的mapperElement方法)MapperRegistry中核心的就是那句mapperProxyFactory.newInstance(sqlSession),它调用了Mapper代理工厂的newInstance方法如下图所示,我们可以看到MapperProxyFactory中使用了jdk动态代理为Mapper接口创建了代理对象。关于jdk动态代理的原理可以参考我的另一篇博文:透过代理模式探究JAVA的静态代理和动态代理。我们看到,在调用jdk动态代理之前,MyBatis先new了一个MyBatisProxy对象,这是啥呢?其实MapperProxy就是jdk动态代理的重要接口InvocationHandler的一个实现类,被代理的方法全部都在MapperProxy这个类中MapperProxy细节MyBatis在MapperProxyFctory中调用了Proxy.newProxyInstance方法产生了Mapper接口的代理对象,代理对象的全部方都在MapperProxy这个类中,我们来看看。上面运用了invoke方法。invoke方法会拦截Mapper中的所有方法。拦截之后invoke首先判断method如果是Object中定义的方法,直接执行。如toString(),hashCode()等,其他Mapper接口定义的方法交由MapperMethodInvoker来执行。MapperMethodInvoker是一个接口,它在MapperProxy这个类中有两个实现类DefaultMethodInvoker和PlainMethodInvoker。这两个类都实现了MapperMethodInvoker的invoke方法,那么如何调用呢?代码写的很清楚了,如果方法是默认方法,那就调用DefaultMethodInvoker的invoke方法,否则如果不是默认默认方法,那就调用PlainMethodInvoker实现的invoke方法。读到这里有些同学就有疑问了,上面是默认方法呢?这个是jdk中的一个方法,我们来看一下jdk的解释,如下图所示。我用谷歌翻译翻了一下,意思是:默认方法是公共非抽象实例方法,即是在接口中声明的带有主体的非静态方法类型。也就是说,默认方法就是我们说的成员方法,在Mapper中定义的抽象方法不属于默认方法,那自然就会调用PlainMethodInvoker实现的invoke方法然后在PlainMethodInvoker实现的invoke方法中调用了MapperMethod的execute方法执行SQL语句.如下图所示,我们可以看到它首先判断了一下SQL语句的类型,让后按照不同类型的sql语句执行不同的代码。方法还是很多的,我们不需要全看,看一个常用的查询返回多条记录的方法executorForMany,如下图所示。MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中,我们不需要全部明白。我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象SQL。至此,相信大家已经了解了MyBatis为什么只用Mappper接口便能够运行SQL,因为映射器的XML文件的命名空间对应的便是这个接口的全路径,那么它根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后还是使用SqlSession接口的方法使得它能够执行查询,有了这层封装我们便可以使用接口编程,这样编程就更简单了。3、SqlSession四大对象现在我们已经知道了映射器其实就是一个动态代理对象,进入到了MapperMethod的execute方法。它经过简单判断就进入了SqlSession的删除、更新、插入、查询等方法,那么这些方法如何执行呢?这是我们需要关心的问题,也是正确编写插件的根本。显然通过类名和方法名字就可以匹配到我们配置的SQL,我们不需要去关心这些细节,我们关心的是设计架构。Mapper执行的过程是通过Excutor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的。Executor表示执行器,由他调度StatementHandler、ParameterHandler和ResultHandler等来执行SQL语句StatementHandler使用JDBC的Statement或PreparedStatement来执行操作,他是四大对象的核心,器承上启下的作用。ParameterHandler用于SQL参数的处理。ResultHandler对JDBC的ResultSet进行封装处理并返回。了解到这里我们在看一下MyBatis处理流程图,如下图所示。构建SqlSession的过程在MyBatis源码学习—SqlSessionFactory的构建过程我们已经学习了,创建SqlSession的流程在本节也进行了学习。下面我们将逐一分析学习剩下的四个对象的生成和运作原理。到这里我们已经来到了MyBatis的底层设计,对Java语言基础不牢的同学来说,这将是一次挑战。详细的内容可以参考我的博文:MyBatis源码学习—MyBatis执行器Executor详解MyBatis源码学习—MyBatis数据仓库会话器StatementHandler详解MyBatis源码学习—MyBatis参数处理器ParameterHandler详解MyBatis源码学习—MyBatis结果处理器ResultHandler详解
LoveIT 2020-07-04MyBatis -
MyBatis源码学习—SqlSessionFactory的构建过程
MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory的创建比较容易理解,而SqlSession的执行过程远远不是那么简单了,本节我们解先来学习一下MyBatis是如何读取配置文件生成Configuration,并最终生成SqlSession的。 SqISessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心接口SqlSession,所以我们需要先创建SqlSessionFactory,为此我们需要提供配置文件和相关的参数。而MyBatis是一一个复杂的系统,采用建造者模式去创建SqlSessionFactory,我们可以通过SqlSessionFactoryBuilder去构建。构建分为两步。第一步,通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个orgapache.ibatis.session.Configuration类中。注意,MyBatis几乎所有的配置都是存在Configuration里的。第二步,使用Confinguration对象去创建SqlSessionFactory。MyBatis中的SqlSessionFactory是一个接口,而不是实现类,为此MyBatis提供了一个默认的SqlSessionFactory实现类,我们一般都会使用它org.apache.iatis.session.defaults.DefaultSqlSessionFactory。注意,在大部分情况下我们都没有必要自己去创建新的SqlSessionFactory的实现类。 特别需要注意的是,在构建SqlSessionFctory的过程中,MyBatis使用Builder设计模式,采用这种方式可以构建复杂的对象而且条理还很清晰,这一点是我们需要在源码中首先学习到的一点知识。接下来,详细的说明一下构建过程的详细过程。1、构建Configuration 在SqlSessionFactory的构建过程中,Configuration的生成是接力比赛的“第一棒”,他能否成功生成直接决定了接下来流程是否可以继续执行。我们来看一段代码: 程序启动执行,首先会由MyBatis的io包下的Resources类到指定的位置加载全局配置文件,方法最终调到了JDK的ClassLoader这个类的同名方法,方法内部核心的实现就一句话URLurl=getResource(name);。最终返回的类型是BufferedInputStream,这个也是Java的io包下的InputStream实现。 成功读取配置文件后就可以拿着输入流对象到SqlSessionFactoryBuilder中构建SqlSessionFactory了,继续跟踪源码,我们看看MyBatis是如何做的~~ 跟随着DEBUG,我们来到了SqlSessionFactoryBuilder内部,来到源码的第68行,可以看到MyBatis使用了一个叫XMLConfigBuilder的类来解析我们输入流中的东西。 继续追进去,我们看到它在构造方法中使用XMLPathParser来最终处理了我们的输入流,这个XMLPathParser是MyBatis的parsing包下的一个用于解析XML文件的一个类,它内部还是使用了Jdk提供的一个类XPath来解析XML文档,经过他解析XML成为一个DOM树(Document对象,也是JDK提供的)。拿到解析出来的对象,XMLConfigParser就会进入下图所示的另一个构造方法中执行Configuration对象的生成。 此时我们已经得到了XMLConfigBuilder对象,再看SqlSessionFactoryBuilder的build方法,将XMLConfigBuilder实例对象parser调用parser()方法得到的Configuration实例对象config作为参数,调用SqlSessionFactory接口的实现类DefaultSqlSessionFactory构造出SqlSessionFactory对象。 XMLConfigBuilder对象在调用parser()方法时,会读出所有所有配置文件,将配置文件解析后保存在Configuration对象中。(代码看起来很长,其实原理很简单,就是对那颗DOM树进行遍历)XMLConfigBuilder的parseConfiguration(XNode)方法把XML全局配置文件中每一个节点的信息都读取出来,保存在一个Configuration对象中,Configuration分别对以下内容做出了初始化:properties全局参数。settings设置。typeAliases别名。typeHandler类型处理器。ObjectFactory对象。plugin插件。environment环境。DatabaseIdProvider数据库标识。Mapper映射器。2、构建Mapper映射器 Mapper映射器是Configuration初始化组件的最后一道工序了,它就是加载我们sql映射文件或者是Mapper接口的地方,来,我们看看MyBatis为我们做了什么,如下图所示。 初始化Mapper的逻辑也不难理解:由parseConfiguration传入mappers结点,之后在mapperElement方法中遍历mappers结点下的所有子节点:如果遍历到package子节点,是以包名引入映射器,则将该包下所有Class注册到Configuration的MapperRegistry中。如果遍历到mapper子节点的class属性,则将指定的接口注册到Configuration的MapperRegistry中。如果遍历到resource或者url子节点,麻烦一点,需要借助XMLMapperBuilder来解析SQL映射文件: Mapper映射问文件最终还是通过JDk的XPathParser解析成一颗DOM树,在构造方法中完成初始化之后将调用XMLMapperBuilder的parse方法遍历这颗DOM树获取并解析其中的SQL语句。 在解析映射文件的时候首先它判断在当前configuration对象中是否已经有此资源了,这个在Configuration是叫一个Set类型的loadedResource变量控制的,这样设计是为了防止资源重复加载。当然如果没有加载,那就要执行加载操作了,进入到configurationElement方法中,如下图所示。 源码的逻辑也是不难的,首先获取namespace结点的值,这个namespace一般就是和这个Mapper相关联的那个Mapper接口,如果它没有的话,那就会在这里直接报错。否则namespace属性保存在XMLMapperBuilder的MapperBuilderAssistant对象中,以便其他方法调用。 之后方法对mapper映射文件每个标签逐一解析并保存到Configuration和MapperBuilderAssistant对象中,最后调用buildStatementFromContext方法解析select、insert、update和delete节点。 最终的一切都还是落到了XMLStatementBuilder类的parseStatementNode方法上,这个方法完成对每条SQL语句的解析并且把每一套SQL语句封装到一个SqlSource对象,SqlSource是个接口,如果是动态SQL就创建DynamicSqlSource实现类,否则普通SQL创建StaticSqlSource实现类。如下图。 这个方法就是把一条SQL语句色剂的所有可能的属性全部解析出来,并且如上面所说,它把每条SQL语句进行了封装用SqlSource来表示一条Sql语句,最后把这条Sql语句通过MapperBuilderAssistant的addMappedStatement方法将MappedStatement对象放入Configuration对象的mappedStatements容器中,得到了最终的Configuration对象后传入SqlSessionFactoryBuilder的另一个build方法中,生成我们需要的DefaultSqlSessionFactory对象。 这里只分析了MyBatis是如何解析resource和url方式指定的mapper配置文件,如果是通过指定Mapper接口的package或者class全限定名配置方式,则Configuration对象会通过addMappers方法将接口注册,再通过Java反射技术和JDK动态代理技术,根据接口class的全限定名找到对应的XML配置文件或者注解进行解析,如果是非注解模式的xml配置文件必须和这个class在同一级目录,且与class同名,这里不再详解。最后一图总结SqlSessionFactory的构建流程: 流程还是比较简单的,就是围绕着SqlSessionFactoryBuilder类该加载配置加载配置,该解析解析,最后得到Configuration对象返回给SqlSessionFactoryBuilder,之后SqlSessionFactoryBuilder拿着Configuration对象new了一个SqlSessionFactory的默认实现类DefaultSqlSessionFactory最后返回该对象。
LoveIT 2020-07-02MyBatis -
MyBatis架构设计及源代码分析(一):MyBatis架构
一、概述MyBatis是一个轻量级的ORM框架,其官方首页是这么介绍自己。MyBatis数据映射器框架使将关系数据库与面向对象的应用程序结合使用变得更加容易。MyBatis使用XML描述符或注释将对象与存储过程或SQL语句耦合。相对于对象关系映射工具,简单性是MyBatis数据映射器的最大优势。而在其官方文档中介绍“WhatisMyBaits”中说到MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和JavaPOJO(PlainOldJavaObjects,普通老式Java对象)为数据库中的记录。二、整体架构从功能流程层次描述MyBatis的整体架构图如下图所示:1、接口层我们知道,在不考虑与Spring集成的情况下,使用MyBatis执行数据库操作的代码如下:上面的代码中就使用了接口层的两个核心接口:SqlSessionFactory和SqlSession,其中SqlSessionFactory用于构建一个SqlSession(openSession()方法)以及生成MyBatis获取(getConfiguration()方法);SqlSession接口中定义了MyBatis暴露给应用程序调用CRUD的API,也就是上层应用与MyBatis交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。这两个类都在MyBatis的session包下,这个包的主体类结构图如下:两个核心接口:SqlsessionFactory和SqlSession。Configuration是MyBatis的配置信息存放的地方,在Configuration类中还引用了一个Environment对象,Environment中可以配置数据源和事物管理。由上图可以看到,Configuration对象与DefaultSqlSessionFactory是1:1的关联关系,这也就意味着在一个DefaultSqlSessionFactory衍生出来的所有SqlSession作用域里,Configuration对象是全局唯一的。同时SqlSessionFactory提供了getConfiguration()接口来公开Configuration对象,因此开发者除了配置文件之外,还可以在程序里动态更改Configuration的属性项以达到动态调整的目的,但此时不仅要考虑到执行完reset,同时还要考虑在修改过程中会可能影响到其他SqlSession的执行。2、核心层(1)配置解析在MyBatis初始化过程中,会加载mybatis-config.xml配置文件、映射配置文件以及Mapper接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。之后,利用该Configuration对象创建SqlSessionFactory对象。待MyBatis初始化之后,开发人员可以通过初始化得到Sq!SessionFactory创建SqlSession对象并完成数据库操作。1)全局配置文件mybatis-config.xml是在XMLConfigBuilder中完成解析的,有关类图大致如下:xml解析常见的方式有三种:DOM(DocumentObjectModel)、SAX(SimpleAPIforXML)、StAX(StreamingAPIforXML)MyBatis在初始化过程中处理mybatisConfig.xml配置文件及映射配置文件时,使用的是DOM解析方式,并结合使用XPath(JDK提供的一个解析xml的类)解析XML配置文件。DOM是基于树形结构的XML解析方式,它会将整个XML文档读入内存并构建一个DOM树,基于这棵树形结构对各个节点(XNode)进行操作。如下图所示:2)SQL映射文件是在XMLMapperBuilder这个类中完成解析的,其中把对Statement的解析(即XxxMapper.xml中SELECT|INSERT|UPDATE|DELETE定义部分)委托给XMLStatementBuilder来完成。XxxMapper.xml的解析比较复杂的,涉及到PreparedMapping、ResultMapping、LanguageDriver、Discriminator、缓存、自动映射等一系列对象的构造。(2)SQL执行SQL语句的执行涉及多个组件,其中比较重要的是Executor、StatementHandler、ParameterHandler和ResultSetHandler。1)Executor主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler完成。下面是Executor的类图从上图中可以看到,Executor主要提供了一下功能:CRUD接口,从方法定义中可看到,它需要MappedStatement、parameter、resultHandler这几个实例对象,这几个也是SQL执行的主要部分,详细实现在后面专题中再介绍。事务提交/回滚(commit()/rollback()),这委托给Transaction对象来完成缓存,createCacheKey()/isCached()延迟加载,deferload()关闭(close()),主要是事务回滚/关闭①BaseExecutor:它内部维护了localCache来localOutputParameterCache来处理缓存,以及线程安全的延迟加载列表deferredLoads、事务对象Transaction。②BatchExecutor:它内部维护了statementList批量提交并通过batchResultList保存执行结果③ResueExecutor:它内部维护了java.sql.Statement对象缓存,以重用Statement对象(对于支持预编译的数据库而言,在创建PreparedStatement时需要发送一次数据库请求预编译,而重用Statement对象主要是减少了这次预编译的网路开销)。2)**StatementHandler**首先通过**ParameterHandler完成SQL语句的实参绑定。然后通过java.sql.Statement对象执行SQL语句并得到结果集ResultSet。最后通过ResultSetHandler**完成结果集的映射,得到结果对象并返回。下图所示就是一条SQL语句在MyBatis中的执行流程:3)SOL解析与scripting模块:MyBatis有动态SQL语句的功能,提供了多种动态SQL语句对应的节点,例如,<where>节点、<if>节点、<foreach>节点等。通过这些节点的组合使用,开发人员可以写出几乎满足所有需求的动态SQL语句。MyBatis中的scripting模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL语句。之后会处理SQL语句中的占位符,绑定用户传入的实参。3、基础层3.1、logging:MyBatis使用了自己定义的一套logging接口,根据开发者常使用的日志框架——Log4j、Log4j2、ApacheCommonsLog、java.util.logging、slf4j、stdout(控制台)——分别提供了适配器。由于各日志框架的Log级别分类法有所不同(比如java.util.logging.Level提供的是All、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SEVERE、OFF这九个级别,与通常的日志框架分类法不太一样),MyBatis统一提供trace、debug、warn、error四个级别,这基本与主流框架分类法是一致的(相比而言缺少Info,也许MyBatis认为自己的日志要么是debug需要的,要么就至少是warn,没有Info的必要)。在org.apache.ibatis.logging里还有个比较特殊的包jdbc,这不是按字面意义理解把日志通过jdbc记录到数据库里,而是将jdbc操作以开发者配置的日志框架打印出来,这也就是我们在开发阶段常用的跟踪SQL语句、传入参数、影响行数这些重要的调试信息。3.2、IOMyBatis里的IO主要是包含两大功能:提供读取资源文件的API(我们写代码常用的Resource类就在这个包下)、封装MyBatis自身所需要的ClassLoader和加载顺序。3.3、reflection在MyBatis如参数处理、结果映射这些大量地使用了反射,需要频繁地读取Class元数据、反射调用get/set,因此MyBatis提供了org.apache.ibatis.reflection对常见的反射操作进一步封装,以提供更简洁方便的API。比如我们reflect时总是要处理异常(IllegalAccessException、NoSuchMethodException),MyBatis统一处理为自定义的RuntimeException,减少代码量。3.4、exceptions在以Spring为代表的开源框架中,对于应用程序中无法进一步处理的异常大都转成RuntimeException来方便调用者操作,另外如频繁遇到的SQLException,JDK约定其是个Exception,从JDK的角度考虑,强制要求开发者捕获SQLException是为了能在catch/finally中关闭数据库连接,而Spring之类的框架为开发者做了资源管理的事情,自然就不需要开发者再烦心SQLException,因此封装转换成RuntimeException。MyBatis的异常体系不复杂,org.apache.ibatis.exceptions下就几个类,主要被使用的是PersistenceException。3.5、缓存缓存是MyBatis里比较重要的部分,MyBatis提供两种级别的缓存:SESSION/一级缓存,默认生命周期是一次SESSION,BaseExecutor中根据MappedStatement的Id、SQL、参数值以及rowBound(边界)来构造CacheKey,并使用BaseExccutor中的localCache来维护此缓存。全局的二级缓存,通过CachingExecutor来实现,其委托TransactionalCacheManager来保存/获取缓存,这个全局二级缓存比较复杂,详细的内容大家可以参考我的另一篇博文深入理解MyBatis缓存机制。3.6、数据源/连接池MyBatis自身提供了一个简易的数据源/连接池,在org.apache.ibatis.datasource下。主要实现类是PooledDataSource,包含了最大活动连接数、最大空闲连接数、最长取出时间(避免某个线程过度占用)、连接不够时的等待时间,虽然简单,却也体现了连接池的一般原理。阿里有个“druid”项目,据他们说比proxool、c3p0的效率还要高,可以学习一下。3.7事务MyBatis对事务的处理相对简单,TransactionIsolationLevel中定义了几种隔离级别,并不支持内嵌事务这样较复杂的场景,同时由于其是持久层的缘故,所以真正在应用开发中会委托Spring来处理事务实现真正的与开发者隔离。分析事务的实现是个入口,借此可以了解不扫JDBC规范方面的事情。3.8Binding模块MyBatis通过绑定模块将用户自定义的SQL映射文件和混和Mapper接口关联起来了,系统可以通过调用自定义Mapper接口中的方法执行相应的SQL语句完成数据库操作。开发人员无须编写自定义Mapper接口的实现,MyBatis会通过动态代理自动为Mapper解口创建实现类实例。参考资料[1]https://blog.csdn.net/tiankong_12345/article/details/90813101#%E4%BA%8C%E3%80%81%E6%A0%B8%E5%BF%83%E5%A4%84%E7%90%86%E5%B1%82[2]https://www.cnblogs.com/mengheng/p/3739610.html[3]https://mybatis.org/mybatis-3/zh/configuration.html
LoveIT 2020-06-24MyBatis -
MyBatis分页插件pageHelper配置和使用
1、PageHelper简介这是一个基于MyBatis开源的分页插件,使用非常方便,支持各种复杂的单表、多表分页查询,让你在写sql时无需考虑分页问题,PageHelper帮你搞定。项目托管在github上https://github.com/pagehelper/Mybatis-PageHelper。2、在项目中引入PageHelperPageHelper是一个通用的MyBatis分页插件,在使用的时候除了要导入MyBatis和数据库驱动的jar包外,还要导入PageHelper的依赖包。3、分页插件的配置配置PageHepler插件对方法有很多种,这里我有最基础的一种方法,使用mybatis-config.xml全局配置文件来配置。配置也很简单,只需要在mybatis-config.xml添加如下配置:配置参数详解:helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby特别注意:使用SqlServer2012数据库时,需要手动指定为sqlserver2012,否则会使用SqlServer2005的方式进行分页。也可以实现AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。offsetAsPageNum:默认值为false,该参数对使用RowBounds作为分页参数时有效。当该参数设置为true时,会将RowBounds中的offset参数当成pageNum使用,可以用页码和页面大小两个参数进行分页。rowBoundsWithCount:默认值为false,该参数对使用RowBounds作为分页参数时有效。当该参数设置为true时,使用RowBounds分页会进行count查询。pageSizeZero:默认值为false,当该参数设置为true时,如果pageSize=0或者RowBounds.limit=0就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是Page类型)。reasonable:分页合理化参数,默认值为false。当该参数设置为true时,pageNum<=0时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。默认false时,直接根据参数进行查询。params:为了支持startPage(Objectparams)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值,可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值,默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。supportMethodsArguments:支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面params配置的字段中取值,查找到合适的值时就会自动分页。使用方法可以参考测试代码中的com.github.pagehelper.test.basic包下的ArgumentsMapTest和ArgumentsObjTest。autoRuntimeDialect:默认值为false。设置为true时,允许在运行时根据多数据源自动识别对应方言的分页(不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五。closeConn:默认值为true。当使用运行时动态数据源或没有设置helperDialect属性自动获取数据库类型时,会自动获取一个数据库连接,通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为false后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。4、在代码中使用PageHelper完成上面的操作后对分页插件的配置就完成了,使用方法也很简单:使用PageHelper.statPage静态方法来分页,使用PageInfo来获取分页信息注意: 1.PageHelper.startPage(pageNum,pageSize);语句之后,第一个SQL语句,必须为要进行分页的(全)查询语句。追注:如果在PageHelper.startPage()方法之后,先执行了其他的sql语句,然后再执行的要分页的查询的话;那么分页会失效。 2.由于分页插件是通过拦截器,在原有SQL上进行追加约束条件,所以使用分页插件时,应注意:保证原有SQL不会受后面追加的条件的影响。给出一个反例:原有SQL中使用变量计算排名时,如果在后面追加了LIMIT的话,那么排名就会受到影响,因为SELECT的优先级在LIMIT之后。 3.PageInfo是比Page信息更丰富的一个类;我们可以直接返回Page,也可以使用PageInfo包装一下返回PageInfo,甚至也可以自定义一个类来存放结果信息(只需将返回的Page中的信息取出来,再setter放入我们自己的类即可)。5、PageHelper常用的APIPageMethod的APIPageHelper继承了抽象类PageMethod,而使用PageHelper进行分页操作的方法实际是在PageMethod中的方法,具体如下:PageInfo中的成员变量每一个成员变量都有对应的get和set方法,使用这些方法可以获得分页的任何信息,这个类十分强大追加:现在SpringBoot已经是Java开发中非常流行的一个框架了,如果我们需要在基于SpringBoot开发的项目中使用pageHelper,只需要更换pageHelper的pom依赖,然后我们无需在写繁杂的xml配置文件了,只需要在SpringBoot的配置文件中写如下配置即可。(1)引入pageHelper依赖(2)编写配置
LoveIT 2019-07-30MyBatis -
MyBatis动态SQL
MyBatis的强大特性之一便是它的动态SQL。如果你有使用JDBC或其他类似框架的经验,你就能体会到根据不同条件拼接SQL语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态SQL这一特性可以彻底摆脱这种痛苦。MyBatis动态SQL元素和使用JSTL或其他类似基于XML的文本处理器相似。在MyBatis之前的版本中,有很多的元素需要来了解。MyBatis3大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis采用功能强大的基于OGNL的表达式来消除其他元素。主要的有以下几个:if可以筛选一到多条SQL分支where一般配合if使用,作用相当于SQL语句中的where关键字choose、when、otherwise筛选一条SQL分支trim类似于replace效果,可以完成set或者是where标记的功能set相当于SQL中的set关键字foreach遍历集合一、if元素使用if可以用条件的筛选SQL语句分支,只有条件满足的时候才会执行。实例:这条sql语句就会动态的根据传入的参数值来查询,比如当只传了empAge=23,其他都为空,那么生成的sql语句就是:select*fromemployeewhereemp_age=23。其实管使用if标签这么写是有问题的,下面有具体分析以及改进方法。二、choose、when、otherwise元素MyBatis提供了choose元素,它有点像Java中的switch语句,即在choose中的SQL分支是由一条会被执行。如下:测试代码:可以看到,即使在条件中给了两个不为空的,但是由于EmpId在最前面,因此首先匹配上后就不在往下找了,和switch很像。最终的运行结果:三、trim,where元素看看下面的这条这种情况(一中的例子):如果三个分之没有一条分支匹配上,那么最终的sql语句会变成:这显然是会导致查询失败的。如果仅仅第二条或第三条匹配,那么sql语句会变成这样:不论是那种情况,都会导致由于sql语句错误而查询失败。MyBatis有一个简单的处理,这在大多数情况下都会有用——那就是使用where元素,where元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where元素也知道如何将他们去除。例如:这样即使三条语句都不满足条件,那么最终的sql语句是:select*fromemployee,也不会影响正常的查询。如果where元素没有按正常套路出牌,我们还是可以通过自定义trim元素来定制我们想要的功能。比如,和where元素等价的自定义trim元素为:prefixOverrides属性会忽略通过管道分隔的文本序列(注意此例中的空格也是必要的)。它带来的结果就是所有在prefixOverrides属性中指定的内容将被移除,并且插入prefix属性中指定的内容。类似的用于动态更新语句的解决方案叫做set。set元素可以被用于动态包含需要更新的列,而舍去其他的.trim标签的全部属性prefix:前缀增加的内容suffix:后缀增加的内容prefixOverrides:前缀覆盖第一个判断的条件suffixOverrides:后缀覆盖最后一个判断的条件四、set元素MyBatis中用于动态更新语句的解决方案叫做set。set元素可以被用于动态包含需要更新的列,而舍去其他的.示例:测试代码:set元素会动态前置SET关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号五、foreach元素动态SQL的另外一个常用的必要操作是需要各种集合集合进行遍历,通常是在构建各种条件语句以及批量插入数据的时候的时候使用较多。foreach元素有许多属性,在使用之前先了解一下foreach元素的属性:属性作用collectioncollection用于遍历的集合的名字,MyBatis支持任何可迭代对象的迭代,常见的比如List、Set、Map对象或者数组等item表示本次迭代获取到的元素index遍历过程的索引值。open前缀close后缀separator分隔符,表示迭代时每个元素之间以什么分隔注意!当使用Map对象(或者Map.Entry对象的集合)迭代时,index是键,item是值1、foreach遍历传递进来的集合,构建条件有时候我们可能会有下面的需求,根据多个id查询对应的信息,这多个id的数量是不固定的。这时候我们可以通过使用foreach标签来遍历集合中的参数,完成多个id之间的拼接。Mapper接口:SQL映射文件:2、使用foreach元素批量插入数据当需要插入的数据比较多的时候,此时再一条一条的插入数据就显得比较慢了,MySQL提供了一次插入多条数据的语法insertintotableName(......)values(......)[,(......),(......)],使用这条语句可以一次插入多条数据Mapper接口:SQL映射文件:总结这篇博文主要对常用的MyBatis动态SQL元素进行了介绍与其使用场景的应用,MyBatis还提供了其他的元素来支持动态SQL,熟练使用这些元素,才能在开发的时候写出简洁高效的SQL语句,不常用的元素信息可以到官方文档进行深入了解,希望这篇博文能够为你提供一些帮助。MyBatis动态SQL官方文档链接:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
LoveIT 2019-07-29MyBatis -
MyBatis SQL映射文件配置详解
配置SQL映射文件MyBatis中的SQL映射文件只有很少的几个顶级元素(按照它们应该被定义的顺序如下):cache–给定命名空间的缓存配置。cache-ref–其他命名空间缓存配置的引用。resultMap–是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。sql–可被其他语句引用的可重用语句块。insert–映射插入语句update–映射更新语句delete–映射删除语句select–映射查询语句1、select元素select元素就是用来查询的,在select里嵌入SQLselect查询语句,就像下边这样:其中select元素中的id属性是必须的,它的值是对应Mapper接口中的一个方法,当调用这个接口就是调用这个sql。关于select元素常用的属性具体如下:属性描述id在命名空间中唯一的标识符,可以被用来引用这条语句。这个是必须的属性parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为unset。resultType返回的期望类型的类的完全限定名或别名。这个属性是可选的resultMap返回值类型是是个map集合,可用于多表联查后的结果MyBatis会封装成一个map返回,这个属性是可选的flushCache将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。这个属性是可选的useCache将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对select元素为true。这个属性是可选的timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset(依赖驱动)。这个属性是可选的2、insertupdatedelete insertupdatedelete元素分别对应SQL语句中的insert、update、delete,分别实现对数据库记录的插入、更新和删除。他们可以有的属性值如下:属性描述id在命名空间中唯一的标识符,可以被用来引用这条语句。这个是必须的属性parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为unset。useGeneratedKeys(仅对insert和update有用)这会令MyBatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键,默认值为false,这个属性是可选的。keyProperty指定实体类中的主键属性,MyBatis会通过getGeneratedKeys的返回值或者通过insert语句的selectKey子元素设置它的键值,他和useGeneratedKeys配合起来才能工作flushCache将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true。这个属性是可选的useCache将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对增删改元素为false。这个属性是可选的timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset(依赖驱动)。这个属性是可选的keyColumn指定数据表中的主键字段名,这个设置仅在某些数据库(像PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。3、resultMapresultMap元素是MyBatis中最重要最强大的元素。ResultMap的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。3.1简单映射例如下面这个例子::JavaBean是这样的:Mybatis会将基于JavaBean的规范,这些在select语句中会精确匹配到列名。这样一个语句简单作用于所有列被自动映射到HashMap的键上,这由resultType属性指定。也就是说,对于resultTypeMyBatis会结果封装一个map返回。3.2高级映射有时候我们避免不了多表联查,这样带来的问题是返回的结果类型中的一个字段在resultType中的不存在,这就会造成问题。MyBatis中使用resultMap来解决这个问题。resultMap元素有很多子元素和一个值得讨论的结构。下面是resultMap标签中可以使用的属性如下:resultMap:constructor-类在实例化时,用来注入结果到构造方法中idArg-ID参数;标记结果作为ID可以帮助提高整体效能arg-注入到构造方法的一个普通结果id–一个ID结果;标记结果作为ID可以帮助提高整体效能result–注入到字段或JavaBean属性的普通结果association–一个复杂的类型关联;许多结果将包成这种类型嵌入结果映射–结果映射自身的关联,或者参考一个collection–复杂类型的集嵌入结果映射–结果映射自身的集,或者参考一个discriminator–使用结果值来决定使用哪个结果映射case–基于某些值的结果映射嵌入结果映射–这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。描述描述id当前命名空间中的一个唯一标识,用于标识一个resultmap.type类的全限定名,或者一个类型别名autoMapping如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。下面是一个例子:学生实体:Student.java课程实体:Course.java测试类AppTest.java运行结果:cache和cache-ref的用法参考另一篇笔记MyBatis缓存配置4、cachecache标签是用于指定MyBatis的二级缓存的具体实现的。虽然MyBtis自带二级缓存的实现,但是MyBatis在缓存方面毕竟和专门的第三方缓存服务提供方还是有差距的,MyBatis官方也深知自己的不足,因此MyBatis提供了org.apache.ibatis.cache.Cache接口,任何只要实现了这个接口的缓存中间件都可成为MyBatis二级缓存,具体的用法请参考我的博客*MyBatis整合Redis作为二级缓存或MyBatis整合Encache作为二级缓存*。5、sql我们在写这个SQL映射文件的时候,有很多重复的SQL语句。将这些重复的SQL语句提取出来,称为SQL片段,给不同方法使用。把我们的SQL语句提取出来,用SQL标签包起来。然后再用include标签,导进语句中。像这些我们需要查询的字段,也可以这样子搞。
LoveIT 2019-07-12MyBatis -
MyBatis整合Redis作为二级缓存
Redis不像Ehcache一样提供了针对MyBatis的二级缓存的实现,因此需要我们自己来实现缓存的逻辑,但是归根到底原理是一样的,就是实现MyBatis的org.apache.ibatis.cache.Cache接口,在实现类中我们把数据的存取中间件变为了Redis而已,下面是一个实现的示例:之后在Mapper文件中指定缓存使用我们实现的这个缓存即可。
LoveIT 2019-07-11MyBatis -
MyBatis整合Ehcache作为二级缓存
0、Ehcache简介 Encache是一个纯粹的Java进程内的缓存框架,具有快速、精干等特点。具体来说,Encache主要特点如下。快速简单多种缓存策略(FIFO、LRU、LFU)缓存数据有内存和磁盘两级,无需担心容量问题缓存数据会在虚拟机重启的过程写入磁盘可以通过RMI、可插入API等方式进行分布式缓存具有缓存和缓存接口的侦听接口 因为以上诸多优点,MyBatis项目开发者最早提供了Ehcache的MyBatis二级缓存实现,该项目名Ehcache-cache,EhCache官方网址是http://www.mybatis.org/ehcache-cache/。下面,我来演示一下在一个标准的Maven项目中集成EhCache框架。1、添加Encache项目依赖除了基本的MyBatis依赖、数据库驱动以外还需要在pom.xml中添加如下依赖:####2、配置ehcache.xml2.1ehcache缓存配置文件编写 和MyBatis一样,EnCache也需要外部的配置文件,而且要求这个文件的名字必须是encache.xml,并且必须放在类路径的根目录下,即src/main/resources目录下 如果想增加一个针对某个MyBatisSQL映射文件的个性化缓存配置时,可以在ehcache.xml文件中添加一个和SQL映射文件命名空间一致的缓存配置,例如针对UserMapper,可以进行如下配置:2.2EhCache可配置信息总览nameCache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)。maxElementsInMemory在内存中缓存的element的最大数目maxElementsOnDisk在磁盘上缓存的element的最大数目,默认值为0,表示不限制。eternal设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。overflowToDisk如果内存中数据超过内存限制,是否要缓存到磁盘上。copyOnRead判断从缓存中读取数据是否是返回对象的引用还是赋值一个对象返回。默认是false,即返回数据的引用,这种情况和MyBatis默认的缓存中只读对象是相同的。如果为true,那就是可读写缓存,每次读取缓存是都赋值一个新的实例。copyOnWrite判断写入缓存时直接缓存对象的引用还是赋值一个对象后timeToIdleSeconds对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。timeToLiveSeconds对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。diskPersistent是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。diskExpiryThreadIntervalSeconds对象检测线程运行时间间隔。标识对象状态的线程多长时间运行一次。diskSpoolBufferSizeMBDiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。memoryStoreEvictionPolicy如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。缓存清空策略:1、FIFO,firstinfirstout(先进先出).2、LFU,LessFrequentlyUsed(最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit属性,hit值最小的将会被清出缓存。3、LRU,LeastRecentlyUsed(最近最少使用).(ehcache默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。3、修改MyBatisSQL映射文件Ehcache提供了如下两个可选的缓存实现:*org.mybatis.caches.ehcache.EhcacheCache*org.mybatis.caches.ehcache.LoggingEhcache 这两个缓存中,第二个是带日志的缓存,由于MyBatis初始化时,如果Cache不是继承自LoggingEhcache,MyBatis便会使用LoggingEhcache装饰代理缓存,所以上面两个缓存使用时并没有区别,都会输出命中率的日志。修改后的UserMapper.xml配置如下:在src/main/test目录下新建测试类AppTest.java测试是否用上了EhCache:运行结果截图:在配置的磁盘路径下确实有缓存文件:4、在SpringBoot2.x上整合ehcache作为MyBatis二级缓存 在SpringBoot上集成ehcache非常简单,其他的东西都一样,按照上面的方法配置即可,之后只需要在SpringBoot项目的配置文件中做如下配置:
LoveIT 2019-07-10MyBatis -
深入理解MyBatis缓存机制
使用缓存可以是应用更快的获取数据,避免频繁的数据库交互,尤其是在查询越多、缓存命中率越高的情况下,使用缓存的作用就越明显。MyBatis作为持久层框架,提供了强大的查询缓存特性,可非常方便的配置和使用。MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。1、默认情况下,一级缓存(SqlSession级别的缓存,也称为本地缓存)是开启的,并且不能控制。2、二级缓存需要手动开启和配置,他是和命名空间绑定的,即二级缓存需要在全局配置文件中开启,并且在sql映射文件中指定二级缓存使用的缓存中间件(MyBatis有自己的实现,但是毫不客气的说和专业的缓存中间件一比就是个弟弟!!!)3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存(或者使用第三方缓存)1、一级缓存先通过一个简单的演示看看MyBatis一级缓存是如何起作用的。运行结果截图如下: 可以看到,两次查询值MyBatis只给数据库发送了一次SQL语句,但是两次查询的结果都是一样的,而且再往下发现两个List<User>对象竟然是同一个对象,之所以这样就是MyBatis的一级缓存在起作用。 在同一个SqlSession中查询是MyBatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入PerpetualCache的HashMap本地缓存对象中。如果同一个SqlSession中执行的方法和参数完全一致,闹通过算法就会生成相同的键值,当Map缓存对象中已经存在该键值时,就会返回缓存中的对象,而不会再给数据库发sql语句了。但是要注意,下面几种情况发生会使一级缓存会失效:1.不同的SqlSession用不同的一级缓存,他们之间的数据不能共享2.就算是同一个SqlSession对象,但是如果执行的方法和参数不同也不行3.默认情况下,在SqlSession期间执行任何一次增删改操作,一级缓存会被清空4.手动清空一级缓存,一级缓存也会失效下面分别举例说明:由于MyBatis的一级缓存存在于SqlSession生命周期中,一次不同的SqlSession当然会有不同的一级缓存。测试结果如下: MyBatis的<insert>、<delete>、<update>和<select>标签都有一个属性:flushCache,在默认情况下,对于增删改操作这个标签默认值是true,也就是每次操作后要清空缓存,而对于<select>操作这个属性默认值是false,也即不刷新缓冲。也就是下面这段代码要说明的:测试结果如下:下面的这种情况就更直观了,但本质上和上面那种情况是一样的——都是一级缓存被清空了。测试结果如下:2、二级缓存二级缓存默认也是采用PerpetualCache,HashMap存储;二级缓存的存储作用域为Mapper(确切说是namespace),即一个Mapper执行了insert、update或delete操作,不影响另外一个Mapper(不同namespace);二级缓存可自定义存储实现,如Ehcache、redis;二级缓存开启后,需要对应的javaBean实现,并且这个javaBean要实现Serializable接口进行序列化2.1配置二级缓存 二级缓存有两种配置方法,一种是基于Mapper.xml文件来配置;另一种就是基于Mapper.java接口来配置。下面分别来看看如何配置使用MyBatis的二级缓存: 首先,无论是通过Mapper.xml文件来配置,还是通过Mapper.java接口来配置,都需要在mybatis-config.xml文件中通过settings设置显式地开启二级缓存:2.2在Mapper.xml中配置二级缓存 在xml文件中配置的方法很简单,在保证二级缓存的全局配置开启的情况下,在UserMapper.xml中只需要添加<cache></cache>即可。<cacha>标签的属性:*eviction:缓存回收策略:flushInterval:缓存多长时间清空一次,默认不清空,单位毫秒ms LRU——最少使用的,移除最长时间不适用的对象; FIFO——先进先出 WEAK——弱引用,更积极的移除基于垃圾回收器状态和弱引用规则的对象 SOFT——软引用,更积极的移除基于垃圾回收器状态和弱引用规则的对象*flushInterval:缓存多久清空一次,默认不清空,时间毫秒ms*readOnly:缓存是否只读*size:缓存觉存放元素个数,默认1024*type:自定义缓存的全类名2.3在Mapper接口中配置二级缓存 在接口中配置主要是借助@CacheNamespace这个注解,但是要注意:配置文件和接口注释是不能够配合使用的。只能通过全注解的方式或者全部通过xml配置文件的方式使用。也就是说你用了配置文件就不要用这种方式,用了接口配置的方式就别用xml配置文件的方式。如下:并在mybatis-config.xml中重新配置Mapper映射文件:4、使用自带的MyBatis二级缓存 配置Mybatis二级缓存的方法有两种,只要配置好,二级缓存就可以工作了。但是在使用前需要注意的是,由于MyBatis使用SerializedCache序列化缓存来实现可读写缓存类,并通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个新的实例。因此,如果配置了只读缓存,MyBatis就会使用Map来存储缓存值。而这个缓存类要求所有被序列化的对象必须实现Serializable接口。因此我们的javaBean需要实现Serializable接口。做好所有准备后,编写一个测试类来看看二级缓存的效果:测试结果:5、MyBatis缓存的执行逻辑1.当一个SqlSession第一次执行一次select后,查到数据后会首先把查询到的结果保存到一级缓存中2.当该SqlSession被关闭或者提交后,保存在一级缓存中的数据会转移到二级缓存中(前提是正确开启并配置了二级缓存)3.当另一个SqlSession第一次执行同样select时,首先会在二级缓存中找,如果没找到,就去自己的一级缓存中找,找到了就返回,如果没找到就去数据库查,MyBatis就是通过这样的机制从而减少了数据库压力提高了性能。MyBatis的执行流程总结起来就是:二级缓存-->一级缓存-->数据库注意事项:1.如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前mapper缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读2.mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是:SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key,查询返回的语句作为value保存的。
LoveIT 2019-07-09MyBatis -
深入理解Mybatis中的一对一、一对多映射关系
1、搭建实验环境1).新建数据库mybatis62).新建表:sys_user、sys_role、sys_user_role,sys_privilege、sys_role_privilegesql脚本如下:3).新建每个表对应的实体JavaBeanSysUser.javaSysRole.javaSysUserRole.javaSysPrivilege.javaSysRolePrivilege.java4).新建SysUserMapper接口5).新建mybaits配置文件2、一对一映射Mybatis中的映射方式有两种,一种是通过resultType自动映射,另一种是通过resultMap自己设置映射规则。resultMap又有两种映射方式:嵌套结果映射和嵌套查询映射。嵌套结果映射:给数据库发一条复杂的sql语句把查询到的结果根据映射规则映射到不同的对象中嵌套查询映射:会发多条sql简单的语句,Mybatis会把多条sql语句的查询据结果封装到一个对象中。如果在mybatis全局配置中设置了延迟加载:配置xml文件:通过resultMap一对一映射,在SysUser实体类中新增一个属性privateSysRolerole标识用户的角色,然后在SysUserMapper.xml中写如下映射userRoleMap:在SysUserMapper接口中增加方法:publicSysUserselectUserAndRoleById(Longid);测试方法:这是日志的打印结果:改进上面的映射方法:上面的映射方法虽然可以使用,但是耦合性太高,而且最大的问题是配置文件不能复用,啥意思?就是说,如果我现在需要一个单独查用户的方法,那我还得专门为查用户写一个sql配置,这样做非常的糟糕,当项目非常大的时候,配置文件的重复配置代码将会非常的多,那么如何解决这个问题呢?我们接着往下看:在SysUserMapper.xml中增加一个专门为查用户的映射userMap:然后刚才的上面的userRoleMap就可以修改成下面的样子:使用association元素替代上面的role.XXX:可是这样还是不行,实际的开发中,肯定会有关于单独查询sys_role的需求,而且人家sys_role肯定也会有单独的mapper,这样就又会存在重复配置的问题,解决这个问题需要用到association元素的另一个功能,具体看代码:新建SysRoleMapper.xml,并配置roleMap如下:这样我们在SysUserMapper.xml就可以把刚才的配置彻底抽取了出来:这样就彻底把模块与模块分开了,当然我们也可以顺便实现以下selectRoleById,下面是配置后的运行时打印的日志的部分:可以看到,日志的打印结果相同,但是修改后的方式肯定比一开始的方法要好,因为这样就把各个查询模块化了,就像搭积木,一个个简单的“积木块”最后通过合理的组织,就可以实现不同的复杂查询。一对一的嵌套查询映射上面这种方法是嵌套结果映射,就是直接给数据库发一条sql语句,数据库返回数据后Mybatis根据映射规则,把数据映射到不同的对象中。而嵌套查询映射则是多次给数据库发简单的sql语句,然后把不同的数据映射到一个对象中。1.association元素的嵌套查询: select:另一个映射查询map的id column:将主查询的那个列的结果作为嵌套查询的参数传给嵌套查询方法 fetchType:数据加载的方式[lazy或eager],即延迟加载或积极加载, 配置这个属性会覆盖全局配置中飞lazyLoadingEnabled2.MyBatis的嵌套查询可以实现懒加载,简单点的说就是不用的时候就不给你加载,等用的时候才去给你加载,这样做的好处是可以降低数据库的压力,做到按需响应。那么要用懒加载必须在全局配置文件中设置如下:<settings><settingname="aggressiveLazyLoading"value="false"/><!--vallue=false时按需加载-,否者全部加载-><settingname="lazyLoadingEnabled"value="true"/><!--是否开启懒加载,true表示开启--><settingname="lazyLoadTriggerMethods"value="equals,clone,hashCode,toString"/><!--懒加载模式下如果调用value后的方法将全部加载--></settings>在SysUserMapper.xml中写一个id为userRoleMapSelecct的新的映射关系,并写SQL查询语句如下:配置好后我们在SysUserMapper接口中增加selectUserAndRoleById3方法,然后写测试:日志的打印结果:可以看到,MyBatis分别给数据库发了两条sql语句,这是因为直接打印,在配置文件的setting中有一个元素lazyLoadTriggerMethods默认值value="equals,clone,hashCode,toString",当程序中调用这些方法的时就会全部加载。但是如果我们在程序中只是用到User的一些属性,那么Mybatis就只发查user的sql语句,把测试代码中的System.out.println(u);改成System.out.println(u.getUserName()+","+u.getUserEmail()+","+u.getCreateTime());再次运行打印的日志部分如下:可以看到,只发了一条sql语句。这就是MyBatis的延迟加载(懒加载),也就是说,当你没用到的时候,MyBatis压根不会帮你去查这个数据。这样一来的好处是会减轻数据库的压力。3、一对多映射使用collection实现一对多映射,collection的属性和用法与association基本是一样的,只是collection是专门用来映射数据库中一对多的多方元素的一个集合。比如现在有这样的需求:查询所有用户以及每个用户在本系统中所拥有的角色。这是一个很典型的一对多的例子,一个用户在系统中有多个角色。举个栗子:在SysUser.java中增加属性List<SysRole>roleList在SysUserMapper.xml中增加reultMapuserRoleListMap,由于roleMap在前面已经定义过了,这里就可以直接使用测试代码:程序运行打印的日志:前面这个实现了一层嵌套,就是一个主查询下面只有一个层子查询然后就结束了,下面我们尝试来实现一个两层嵌套:比如现在有这样的需求:查询所有的用户的角色,以及每个角色拥有的权限。很好想,就是一个用户可以有多个角色,每个角色又有不同的权限。实现:首先在SysRole中增加一个属性privateList<SysPrivilege>privilegeList新建SysPrivilegeMapper.xml文件,增加一个resultMapprivilegeMap在SysRoleMapper.xml中增加一个resultMaprolePrivilegeListMap,由于roleMap在上面已经定义过了,,用extends继承他就可以直接使用了。在SysUserMapper.xml中增加reusltMap:userRoleListMapSelect在SysUserMapper接口中增加方法selectAllUserAndRole2,并且编写测试代码:程寻运行打印的日志的一部分:这样,我们就算是实现了一对多的两层嵌套结果映射的一个查询,这种方式在日常非常常见,也是MyBatis中非常强大的地方。最后我们看一下一对多的嵌套查询映射,和一对一的实现方法是类似的:前面我们写过一个resultMaprolePrivilegeListMap,但是还没有为他写接口方法,这里我们首先来实现这个:在SysRoleMapper.xml增加一个resultMap:rolePrivilegeListMap2在SysRoleMapper接口中增加一个方法selectRoleByUserId最后在SysUserMapper.xml中增加一个resultMap:userRoleListMap2在SysUserMapper接口中增加一个方法selectAllUserAndRole2:测试代码:程寻运行打印的日志:可以看到,程序运行时,MyBatis给数据库发了多条sql语句,最终通过预定的映射集合,把这些查出来的数据放进去,之后打包组合成一个List<SysRole>对象返回。
LoveIT 2019-07-08MyBatis -
MyBatis-配置SQL映射文件
MyBatis中的SQL映射文件只有很少的几个顶级元素(按照它们应该被定义的顺序如下):cache–给定命名空间的缓存配置。cache-ref–其他命名空间缓存配置的引用。resultMap–是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。sql–可被其他语句引用的可重用语句块。insert–映射插入语句update–映射更新语句delete–映射删除语句select–映射查询语句1、select元素select元素就是用来查询的,在select里嵌入SQLselect查询语句,就像下边这样:其中select元素中的id属性是必须的,它的值是对应Mapper接口中的一个方法,当调用这个接口就是调用这个sql。关于select元素常用的属性具体如下:属性描述id在命名空间中唯一的标识符,可以被用来引用这条语句。这个是必须的属性parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为unset。resultType返回的期望类型的类的完全限定名或别名。这个属性是可选的resultMap返回值类型是是个map集合,可用于多表联查后的结果MyBatis会封装成一个map返回,这个属性是可选的flushCache将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。这个属性是可选的useCache将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对select元素为true。这个属性是可选的timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset(依赖驱动)。这个属性是可选的2、insertupdatedelete insertupdatedelete元素分别对应SQL语句中的insert、update、delete,分别实现对数据库记录的插入、更新和删除。他们可以有的属性值如下:属性描述id在命名空间中唯一的标识符,可以被用来引用这条语句。这个是必须的属性parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为MyBatis可以通过TypeHandler推断出具体传入语句的参数,默认值为unset。useGeneratedKeys(仅对insert和update有用)这会令MyBatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键,默认值为false,这个属性是可选的。keyProperty指定实体类中的主键属性,MyBatis会通过getGeneratedKeys的返回值或者通过insert语句的selectKey子元素设置它的键值,他和useGeneratedKeys配合起来才能工作flushCache将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true。这个属性是可选的useCache将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对增删改元素为false。这个属性是可选的timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为unset(依赖驱动)。这个属性是可选的keyColumn指定数据表中的主键字段名,这个设置仅在某些数据库(像PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。3、resultMapresultMap元素是MyBatis中最重要最强大的元素。ResultMap的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。3.1简单映射例如下面这个例子::JavaBean是这样的:Mybatis会将基于JavaBean的规范,这些在select语句中会精确匹配到列名。这样一个语句简单作用于所有列被自动映射到HashMap的键上,这由resultType属性指定。也就是说,对于resultTypeMyBatis会结果封装一个map返回。3.2高级映射有时候我们避免不了多表联查,这样带来的问题是返回的结果类型中的一个字段在resultType中的不存在,这就会造成问题。MyBatis中使用resultMap来解决这个问题。resultMap元素有很多子元素和一个值得讨论的结构。下面是resultMap标签中可以使用的属性如下:resultMap:constructor-类在实例化时,用来注入结果到构造方法中idArg-ID参数;标记结果作为ID可以帮助提高整体效能arg-注入到构造方法的一个普通结果id–一个ID结果;标记结果作为ID可以帮助提高整体效能result–注入到字段或JavaBean属性的普通结果association–一个复杂的类型关联;许多结果将包成这种类型嵌入结果映射–结果映射自身的关联,或者参考一个collection–复杂类型的集嵌入结果映射–结果映射自身的集,或者参考一个discriminator–使用结果值来决定使用哪个结果映射case–基于某些值的结果映射嵌入结果映射–这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。描述描述id当前命名空间中的一个唯一标识,用于标识一个resultmap.type类的全限定名,或者一个类型别名autoMapping如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。下面是一个例子:学生实体:Student.java课程实体:Course.java测试类AppTest.java运行结果:cache和cache-ref的用法参考另一篇笔记MyBatis缓存配置4、cachecache标签是用于指定MyBatis的二级缓存的具体实现的。虽然MyBtis自带二级缓存的实现,但是MyBatis在缓存方面毕竟和专门的第三方缓存服务提供方还是有差距的,MyBatis官方也深知自己的不足,因此MyBatis提供了org.apache.ibatis.cache.Cache接口,任何只要实现了这个接口的缓存中间件都可成为MyBatis二级缓存,具体的用法请参考我的博客MyBatis整合Redis作为二级缓存或MyBatis整合Encache作为二级缓存。5、sql我们在写这个SQL映射文件的时候,有很多重复的SQL语句。将这些重复的SQL语句提取出来,称为SQL片段,给不同方法使用。把我们的SQL语句提取出来,用SQL标签包起来。然后再用include标签,导进语句中。像这些我们需要查询的字段,也可以这样子搞。
LoveIT 2019-07-08MyBatis -
MyBatis全局配置文件详解
MyBatis使用过程中主要需要配置两个xml文件,一个是全局配置文件,另一个是SQL映射文件。本片博文我们就来学习一下MyBatis的全局文件的使用配置方式。在官方文档中也有详细的解释:MyBatis全局配置文件的官方文档。通过看文档和写代码来学习全局配置文件的使用,全局配置文件的配置主要有以下:这些子元素的配置是有顺序的,只能按照上面的顺序配置。可缺省部分子元素。1、propertiesproperties提供了一个通过外部配置文件(例如数据库配置文件)来动态配置环境的方法。以动态配置数据库为例:数据库配置文件jdbc.properties的详细配置如下:driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://127.0.0.1:3306/xust?autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&setglobaltime_zone='+8:00'username=rootpassword=95162437那么我们可以在mybatis-config.xml中这么写:2、settingssetting可以调整MyBatis的一些重要的配置,它们会改变MyBatis的运行时的行为,具体都有哪些配置可以设置可以参考官网,这里不再赘述,这里说几个常用的:设置名描述有效值默认值cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true|falsetruelazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true|falsefalseaggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载(参考lazyLoadTriggerMethods)。true|falsefalse(在3.4.1及之前的版本中默认为true)multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true|falsetrueuseColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true|falsetrueuseGeneratedKeys允许JDBC支持自动生成主键,需要数据库驱动支持。如果设置为true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如Derby)。true|falseFalseautoMappingBehavior指定MyBatis应如何自动映射列到字段或属性。NONE表示关闭自动映射;PARTIAL只会自动映射没有定义嵌套结果映射的字段。FULL会自动映射任何复杂的结果集(无论是否嵌套)。NONE,PARTIAL,FULLPARTIALautoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE:不做任何反应WARNING:输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日志等级必须设置为WARN)FAILING:映射失败(抛出SqlSessionException)NONE,WARNING,FAILINGNONEdefaultExecutorType配置默认的执行器。SIMPLE就是普通的执行器;REUSE执行器会重用预处理语句(PreparedStatement);BATCH执行器不仅重用语句还会执行批量更新。SIMPLEREUSEBATCHSIMPLEdefaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置(null)defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置(null)defaultResultSetType指定语句默认的滚动策略。(新增于3.5.2)FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE|DEFAULT(等同于未设置)未设置(null)safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。true|falseFalsesafeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为false。true|falseTruemapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名A_COLUMN映射到经典Java属性名aColumn。true|falseFalselocalCacheScopeMyBatis利用本地缓存机制(LocalCache)防止循环引用和加速重复的嵌套查询。默认值为SESSION,会缓存一个会话中执行的所有查询。若设置值为STATEMENT,本地缓存将仅用于执行语句,对相同SqlSession的不同查询将不会进行缓存。SESSION|STATEMENTSESSIONjdbcTypeForNull当没有为参数指定特定的JDBC类型时,空值的默认JDBC类型。某些数据库驱动需要指定列的JDBC类型,多数情况直接用一般类型即可,比如NULL、VARCHAR或OTHER。JdbcType常量,常用值:NULL、VARCHAR或OTHER。OTHERlazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toStringdefaultScriptingLanguage指定动态SQL生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriverdefaultEnumTypeHandler指定Enum使用的默认TypeHandler。(新增于3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandlercallSettersOnNulls指定当结果集中值为null的时候是否调用映射对象的setter(map对象时为put)方法,这在依赖于Map.keySet()或null值进行初始化时比较有用。注意基本类型(int、boolean等)是不能设置成null的。true|falsefalsereturnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回null。当开启这个设置时,MyBatis会返回一个空实例。请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于3.4.2)true|falsefalselogPrefix指定MyBatis增加到日志名称的前缀。任何字符串未设置logImpl指定MyBatis所用日志的具体实现,未指定时将自动查找。SLF4J|LOG4J|LOG4J2|JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING未设置proxyFactory指定Mybatis创建可延迟加载对象所用到的代理工具。CGLIB|JAVASSISTJAVASSIST(MyBatis3.3以上)vfsImpl指定VFS的实现自定义VFS的实现的类全限定名,以逗号分隔。未设置useActualParamName允许使用方法签名中的名称作为语句参数名称。为了使用该特性,你的项目必须采用Java8编译,并且加上-parameters选项。(新增于3.4.1)true|falsetrueconfigurationFactory指定一个提供Configuration实例的类。这个被返回的Configuration实例用来加载被反序列化对象的延迟加载属性值。这个类必须包含一个签名为staticConfigurationgetConfiguration()的方法。(新增于3.2.3)一个类型别名或完全限定类名。未设置shrinkWhitespacesInSqlRemovesextrawhitespacecharactersfromtheSQL.NotethatthisalsoaffectsliteralstringsinSQL.(Since3.5.5)true|falsefalseMybatis中可配置的settings如下:注意:settings、properties这些标签配置的位置不是随便的,他们的位置必须放在environment标签之前!3、typeAliase类型别名是为Java类型设置一个短的名字。它只和XML配置有关,存在的意义仅在于用来减少类完全限定名的冗余,即人好记,好写。3.1给单独的一个类重命名这样做后,以后在程序中任何使用com.xzy.bean.Employee的地方都可以用Employee来代替,例如:在mybatis-config.xml中添加如下配置:那么,在EmpoyeeMapper.xml中可以这么用:3.2给整个包重命名使用package元素:<typeAliases><packagename="com.xzy.bean"/></typeAliases> 这样一来每一个在包com.xzy.bean中的JavaBean,在没有注解的情况下,会使用类名的首字母小写来作为它的别名。如果使用了注解,那就以注解为他的别名。3.3@Alias(name)注解给一个JavaBean重命名其中name就是你给这个JavaBean起的别名4、TypeHandler—类型处理器无论是MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。Mytais给了很多的类型处理器来实现java类型到数据库类型的转换,但是就是这样内置的类型处理器还是无法完全胜任,因此MyBatis允许我们重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现org.apache.ibatis.type.TypeHandler接口,或继承类org.apache.ibatis.type.BaseTypeHandler,然后可以选择性地将它映射到一个JDBC类型。比如,有一个Phone类型:在另一个javaBean中使用他:自定义类型处理器自定义一个类型处理器,让他实现Typehandler接口,这个接口有一个泛型,这个参数就是处理后需要的java中对应对类型。在mybatis-config.xml中注册这个类型处理器注册的方式有两种,一种是使用<typeHandlerhandler=""/>这个标签,在handler属性中执行处理器的全类名;另一种方式是直接使用<packgename=""/>标签在name属性中指定包名,然后整个包下的处理器都会被自动注册。具体如下:5、配置环境(environments)MyBatis可以配置成适应多种环境,这种机制有助于将SQL映射应用于多种数据库之中,现实情况下有多种理由需要这么做。不过要记住:尽管可以配置多个环境,但是每个SqlSessionFactory实例只能选择其一。环境配置实例:5.1事务管理器(transactionManager)在MyBatis中有两种类型的事务管理器(也就是type=”[JDBC|MANAGED]”):JDBC–这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。MANAGED–这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期5.1数据源(dataSource)dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。有三种内建的数据源类型(也就是type=”[UNPOOLED|POOLED|JNDI]”):UNPOOLED–这个数据源的实现只是每次被请求时打开和关闭连接。这就是每次操作就要和数据库发生物理连接,数据会慢很多。这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种方式每次需要和数据可连接的时候就直接去连接池中去拿。这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。通过需要实现接口org.apache.ibatis.datasource.DataSourceFactory,也可使用任何第三方数据源:6、映射器(mappers)mappers的作用就是告诉mybatis去哪里找SQL语句,因此对他的配置是至关重要。mybatis提供了如下4种配置的方法:6.1使用resource配置6.2使用文件路径配置6.3使用class属性配置接口配置单个mapper接口6.4使用name属性注册一个包下的所有mapper接口至此对于mybatis的全局配置大致完成了,接下来就是对SQL映射文件的配置了。
LoveIT 2019-07-06MyBatis