文章目录
一、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对数据源的分类:
我们使用的POOLED
、UNPOOLED
、JNDI
都是使用连接池的别名,我们可以通过查看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
:连接池中最大的活跃连接数量,默认值:10poolMaximumIdleConnections
:连接池最大的空闲连接数,默认值:5poolMaximumCheckoutTime
:获取连接的超时时间,超出该时间还未获取到连接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 中有两种类型的事务管理器JDBC
和MANAGED
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)注册事务管理器:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131671.html