1 MyBatis的连接池技术
我们在前面的WEB课程中也学习过类似的连接池技术,而在MyBatis中也有连接池技术,但是它采用的是自己的连接池技术。在MyBatis的SqlMapConfig.xml配置文件中,通过来实现MyBatis中连接池的配置。
1.1 MyBatis连接池的分类
在MyBatis中我们将它的数据源dataSource分为以下几类:
可以看出MyBatis将它自己的数据源分为三类:
-
UNPOOLED
:不使用连接池的数据源; -
POOLED
:使用连接池的数据源; -
JNDI
:使用JNDI实现的数据源。
具体结构如下:
相应地,MyBatis内部分别定义了实现了 java.sql.DataSource
接口的 UnpooledDataSource
,PooledDataSource
类来表示 UNPOOLED
、POOLED
类型的数据源。
在这三种数据源中,我们一般采用的是 POOLED
数据源。很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术。
1.2 MyBatis中数据源的配置
我们的数据源配置就是在SqlMapConfig.xml文件中,具体配置如下:
<!-- 配置数据源(连接池)信息 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
MyBatis在初始化时,根据 <dataSource>
的type属性来创建相应类型的的数据源DataSource,即:
-
type="POOLED"
:MyBatis会创建PooledDataSource实例; -
type="UNPOOLED"
: MyBatis会创建UnpooledDataSource实例; -
type="JNDI"
:MyBatis会从JNDI服务上查找DataSource实例,然后返回使用。
1.3 MyBatis中DataSource的存取
MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口 org.apache.ibatis.datasource.DataSourceFactory
,通过其 getDataSource()
方法返回数据源DataSource。下面是DataSourceFactory源码,具体如下:
package org.apache.ibatis.datasource;
import java.util.Properties;
import javax.sql.DataSource;
/** * @author Clinton Begin */
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中,供以后使用。具体分析过程如下:
- 先进入XMLConfigBuilder类中,可以找到如下代码:
- 分析configuration对象的environment属性,结果如下:
1.4 MyBatis中连接的获取过程分析
当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。
@Test
public void testSql() throws Exception {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("findUserById",41);
System.out.println(list.size());
}
只有当第4句 sqlSession.selectList("findUserById")
,才会触发MyBatis在底层执行下面这个方法来创建java.sql.Connection对象。
如何证明它的加载过程呢? 我们可以通过断点调试,在PooledDataSource中找到如下 popConnection()
方法,如下所示:
分析源代码,得出PooledDataSource工作原理如下:
下面是连接获取的源代码:
最后我们可以发现,真正连接打开的时间点,只是在我们执行SQL语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。
2 MyBatis的事务控制
2.1 JDBC中事务的回顾
在JDBC中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()
方法就可以调整。通过JDK文档,我们找到该方法如下:
那么我们的MyBatis框架因为是对JDBC的封装,所以MyBatis框架的事务控制方式,本身也是用JDBC的setAutoCommit()方法来设置事务提交方式的。
2.2 MyBatis中事务提交方式
MyBatis中事务的提交方式,本质上就是调用JDBC的setAutoCommit()来实现事务控制。我们运行之前所写的代码:
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建SqlSession工厂对象
factory = builder.build(in);
//4.创建SqlSession对象
session = factory.openSession();
//5.创建Dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@Test
public void testSaveUser() throws Exception {
User user = new User();
user.setUsername("mybatis user09");
//6.执行操作
int res = userDao.saveUser(user);
System.out.println(res);
System.out.println(user.getId());
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
//7.提交事务
session.commit();
//8.释放资源
session.close();
in.close();
}
观察在它在控制台输出的结果:
这是我们的Connection的整个变化过程,通过分析我们能够发现之前的CUD操作过程中,我们都要手动进行事务的提交,原因是setAutoCommit()方法,在执行时它的值被设置为false了,所以我们在CUD操作中,必须通过sqlSession.commit()方法来执行提交操作。
2.3 MyBatis自动提交事务的设置
通过上面的研究和分析,现在我们一起思考,为什么CUD过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。
明白这一点后,我们现在一起尝试不进行手动提交,一样实现CUD操作。
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建SqlSession工厂对象
factory = builder.build(in);
//4.创建SqlSession对象
session = factory.openSession(true);
//5.创建Dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
//7.释放资源
session.close();
in.close();
}
所对应的DefaultSqlSessionFactory类的源代码:
运行的结果如下:
我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。