前言
MyBatis+Spring MVC这套组合,在实际互联网项目中非常流行,博主工作中也涉及过,打算由浅入深、系统的写出来!这个系列将会涵盖MyBatis开发详解、Spring MVC开发详解,以及2者的结合使用,并会分析它们的原理!(可以参考博主的另一篇文章了解Spring MVC原理:《写出我的第一个框架:迷你版Spring MVC》)
没有MyBatis之前
在早期,我们都是通过原生的JDBC来操作数据库的,而这种方式存在很多问题。
我们先来看一个例子:
问题有哪些呢?
第一,在创建数据库连接这块,程序是需要时创建,用完后关闭。如果频繁的创建、关闭数据库连接,显然存在问题。当然,我们可以通过数据库连接池来处理这个问题。
第二,硬编码的地方太多了。比如,数据库连接相关的一些信息,SQL相关的一些信息。当然,我们可以通过使用配置文件,来避免这个问题。
第三,实质上,我们编写JDBC是有步骤可循的,比如,我们得先得到数据库连接对象,得有SQL,有输入参数,设置参数,去执行SQL,然后遍历结果集将数据库SQL执行的结果对象转化为JAVA对象,然后再去业务处理,最后释放资源。那么这个过程,实际上是个模板,能不能抽离出来,更好的去完成这个过程呢?
比如,Hibernate,这个纯粹的ORM(对象关系映射)框架,让程序员以面向对象的方式来完成数据库的操作,功能很强大,SQL都帮我们自动生成了,但是这也带来了一些其他问题,比如门槛较高,有时候我们想编写SQL,修改SQL,优化SQL都费劲,在互联网项目快速迭代开发中,太过笨重了!
基于这些因素,出现了iBatis,并发展为今天的MyBatis,作为Apache的顶级项目,目前已经托管到github下。
MyBatis框架的架构
上面我们说了一些JDBC的缺点,而MyBatis是避免了这些问题的。如果让我们来实现MyBatis的话,我们会怎么想呢?
第一,应该存在一个配置文件A,可以将数据库的连接信息,事务信息等放入其中;
第二,应该提供一个配置文件B,可以让程序员编写SQL,重点需要解决的是如何给SQL传递参数,以及如何将结果映射为JAVA对象;
第三,应该提供API可以执行文件B中的SQL
基于上面的分析,我们来看一下MyBatis的架构:
这里,我们先简单了解些概念:
SqlSessionFactory用于创建SqlSession,SqlSession即操作DB的接口,其内部借助Executor执行器完成对数据库的操作。
全局配置文件,就是图中的SqlMapConfig.xml;SQL文件即是Mapper.xml文件。
对于MappedStatement而言,会完成输入映射以及输出映射。
Quick Start
这里先写个简单的DEMO带大家初步了解下。
POM依赖:
MyBatis的全局配置文件:
需要注意下:
第一,我们把MySQL的一些连接信息放入到db.properties中,使用<properties>标签加载属性文件,并通过${XXX}的方式引用。
第二,要知道现在的日志框架有很多,这里使用<settings>设置下日志使用LOG4J实现。
第三,我们说SQL结果集要完成到JAVA对象的映射,那么根据反射的原理,我们都能猜到必须要提供带包路径的全限定名称,那么为了简化,提供<typeAliases>标签进行别名映射处理。提供了2种方式,一个是单个的类型别名映射,一个是基于包扫描的批量映射。当然批量映射的别名就是类名。
第四,需要<mappers>标签加载SQL文件。同上面一样,也提供了基于包扫描的批量加载。
log4j.properties:
在开发阶段,显然,我们希望MyBatis能够为我们打印SQL日志,方便调试,排查问题。
SQL配置:
需要关注下:
第一,namespace,顾名思义,命名空间,其实是想隔离SQL,不过到了MyBatis和Spring结合使用时,具有特殊的意义。这里暂且使用全限定类名。
第二,<select>等SQL Command标签需要一个ID,还需要输入参数parameterType,输出参数映射resultType等。其实这些在MyBatis的底层封装成了一个MappedStatement对象。当然定位这个对象,需要namespace.id的方式。
第三,${value} VS #{xxx}
其实2者,都可以接受JAVA简单类型,如int,也可以接受POJO,Map等复杂类型。如果是JAVA简单类型,那么$的方式必须是${value},而#{}可以随意,是因为在这种情况下,$会通过反射getValue()的方式取值。如果是POJO等复杂类型,2者其实都可以通过OGNL表达式取到,只不过#会额外的进行JAVA类型到数据库类型的转换,而$没有类型处理过程,它直接拼接。也就是说#会使用预编译成?,而$将在SQL编译阶段就采取替换操作,可能带来SQL注入的问题。所以在实际开发中,我们当然优先采用#的方式取值。
测试程序:
测试程序,并没有太多可以说的,关注2点即可:
第一,selectOne VS selectList
显然,我们需要清楚的知道,SQL返回的结果集是一条记录,还是多条记录,如果使用selectOne那么必须最多返回一条记录。那么返回多条记录与返回一条记录的时候,resultType有变化么?(其实是不变的。)
第二,SqlSession
SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession
我们重点关注的是SqlSession,它其实是一个interface,定义了很多操作数据库的接口,而且extends Closeable,很明确是需要close的。
它的实现类DefaultSqlSession中有一些数据域(比如说autoCommit,在默认情况下是不开启自动提交的),而且方法也并不是Synchronized,这说明SqlSession并不是线程安全的,因此我们应该是局部使用SqlSession,使用完毕后close掉。
Mapper代理开发
其实,除了Mapper代理开发外,还有一种原始Dao开发的方式。原始Dao开发方式的思路大致是这样的:
第一,我们提供Dao接口,有增、删、改、查的方法。
第二,我们提供Dao的实现类,在实现类中,我们利用Spring注入SqlSessionFactory,然后在各个方法中得到SqlSession,进行操作后,关闭SqlSession即可。
这种方式,重复的代码太多,已经OUT了,目前使用最多的就是Mapper代理开发。
提供Mapper.java接口:
提供与之对应的Mapper.xml文件:
在全局配置文件中加载Mapper.xml:
测试代码示例:
从这里,你应该可以看出Mapper的开发应该遵循一些规范,这样MyBatis才可以自动帮助我们生成XXXMapper类的代理实现类。(说白了,这些规范,就是为了利用反射)
第一,保证XXXMapper.xml中的namespace同XXXMapper.java的全限定名称一致
第二,保证XXXMapper.xml中的Statement的ID同XXXMapper.java的方法名称一致
第三,保证XXXMapper.xml中的Statement的输入参数的类型(parameterType)、输出参数的类型(resultType)同XXXMapper.java的保持一致
从这里,你大致可以了解到Mapper代理开发,程序员主要关注的就是Mapper.java以及Mapper.xml的生成,可以说极大的简化了工作量!
关于自增主键返回
很多时候,我们面临这样的需求,A表的字段ID是主键,而且是auto_increment自动增长的;我们完成A表的插入后,希望得到主键,以便后续的操作,比如另外一个表B,和表A存在主外键关系。
MyBatis当然早就替我们想好了,只需要稍微配置下,就可以将MySQL自动生成的主键取出设置到对应的JAVA对象的属性上。
看一个例子:
特别注意keyProperty是表示将获取到的自动增长的值设置到哪个Field域上。
关于动态SQL
我们知道,在JSP中,可以使用JSTL标签开发;而动态SQL就是类似于JSTL的一组标签,可以帮助我们灵活的生成SQL,比如实现判断,遍历数组/集合,SQL片段的复用等。值得关注的是,有些标签还挺智能,比如<where>还可以替你去掉第一个and.....
本篇博客并不会说明各个标签的使用方式,这样的例子,网上很多,大家可以参考。一句话,在开发阶段,我们只需要让MyBatis打印SQL,我们就能明白,我们的动态SQL是不是使用对了!
到这里,本篇博客就准备结束了,下一篇博客将会为大家介绍MyBatis的一些高级开发知识~
See U next time~