【MyBatis】Mybatis 连接池与事务控制

1. Mybatis 连接池

1.1 Mybatis连接池的分类

在 Mybatis 中我们将它的数据源 dataSource 分为以下几类:

可以看出 Mybatis 将它自己的数据源分为三类:

  • UNPOOLED 不使用连接池的数据源
  • POOLED 使用连接池的数据源
  • JNDI 使用 JNDI 实现的数据源

具体结构如下:

相应地, MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,PooledDataSource 类来表示 UNPOOLED、 POOLED 类型的数据源。

PooledDataSource和UnpooledSource都实现了java.sql.DataSource接口。
并且PooledDataSource持有一个UnpooledDataSource的引用,当PooledDataSource需要创建java.sql.Connection实例对象时,还是通过UnpooledDataSource来创建。

PolledDataSource只是提供一种缓存连接池机制。

在这三种数据源中,我们一般采用的是 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 在初始化时, 根据的 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 源码,具体如下:

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
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 对象中, 供以后使用。
具体分析过程如下:
1.先进入 XMLConfigBuilder 类中,可以找到如下代码:

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

2.分析 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 工作原理如下:

下面是连接获取的源代码:

  @Override
  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
  }

最后我们可以发现,真正连接打开的时间点,只是在我们执行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);
}

@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
	//7.提交事务
	session.commit();
	//8.释放资源
	session.close();
	in.close();
}

@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());
}

观察在它在控制台输出的结果:

这是我们的 Connection 的整个变化过程, 通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在 CUD 操作中,必须通过 sqlSession.commit()方法来执行提交操作。

2.3 Mybatis自动提交事务

通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。

所对应的 DefaultSqlSessionFactory 类的源代码:

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }

由上面源码可以看出,如果我们在调用openSession方法时没有传递参数的话,他会以false的为参数传递,如果指定了autoCommit那么会以指定的autoCommit传递。

那么我们可以大胆试试openSession(true).

package com.siyi.test;

import com.siyi.dao.IUSerDao;
import com.siyi.domain.QueryVo;
import com.siyi.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class MbatisTest {

    private InputStream in;
    private SqlSession session;
    private IUSerDao iuSerDao;

    @Before//用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件生成字节输入流
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //3.获取SqlSession对象
        session = factory.openSession(true);
        //4.获取DAO的代理对象
        iuSerDao = session.getMapper(IUSerDao.class);
    }

    @After//用于在测试方法之后执行
    public void destroy() throws IOException {
        //事务提交
        //session.commit();
        //释放资源
        session.close();
        in.close();
    }

    /**
     * 测试保存操作
     */
    @Test
    public void testSave() throws IOException {
        User user = new User();
        user.setUsername("autocommit");
        user.setAddress("中国四川");
        user.setSex("男");
        user.setBirthday(new Date());
        System.out.println(user);
        iuSerDao.saveUser(user);
        System.out.println(user);
    }
}

在这里插入图片描述
此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false 再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

发布了476 篇原创文章 · 获赞 152 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_41879343/article/details/104919577