07【MyBatis连接池与事务管理】

一、MyBatis连接池与事务管理

1.1 MyBatis连接池管理

1.1.1 搭建项目

在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过 <dataSource type="pooled">来实现 Mybatis 中连接池的配置。

  • 依赖:
<dependencies>
    <!-- mybatis依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>

    <!--mysql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <!--日志依赖-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <!--测试单元-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <!--lombok插件-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>

    <!--druid数据源-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.1</version>
    </dependency>
</dependencies>
  • 实体类:
package com.dfbz.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
    private Integer id;
    private String name;
    private Integer age;
    private String addr;
    private Double salary;
}

  • EmpDao:
package com.dfbz.dao;

import com.dfbz.entity.Emp;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public interface EmpDao {

    Emp findById(Integer id);
    
    void deleteById(Integer id);
}
  • EmpDao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dfbz.dao.EmpDao">

    <delete id="deleteById">
        delete from emp where id=#{id}
    </delete>

    <select id="findById" resultType="Emp">
        select * from emp where id=#{id};
    </select>
</mapper>

1.1.2 MyBatis内置数据源

我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--日志配置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--  default 默认使用那个运行环境 -->
    <environments default="dev">
        
        <!--配置dev运行环境-->
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <!--POOLED指定采用mybatis内置的连接池支持-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis01?characterEncoding=UTF8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="admin"></property>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.dfbz.dao"/>
    </mappers>
</configuration>

在这里插入图片描述

Tips:MyBatis 在初始化时,解析此文件,根据<dataSource>的type属性来创建相应类型的的数据源

在MyBatis的源码包中可以看到MyBatis对数据源的分类:

在这里插入图片描述


我们使用的POOLEDUNPOOLEDJNDI都是使用连接池的别名,我们可以通过查看Configuration类获取真正的类:

在这里插入图片描述

别名对应的类:

  • POOLED:PooledDataSourceFactory—>PooledDataSource

  • UNPOOLED:UnpooledDataSourceFactory—>UnpooledDataSource

  • JNDI:JndiDataSourceFactory—>JndiDataSource

我们发现这三个类实现与DataSourceFactory接口:

在这里插入图片描述

1)UNPOOLED

采用这种方式,Mybatis内部创建PooledDataSource作为数据源,如果采用UNPOOLED方式,Mybatis将不会使用数据源,每次查询都会创建一个新的连接;

这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

  • UNPOOLED数据源相关的配置属性如下:
    • driver:这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
    • url:这是数据库的 JDBC URL 地址。
    • username:登录数据库的用户名。
    • password:登录数据库的密码。
    • defaultTransactionIsolationLevel:默认的连接事务隔离级别。
    • defaultNetworkTimeout:等待数据库操作完成的默认网络超时时间(单位:毫秒)。

配置SqlMapConfig.xml:

<environments default="dev">
    <!--配置dev运行环境-->
    <environment id="dev">
        <!--事务管理器:JDBC事务管理-->
        <transactionManager type="JDBC"></transactionManager>
        <!--采用UNPOOLED连接池-->
        <dataSource type="UNPOOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis01?characterEncoding=UTF8"></property>
            <property name="username" value="root"></property>
            <property name="password" value="admin"></property>

            <!--
                事务隔离级别:
                        NONE(0): 不支持事务(MySQL等数据库并不支持该选项)
                        READ_UNCOMMITTED(1): 读未提交
                        READ_COMMITTED(2): 读已提交
                        REPEATABLE_READ(4): 可重复读
                        SERIALIZABLE(8): 串行化
            -->
            <property name="defaultTransactionIsolationLevel" value="4"></property>

            <!--SQL语句的执行超时时间-->
            <property name="defaultNetworkTimeout" value="20000"></property>
        </dataSource>
    </environment>
</environments>
  • 测试类:
package com.dfbz.mybatis;

import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
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.Before;
import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_UNPooled {
    SqlSessionFactory factory;

    @Before
    public void before() throws Exception {
        factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
    }

    @Test
    public void test1() throws Exception {
        SqlSession session = factory.openSession();

        EmpDao mapper = session.getMapper(EmpDao.class);
        Emp emp = mapper.findById(13);

        System.out.println(emp);
        session.close();
    }

    @Test
    public void test2() throws Exception {
        SqlSession session = factory.openSession();

        EmpDao mapper = session.getMapper(EmpDao.class);
        mapper.deleteById(13);
        
        session.rollback();
        session.close();
    }
}

UNPOOLED的特点在于,如果采用UNPOOLED方式,Mybatis将不会使用数据源,每次查询都会创建一个新的连接;

我们翻开UnpooledDataSource源码,发现其每次在获取连接时,都是调用原生的JDBC去创建一个新的连接,并没有对连接进行连接池的管理;

在这里插入图片描述

在使用UNPOOLED连接池策略时,每次获取一个session,都会调用UnpooledDataSource类的doGetConnection方法来获取一个新的连接,而该方法每次调用都会使用DriverManager类来创建一个新的连接;

在这里插入图片描述

2)POOLED

这种方式MyBatis会创建 PooledDataSource 实例来管理数据库连接(Connection)

这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections:连接池中最大的活跃连接数量,默认值:10
  • poolMaximumIdleConnections:连接池最大的空闲连接数,默认值:5
  • poolMaximumCheckoutTime:获取连接的超时时间,超出该时间还未获取到连接MyBatis将会强制获取连接;默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait:如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。

配置SqlMapConfig.xml:

<environments default="dev">
    <!--配置dev运行环境-->
    <environment id="dev">
        <!--事务管理器:JDBC事务管理-->
        <transactionManager type="JDBC"></transactionManager>
        
        <!--采用POOLED连接池-->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis01?characterEncoding=UTF8"></property>
            <property name="username" value="root"></property>
            <property name="password" value="admin"></property>

            <!--连接池中最大的活跃连接数量-->
            <property name="poolMaximumActiveConnections" value="5"></property>

            <!--连接池最大的空闲连接数-->
            <property name="poolMaximumIdleConnections" value="0"></property>

            <!--获取连接的超时时间,超出该时间还未获取到连接MyBatis将会强制获取连接(获取poolMaximumActiveConnections个)-->
            <property name="poolMaximumCheckoutTime" value="3000"></property>

            <!--有多久没有获取到连接就打印日志-->
            <property name="poolTimeToWait" value="1000"></property>
        </dataSource>
    </environment>
</environments>
  • 测试类:
package com.dfbz.mybatis;

import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
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.Before;
import org.junit.Test;

import java.util.HashSet;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_Pooled {
    SqlSessionFactory factory;

    @Before
    public void before() throws Exception {
        factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("SqlMapConfig.xml"));
    }

    @Test
    public void test1() throws Exception {

        HashSet<Object> sessionCount = new HashSet<>();

        for (int i = 1; i <= 20; i++) {
            // 获取新的session
            SqlSession session = factory.openSession();

            // 执行查询(使其成为活跃连接)
            Object emp = session.selectOne("com.dfbz.dao.EmpDao.findById", 9);

            // 使用HashSet去重特点,统计创建session的次数
            sessionCount.add(session.toString());
        }

        System.out.println(sessionCount.size());         // 查看最终创建了多少个连接(20个)
    }


    @Test
    public void test2() throws Exception {
        SqlSession session = factory.openSession();
        EmpDao mapper = session.getMapper(EmpDao.class);

        Emp emp = mapper.findById(10);

        System.out.println(emp);

        session.close();
    }
}

选择Pooled方式MyBatis底层则会创建PooledDataSource类来对连接进行管理,该类中提供PoolState类负责存储连接;

PooledDataSource类的成员变量如下:

在这里插入图片描述

PoolState

在这里插入图片描述

采用POOLED方案管理连接时,每次获取一个连接都会调用PooledDataSource类的popConnection方法从数据源中获取一个连接:

在这里插入图片描述

3)JNDI

这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,MyBatis会从容器发布的JNDI服务上查找 DataSource 实例;

<environments default="dev">
    <environment id="dev">
        <transactionManager type="JDBC"></transactionManager>
        <!--采用JNDI数据源服务-->
        <dataSource type="JNDI">
            <property name="data_source" value="java:comp/env/MyBatisJNDI"/>
        </dataSource>
    </environment>
</environments>

1.1.3 自定义连接池

  • 自定义MyDataSourceFactory:
package com.dfbz.datasource;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.datasource.DataSourceFactory;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class MyDataSourceFactory implements DataSourceFactory {
    private DataSource dataSource;

    private Properties properties;

    @Override
    public void setProperties(Properties properties) {
        // MyBatis在初始化时会读取SqlMapConfig.xml中的properties配置,然后将读取到的配置传递到这个方法中
        this.properties=properties;

        try {
            dataSource=DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public DataSource getDataSource() {
        // Mybatis需要获取连接时首先从这个方法这里获取数据源(该方法只会调用一次)
        return dataSource;
    }
}
  • 注册我们自己的连接池:
<!--  default 默认使用那个运行环境 -->
<environments default="dev">
    <!--配置dev运行环境-->
    <environment id="dev">
        <!--事务管理器:JDBC事务管理-->
        <transactionManager type="JDBC"></transactionManager>

        <!--注册我们自己的连接池-->
        <dataSource type="com.dfbz.datasource.MyDataSourceFactory">
            <property name="driver" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF8"></property>
            <property name="username" value="root"></property>
            <property name="password" value="admin"></property>

            <!--初始化连接数(具体数据源的配置)-->
            <property name="initialSize" value="5"></property>

            <!--最大连接数(具体数据源的配置)-->
            <property name="maxActive" value="15"></property>

            <!--最长等待时间(具体数据源的配置)-->
            <property name="maxWait" value="3000"></property>
        </dataSource>
    </environment>
</environments>
  • 测试类:
package com.dfbz.demo;

import com.dfbz.entity.Emp;
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.InputStream;
import java.util.HashSet;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_自定义连接池 {
    InputStream is;
    SqlSessionFactoryBuilder sessionFactoryBuilder;
    SqlSessionFactory factory;

    @Before
    public void before() throws Exception{
        is = Resources.getResourceAsStream("SqlMapConfig.xml");

        sessionFactoryBuilder = new SqlSessionFactoryBuilder();

        factory = sessionFactoryBuilder.build(is);
    }

    @After
    public void after() throws Exception{
        is.close();
    }

    @Test
    public void test1() throws Exception {

        HashSet<Object> sessionCount = new HashSet<>();

        for (int i = 1; i <= 20; i++) {
            // 获取新的session
            SqlSession session = factory.openSession();

            // 执行查询(使其成为活跃连接)
            Emp emp = session.selectOne("com.dfbz.dao.EmpDao.findById");

            // 使用HashSet去重特点,统计创建session的次数
            sessionCount.add(session.toString());
        }

        System.out.println(sessionCount.size());         // 查看最终创建了多少个连接(20个)
    }
}

1.2 MyBatis事务管理

1.2.1 JDBC

在 MyBatis 中有两种类型的事务管理器JDBCMANAGED

  • JDBC:使用原生的JDBC提交和回滚;它依赖从数据源获得的连接来管理事务作用域。

在SqlMapConfig.xml中选择:

<!--采用JdbcTransaction事务管理器来管理事务-->
<transactionManager type="JDBC"></transactionManager>

Tips:通过Configuration配置类可以查看JDBC别名的全称:JdbcTransactionFactory

查看JDBCTransactionManager部分源码:

在这里插入图片描述

  • 测试类:
package com.dfbz.demo;

import com.dfbz.dao.EmpDao;
import com.dfbz.entity.Emp;
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.InputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_JDBC {
    InputStream is;
    SqlSessionFactoryBuilder sessionFactoryBuilder;
    SqlSessionFactory factory;
    SqlSession session ;

    @Before
    public void before() throws Exception{
        is = Resources.getResourceAsStream("SqlMapConfig.xml");

        sessionFactoryBuilder = new SqlSessionFactoryBuilder();

        factory = sessionFactoryBuilder.build(is);

    }

    @After
    public void after() throws Exception{
        session.close();
        is.close();
    }


    @Test
    public void test1() throws Exception{
        // 修改为自动提交
        session = factory.openSession(true);

        // 修改隔离级别为RU
//        session = factory.openSession(TransactionIsolationLevel.READ_UNCOMMITTED);

        EmpDao mapper = session.getMapper(EmpDao.class);
//        mapper.deleteById(7);

        Emp emp = mapper.findById(1);
        System.out.println(emp);
        session.commit();
        session.close();
    }
}

1.2.2 MANAGED

  • MANAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。

通过Configuration配置类可以查看MANAGED的全称:ManagedTransactionFactory

我们翻开ManagedTransactionFactory类的源码查看:

在这里插入图片描述

发现ManagedTransactionFactory在提交和回滚上是没有做任何操作的;因此如果事务管理器设置为了MANAGED,那么事务将不支持回滚功能;

测试:

package com.dfbz.demo;

import com.dfbz.dao.EmpDao;
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.InputStream;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_Managed {
    InputStream is;
    SqlSessionFactoryBuilder sessionFactoryBuilder;
    SqlSessionFactory factory;
    SqlSession session ;

    @Before
    public void before() throws Exception{
        is = Resources.getResourceAsStream("SqlMapConfig.xml");

        sessionFactoryBuilder = new SqlSessionFactoryBuilder();

        factory = sessionFactoryBuilder.build(is);

    }

    @After
    public void after() throws Exception{
        session.close();
        is.close();
    }


    @Test
    public void test1() throws Exception{
        /*
            autoCommit设置的值对于Managed事务管理器无效
            因为Managed提交或者回滚事务都不会由MyBatis来处理,交给默认的连接来处理(默认自动提交)
         */
        session = factory.openSession(false);

        EmpDao mapper = session.getMapper(EmpDao.class);
        
        mapper.deleteById(1);
        session.close();
    }
}

观察数据库,发现数据直接提交,而不是回滚

1.2.3 自定义事务管理器

我们自定义事务管理器需要实现TransactionFactory接口:

在这里插入图片描述

1)定义事务处理器(Transaction):
package com.dfbz.transaction;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class MyTransaction implements Transaction {

    protected Connection connection;
    protected DataSource dataSource;
    protected TransactionIsolationLevel level;
    protected boolean autoCommit;

    public MyTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        dataSource = ds;
        level = desiredLevel;
        autoCommit = desiredAutoCommit;
    }

    public MyTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {

        if(connection==null){
            
            // 从连接池中获取一个连接
            this.connection=dataSource.getConnection();
            
            // 设置事务的提交方式
            this.connection.setAutoCommit(this.autoCommit);
        }
        
        if (this.level != null) {
            
            // 设置事本次连接的事务隔离级别
            this.connection.setTransactionIsolation(this.level.getLevel());
        }

        return this.connection;
    }

    // 只有自动提交手动设置为false了,在session.commit()时才会调用事务管理器的提交方法来提交事务
    @Override
    public void commit() throws SQLException {				

        connection.commit();
        System.out.println("有一个事务提交啦!");
    }

    @Override
    public void rollback() throws SQLException {
        connection.rollback();
        System.out.println("有一个事务回滚啦!");
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }

    @Override
    public Integer getTimeout() throws SQLException {
        return null;
    }
}
2)定义事务工厂(MyTransactionFactory):
package com.dfbz.transaction;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class MyTransactionFactory implements TransactionFactory {
    @Override
    public Transaction newTransaction(Connection conn) {
        return new MyTransaction(conn);
    }

    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new MyTransaction(dataSource,level,autoCommit);
    }
}
3)注册事务管理器:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

緑水長流*z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值