文章目录
欢迎关注公众号:理木客
1.导读
本文主要讲解
- JDBC演变到Mybatis的过程
- 把JDBC封装成Mybatis的原因
- mybatis的API、源码分析
2.Jdbc VS Mybatis
我们先看看最基础的通过JDBC查询数据库数据,一般需要以下七个步骤:
- 加载JDBC驱动
- 建立并获取数据库连接
- 创建 JDBC Statements 对象
- 设置SQL语句的传入参数
- 执行SQL语句并获得查询结果
- 对查询结果进行转换处理并将处理结果返回
- 释放相关资源(关闭Connection,关闭Statement,关闭ResultSet)
上面我们看到了实现JDBC有七个步骤,哪些步骤是可以进一步封装的,减少我们开发的代码量?
问题1
数据库连接频繁的开启和关闭本身就造成了资源的浪费,影响系统的性能。
mybatis解决方案:
数据库连接的获取和关闭我们可以使用数据库连接池来解决资源浪费的问题。通过连接池就可以反复利用已经建立的连接去访问数据库了。减少连接的开启和关闭的时间。
问题2
现在连接池多种多样,可能存在变化,有可能采用DBCP的连接池,也有可能采用容器本身的JNDI数据库连接池。
mybatis解决方案:
我们可以通过DataSource进行隔离解耦,我们统一从DataSource里面获取数据库连接,DataSource具体由DBCP实现还是由容器的JNDI实现都可以,所以我们将DataSource的具体实现通过让用户配置来应对变化。
问题3
JDBC很多情况下,我们都可以通过在SQL语句中设置占位符来达到使用传入参数的目的,这种方式本身就有一定局限性,它是按照一定顺序传入参数的,要与占位符一一匹配。但是,如果我们传入的参数是不确定的(比如列表查询,根据用户填写的查询条件不同,传入查询的参数也是不同的,有时是一个参数、有时可能是三个参数),那么我们就得在后台代码中自己根据请求的传入参数去拼凑相应的SQL语句,这样的话还是避免不了在Java代码里面写SQL语句的命运。既然我们已经把SQL语句统一存放在配置文件或者数据库中了,怎么做到能够根据前台传入参数的不同,动态生成对应的SQL语句呢?
mybatis解决方案:
mybatis支持<if test=""></if>
,<where></where>
,<set></set>
等,通过这样的标签可以达到动态SQL的目的。
问题4
JDBC执行SQL语句、获取执行结果、对执行结果进行转换处理、释放相关资源是一整套下来的。假如是执行查询语句,那么执行SQL语句后,返回的是一个ResultSet结果集,这个时候我们就需要将ResultSet对象的数据取出来,不然等到释放资源时就取不到这些结果信息了?而且JDBC返回的数据是一个ResultSet的结果集,如果想取出数据就要遍历,很麻烦!
mybatis解决方案:
mybatis可以设置结果返回的类型如:结果转换成一个JavaBean对象返回、一个Map返回、一个List返回等,这样数据直接就是我们想要的,另外mybatis支持二级缓存详情参考博客:细说mybatis一级缓存二级缓存
mybatis的缺点
- mybatis所有的数据库操作都是基于SQL语句,导致什么样的数据库操作都要写SQL语句。当你使用mybatis编写SQL语句时工作量很大,尤其是字段多、关联表多时,sql语句超级复杂,更是如此。
- SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
3.mybatis的API解析
Dao中需要通过SqlSession对象来操作DB。而SqlSession对象的创建,需要其工厂对象SqlSessionFactory。SqlSessionFactory对象,需要通过其构建器对象SqlSessionFactoryBuilder的build()方法,在加载了主配置文件的输入流对象后创建。
3.1.Resources类
Resources
类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。
类路径:org.apache.ibatis.io.Resources;
3.2.SqlSessionFactoryBuilder类
SqlSessionFactory
的创建,需要使用SqlSessionFactoryBuilder
对象的build()方法。由于SqlSessionFactoryBuilder
对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。所以,一般会将该SqlSessionFactoryBuilder
对象创建为一个方法内的局部对象,方法结束,对象销毁。其被重载的build()
方法较多。
类路径:org.apache.ibatis.session.SqlSessionFactoryBuilder;
3.3.SqlSessionFactory接口
SqlSessionFactory
接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建SqlSession
需要使用SqlSessionFactory
接口的的openSession()
方法。
- openSession(true):创建一个有自动提交功能的SqlSession
- openSession(false):创建一个非自动提交功能的SqlSession,需手动提交
- openSession():不填创建非自动提交功能的SqlSession
类路径:org.apache.ibatis.session.SqlSessionFactory;
3.4.SqlSession接口
SqlSession
接口对象用于执行持久化操作。一个SqlSession
对应着一次数据库会话,一次会话以SqlSession
对象的创建开始,以SqlSession
对象的关闭结束。
SqlSession
接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其close()
方法,将其关闭。再次需要会话,再次创建。而在关闭时会判断当前的SqlSession
是否被提交:若没有被提交,则会执行回滚后关闭;若已被提交,则直接将SqlSession
关闭。所以,SqlSession
无需手工回滚。
4.源码分析
4.1.sqlSessionFactory输入流的关闭
在输入流对象使用完毕后,不用手工进行流的关闭。因为在输入流被使用完毕后,SqlSessionFactoryBuilder
对象的build()
方法会自动将输入流关闭。
4.2.SqlSession的创建
SqlSession
对象的创建,需要使用SqlSessionFactory
接口对象的openSession()
方法。SqlSessionFactory
接口的实现类为DefaultSqlSessionFactory
。
从以上源码可以看到,无参的openSession()方法,将事务的自动提交直接赋值为false。而所谓创建SqlSession,就是加载了主配置文件,创建了一个执行器对象(将来用于执行映射文件中的SQL语句),初始化了一个DB数据被修改的标志变量dirty,关闭了事务的自动提交功能。
4.3.增删改的执行
对于SqlSession的insert()、delete()、update()方法,其底层均是调用执行了update()方法。
从以上源码可知,无论执行增、删还是改,均是对数据进行修改,均将dirty变量设置为了true,且在获取到映射文件中指定id的SQL语句后,由执行器executor执行。这一点其实和JDBC有点像。
4.4.SqlSession的提交commit()
由以上代码可知,isCommitOrRollbackRequired(force)
方法的返回值为true,这里的executor是Executor接口实现类CachingExecutor
的对象
而CachingExecutor
可以看成是SimpleExecutor
类的一个装饰类,主要作用是在SimpleExecutor
加一个缓冲区。因此delegate
实际上SimpleExecutor
类的对象,而SimpleExecutor
继承BaseExecutor
类,调用的自然是BaseExecutor
类的commit()
方法:
这里首先进行了清空缓存和刷新数据,然后进行的事物提交,即transaction.commit()
,由以上代码可知,执行SqlSession
的无参commit()
方法,最终会将事务进行提交。
这里的transaction
是Transaction
接口实现类JdbcTransaction
的对象,进入到这个类中。
可以看到底层调用的依然是java基础api中的Connection中的commit方法,这也再一次证明了Mybatis框架实际上是对Jdbc的封装。
4.5.SqlSession的关闭
以上代码可知,isCommitOrRollbackRequired(force)方法的返回值为false。j继续跟踪executor的close()方法:
相反的如果不执行提交,dirty是true,isCommitOrRollbackRequired(force)运算完的结果是true,事务就会进行回滚。
欢迎关注公众号:理木客