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

* @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"?>
<delete id="deleteById">
    delete from emp where id=#{id}
</delete>

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

#### 1.1.2 MyBatis内置数据源


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



<?xml version="1.0" encoding="utf-8" ?>
<!--日志配置-->
<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>

![在这里插入图片描述](https://img-blog.csdnimg.cn/b131eb87547e46bb82f198a457d24c31.png#pic_center)



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


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/ae7649cb3a994d16a42b16ff761f4ac1.png#pic_center)




---


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/d717fc2494c7429cbef8d2ba775b34e4.png#pic_center)


别名对应的类:


* POOLED:PooledDataSourceFactory—>PooledDataSource
* UNPOOLED:UnpooledDataSourceFactory—>UnpooledDataSource
* JNDI:JndiDataSourceFactory—>JndiDataSource


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/889cf39c2b0d4d7ea3b8d0bff50e1e6a.png#pic_center)


##### 1)UNPOOLED


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


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


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


配置SqlMapConfig.xml:



        <!--

事务隔离级别:
NONE(0): 不支持事务(MySQL等数据库并不支持该选项)
READ_UNCOMMITTED(1): 读未提交
READ_COMMITTED(2): 读已提交
REPEATABLE_READ(4): 可重复读
SERIALIZABLE(8): 串行化
–>

        <!--SQL语句的执行超时时间-->
        <property name="defaultNetworkTimeout" value="20000"></property>
    </dataSource>
</environment>

* 测试类:



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去创建一个新的连接,并没有对连接进行连接池的管理;


![在这里插入图片描述](https://img-blog.csdnimg.cn/fb176c423df9474e9ae37347309771a2.png#pic_center)


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/43cf154ede29471f8ace434a63c5bfdd.png#pic_center)


##### 2)POOLED


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


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


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


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


配置SqlMapConfig.xml:



    <!--采用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>

* 测试类:



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`类的成员变量如下:


![在这里插入图片描述](https://img-blog.csdnimg.cn/f754c92807c84793bded804b917ec665.png#pic_center)


`PoolState`:


![在这里插入图片描述](https://img-blog.csdnimg.cn/31b91595c40647969ac48ae8f6e63fce.png#pic_center)


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/86bd54b704574a8a81499aef64c89a17.png#pic_center)


##### 3)JNDI


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




#### 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;
}

}


* 注册我们自己的连接池:



    <!--注册我们自己的连接池-->
    <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>

* 测试类:



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 中有两种类型的事务管理器`JDBC`和`MANAGED`


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


在SqlMapConfig.xml中选择:





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


查看JDBCTransactionManager部分源码:


![在这里插入图片描述](https://img-blog.csdnimg.cn/34cc51b0ada549dbb341a1a9409bb636.png#pic_center)


* 测试类:



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`类的源码查看:


![在这里插入图片描述](https://img-blog.csdnimg.cn/816e478c7a73406c83bb3ae91d3b98c2.png#pic_center)


发现`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`接口:


![在这里插入图片描述](https://img-blog.csdnimg.cn/18133b437cfc49f3a3b195b1f05ff871.png#pic_center)


##### 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
  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值