本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架。再而论述Mybatis作为一个数据持久层框架本身有待改进之处。
JDBC实现查询分析我们先看看我们最熟悉也是最基础的通过JDBC查询数据库数据,一般需要以下七个步骤:
加载JDBC驱动;建立并获取数据库连接;创建JDBCStatmnts对象;设置SQL语句的传入参数;执行SQL语句并获得查询结果;对查询结果进行转换处理并将处理结果返回;释放相关资源(关闭Connction,关闭Statmnt,关闭RsultSt);以下是具体的实现代码:
publicstaticListMapString,ObjctquryForList(){Connctionconnction=null;RsultStrs=null;PrpardStatmntstmt=null;ListMapString,ObjctrsultList=nwArrayListMapString,Objct();try{//加载JDBC驱动Class.forNam("oracl.jdbc.drivr.OraclDrivr").nwInstanc();Stringurl="jdbc:oracl:thin:
localhost::ORACLEDB";Stringusr="trainr";Stringpassword="trainr";//获取数据库连接connction=DrivrManagr.gtConnction(url,usr,password);Stringsql="slct*fromusrinfowhrusr_id=?";//创建Statmnt对象(每一个Statmnt为一次数据库执行请求)stmt=connction.prparStatmnt(sql);//设置传入参数stmt.stString(1,"zhangsan");//执行SQL语句rs=stmt.xcutQury();//处理查询结果(将查询结果转换成ListMap格式)RsultStMtaDatarsmd=rs.gtMtaData();intnum=rsmd.gtColumnCount();whil(rs.nxt()){Mapmap=nwHashMap();for(inti=0;inum;i++){StringcolumnNam=rsmd.gtColumnNam(i+1);map.put(columnNam,rs.gtString(columnNam));}rsultList.add(map);}}catch(Excption){.printStackTrac();}finally{try{//关闭结果集if(rs!=null){rs.clos();rs=null;}//关闭执行if(stmt!=null){stmt.clos();stmt=null;}if(connction!=null){connction.clos();connction=null;}}catch(SQLExcption){.printStackTrac();}}rturnrsultList;}JDBC演变到Mybatis过程#上面我们看到了实现JDBC有七个步骤,哪些步骤是可以进一步封装的,减少我们开发的代码量。
.1第一步优化:连接获取和释放##1、问题描述:
数据库连接频繁的开启和关闭本身就造成了资源的浪费,影响系统的性能。
解决问题:
数据库连接的获取和关闭我们可以使用数据库连接池来解决资源浪费的问题。通过连接池就可以反复利用已经建立的连接去访问数据库了。减少连接的开启和关闭的时间。
、问题描述:
但是现在连接池多种多样,可能存在变化,有可能采用DBCP的连接池,也有可能采用容器本身的JNDI数据库连接池。
解决问题:
我们可以通过DataSourc进行隔离解耦,我们统一从DataSourc里面获取数据库连接,DataSourc具体由DBCP实现还是由容器的JNDI实现都可以,所以我们将DataSourc的具体实现通过让用户配置来应对变化。
.第二步优化:SQL统一存取##1、问题描述:
我们使用JDBC进行操作数据库时,SQL语句基本都散落在各个JAVA类中,这样有三个不足之处:
第一,可读性很差,不利于维护以及做性能调优。
第二,改动Java代码需要重新编译、打包部署。
第三,不利于取出SQL在数据库客户端执行(取出后还得删掉中间的Java代码,编写好的SQL语句写好后还得通过+号在Java进行拼凑)。
解决问题:
我们可以考虑不把SQL语句写到Java代码中,那么把SQL语句放到哪里呢?首先需要有一个统一存放的地方,我们可以将这些SQL语句统一集中放到配置文件或者数据库里面(以ky-valu的格式存放)。然后通过SQL语句的ky值去获取对应的SQL语句。
既然我们将SQL语句都统一放在配置文件或者数据库中,那么这里就涉及一个SQL语句的加载问题。
.第三步优化:传入参数映射和动态SQL1、问题描述:
很多情况下,我们都可以通过在SQL语句中设置占位符来达到使用传入参数的目的,这种方式本身就有一定局限性,它是按照一定顺序传入参数的,要与占位符一一匹配。但是,如果我们传入的参数是不确定的(比如列表查询,根据用户填写的查询条件不同,传入查询的参数也是不同的,有时是一个参数、有时可能是三个参数),那么我们就得在后台代码中自己根据请求的传入参数去拼凑相应的SQL语句,这样的话还是避免不了在Java代码里面写SQL语句的命运。既然我们已经把SQL语句统一存放在配置文件或者数据库中了,怎么做到能够根据前台传入参数的不同,动态生成对应的SQL语句呢?
解决问题:
第一,我们先解决这个动态问题,按照我们正常的程序员思维是,通过if和ls这类的判断来进行是最直观的,这个时候我们想到了JSTL中的这样的标签,那么,能不能将这类的标签引入到SQL语句中呢?假设可以,那么我们这里就需要一个专门的SQL解析器来解析这样的SQL语句,但是,if判断的变量来自于哪里呢?传入的值本身是可变的,那么我们得为这个值定义一个不变的变量名称,而且这个变量名称必须和对应的值要有对应关系,可以通过这个变量名称找到对应的值,这个时候我们想到了ky-valu的Map。解析的时候根据变量名的具体值来判断。
假如前面可以判断没有问题,那么假如判断的结果是tru,那么就需要输出的标签里面的SQL片段,但是怎么解决在标签里面使用变量名称的问题呢?这里我们需要使用一种有别于SQL的语法来嵌入变量(比如使用#变量名#)。这样,SQL语句经过解析后就可以动态的生成符合上下文的SQL语句。
还有,怎么区分开占位符变量和非占位变量?有时候我们单单使用占位符是满足不了的,占位符只能为查询条件占位,SQL语句其他地方使用不了。这里我们可以使用#变量名#表示占位符变量,使用变量名表示非占位符变量。
.第四步优化:结果映射和结果缓存1、问题描述:
执行SQL语句、获取执行结果、对执行结果进行转换处理、释放相关资源是一整套下来的。假如是执行查询语句,那么执行SQL语句后,返回的是一个RsultSt结果集,这个时候我们就需要将RsultSt对象的数据取出来,不然等到释放资源时就取不到这些结果信息了。
我们从前面的优化来看,以及将获取连接、设置传入参数、执行SQL语句、释放资源这些都封装起来了,只剩下结果处理这块还没有进行封装,如果能封装起来,每个数据库操作都不用自己写那么一大堆Java代码,直接调用一个封装的方法就可以搞定了。
解决问题:
我们分析一下,一般对执行结果的有哪些处理,有可能将结果不做任何处理就直接返回,也有可能将结果转换成一个JavaBan对象返回、一个Map返回、一个List返回等`,结果处理可能是多种多样的。从这里看,我们必须告诉SQL处理器两点:第一,需要返回什么类型的对象;第二,需要返回的对象的数据结构怎么跟执行的结果映射,这样才能将具体的值copy到对应的数据结构上。
接下来,我们可以进而考虑对SQL执行结果的缓存来提升性能。缓存数据都是ky-valu的格式,那么这个ky怎么来呢?怎么保证唯一呢?即使同一条SQL语句几次访问的过程中由于传入参数的不同,得到的执行SQL语句也是不同的。那么缓存起来的时候是多对。但是SQL语句和传入参数两部分合起来可以作为数据缓存的ky值。
.5第五步优化:解决重复SQL语句问题1、问题描述:
由于我们将所有SQL语句都放到配置文件中,这个时候会遇到一个SQL重复的问题,几个功能的SQL语句其实都差不多,有些可能是SELECT后面那段不同、有些可能是WHERE语句不同。有时候表结构改了,那么我们就需要改多个地方,不利于维护。
解决问题:
当我们的代码程序出现重复代码时怎么办?将重复的代码抽离出来成为独立的一个类,然后在各个需要使用的地方进行引用。对于SQL重复的问题,我们也可以采用这种方式,通过将SQL片段模块化,将重复的SQL片段独立成一个SQL块,然后在各个SQL语句引用重复的SQL块,这样需要修改时只需要修改一处即可。
Mybaits有待改进之处1、问题描述:
Mybaits所有的数据库操作都是基于SQL语句,导致什么样的数据库操作都要写SQL语句。一个应用系统要写的SQL语句实在太多了。
改进方法:
我们对数据库进行的操作大部分都是对表数据的增删改查,很多都是对单表的数据进行操作,由这点我们可以想到一个问题:单表操作可不可以不写SQL语句,通过JavaBan的默认映射器生成对应的SQL语句,比如:一个类UsrInfo对应于USER_INFO表,usrId属性对应于USER_ID字段。这样我们就可以通过反射可以获取到对应的表结构了,拼凑成对应的SQL语句显然不是问题。
5MyBatis框架整体设计MyBatis框架整体设计
5.1接口层-和数据库交互的方式MyBatis和数据库的交互有两种方式:
使用传统的MyBatis提供的API;使用Mappr接口;5.1.1使用传统的MyBatis提供的API这是传统的传递StatmntId和查询参数给SqlSssion对象,使用SqlSssion对象完成和数据库的交互;MyBatis提供了非常方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis自身配置信息的维护操作。
传统的MyBatis工作模式
上述使用MyBatis的方法,是创建一个和数据库打交道的SqlSssion对象,然后根据StatmntId和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis为了适应这一趋势,增加了第二种使用MyBatis支持接口(Intrfac)调用方式。
5.1.使用Mappr接口MyBatis将配置文件中的每一个节点抽象为一个Mappr接口:
这个接口中声明的方法和节点中的slct
updat
dlt
insrt节点项对应,即slct
updat
dlt
insrt节点的id值为Mappr接口中的方法名称,paramtrTyp值表示Mappr对应方法的入参类型,而rsultMap值则对应了Mappr接口表示的返回值类型或者返回结果集的元素类型。
Mappr接口和Mappr.xml配置文件之间的对应关系
根据MyBatis的配置规范配置好后,通过SqlSssion.gtMappr(XXXMappr.class)方法,MyBatis会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mappr实例,我们使用Mappr接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定StatmntId,底层还是通过SqlSssion.slct("statmntId",paramtrObjct);或者SqlSssion.updat("statmntId",paramtrObjct);等等来实现对数据库的操作,MyBatis引用Mappr接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。
5.数据处理层数据处理层可以说是MyBatis的核心,从大的方面上讲,它要完成两个功能:
通过传入参数构建动态SQL语句;SQL语句的执行以及封装查询结果集成List;5..1参数映射和动态SQL语句生成动态语句生成可以说是MyBatis框架非常优雅的一个设计,MyBatis通过传入的参数值,使用Ognl来动态地构造SQL语句,使得MyBatis有很强的灵活性和扩展性。
参数映射指的是对于java数据类型和jdbc数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将java类型的数据,转换成jdbc类型的数据,通过prpardStatmnt.stXXX()来设值;另一个就是对rsultst查询结果集的jdbcTyp数据转换成java数据类型。
5..SQL语句的执行以及封装查询结果集成List动态SQL语句生成之后,MyBatis将执行SQL语句,并将可能返回的结果集转换成List列表。
MyBatis在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。
5.框架支撑层1、事务管理机制
事务管理机制对于ORM框架而言是不可缺少的一部分,事务管理机制的质量也是考量一个ORM框架是否优秀的一个标准。
、连接池管理机制
由于创建一个数据库连接所占用的资源比较大,对于数据吞吐量大和访问量非常大的应用而言,连接池的设计就显得非常重要。
、缓存机制
为了提高数据利用率和减小服务器和数据库的压力,MyBatis会对于一些查询提供会话级别的数据缓存,会将对某一次查询,放置到SqlSssion中,在允许的时间间隔内,对于完全相同的查询,MyBatis会直接将缓存结果返回给用户,而不用再到数据库中查找。
、SQL语句的配置方式
传统的MyBatis配置SQL语句方式就是使用XML文件进行配置的,但是这种方式不能很好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis引入了Mappr接口的概念,面向接口的引入,对使用注解来配置SQL语句成为可能,用户只需要在接口上添加必要的注解即可,不用再去配置XML文件了,但是,目前的MyBatis只是对注解配置SQL语句提供了有限的支持,某些高级功能还是要依赖XML配置文件配置SQL语句。
5.引导层引导层是配置和启动MyBatis配置信息的方式。MyBatis提供两种方式来引导MyBatis:基于XML配置文件的方式和基于JavaAPI的方式。
5.5主要构件及其相互关系从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
SqlSssion:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
Excutor:MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护;
StatmntHandlr:封装了JDBCStatmnt操作,负责对JDBCstatmnt的操作,如设置参数、将Statmnt结果集转换成List集合。
ParamtrHandlr:负责对用户传递的参数转换成JDBCStatmnt所需要的参数;
RsultStHandlr:负责将JDBC返回的RsultSt结果集对象转换成List类型的集合;
TypHandlr:负责java数据类型和jdbc数据类型之间的映射和转换;
MappdStatmnt:MappdStatmnt维护了一条slct
updat
dlt
insrt节点的封装;
SqlSourc:负责根据用户传递的paramtrObjct,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
BoundSql:表示动态生成的SQL语句以及相应的参数信息;
Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;
它们的关系如下图所示:
MyBatis主要构件关系如图
6SqlSssion工作过程分析1、开启一个数据库访问会话---创建SqlSssion对象
SqlSssionsqlSssion=factory.opnSssion();
MyBatis封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSssion对象中
、为SqlSssion传递一个配置的Sql语句的StatmntId和参数,然后返回结果:
ListEmployrsult=sqlSssion.slctList("