Mybatis(5)数据库连接池及事务

1、Mybatis连接池

​ Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的 SqlMapConfig.xml 配置文件中,通过 dataSource type=”pooled” 来实现 Mybatis 中连接池的配置。使用连接池可以减少因为获取连接而浪费的时间。

1.1、mybatis中的三种连接池配置方式

配置位置:

​ 主配置文件SqlMapConfig.xml中的dataSource标签,其中type属性就是表示采用何种连接池配置方式

type的三种类型

​ POOLEND:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的具体实现。

​ UNPOOLED:采用传统的连接获取方式,虽然也用javax.sql.DataSource规范,但它没有池的概念,有请求一个连接,才创建一个连接。

​ JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。 注意:如果不是web或者maven的war工程,是不能使用的。 我们开发中中使用的是tomcat服务器,采用连接池就是dbcp连接池。

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、关于POOLED与UNPOOLED两种连接池源码分析

1.3.1 、对UNPOOLED的源码分析

type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例

对UNPOOLED的实现类:

public class UnpooledDataSource implements DataSource

具体分析

首先找到getConnection

public Connection getConnection() throws SQLException {
return this.doGetConnection(this.username, this.password);
} public Connection getConnection(String username, String password) throws SQLException {
return this.doGetConnection(username, password);
}

可以看到两个方法都调用doGetConnection

private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (this.driverProperties != null) {
props.putAll(this.driverProperties);
} if (username != null) {
props.setProperty("user", username);
} if (password != null) {
props.setProperty("password", password);
} return this.doGetConnection(props);
}

该方法生成Properties props = new Properties();对象

该对象传给doGetConnection(props)

private Connection doGetConnection(Properties properties) throws SQLException {
this.initializeDriver();
Connection connection = DriverManager.getConnection(this.url, properties);
this.configureConnection(connection);
return connection;
}

首先调用initializeDriver();

**Connection connection = DriverManager.getConnection(this.url, properties);**这就是我们最开始创建连接的方式

关于initializeDriver();

private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(this.driver)) {
try {
Class driverType;
if (this.driverClassLoader != null) {
driverType = Class.forName(this.driver, true, this.driverClassLoader);
} else {
driverType = Resources.classForName(this.driver);
} Driver driverInstance = (Driver)driverType.newInstance();
DriverManager.registerDriver(new UnpooledDataSource.DriverProxy(driverInstance));
registeredDrivers.put(this.driver, driverInstance);
} catch (Exception var3) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + var3);
}
} }

**driverType = Class.forName(this.driver, true, this.driverClassLoader);**加载驱动

所以由此可看出UNPOOLED建立连接的方式就是我们最基础的方式获取连接,即通过加载驱动等方式来获取连接。

1.3.2、对POOLED源码分析

type=”POOLED”:MyBatis 会创建 PooledDataSource 实例

public class PooledDataSource implements DataSource

首先找到getConnection

public Connection getConnection() throws SQLException {
return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
} public Connection getConnection(String username, String password) throws SQLException {
return this.popConnection(username, password).getProxyConnection();
}

转到popConnection(username, password)

private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0; while(conn == null) {
PoolState var8 = this.state;
synchronized(this.state) {
if (!this.state.idleConnections.isEmpty()) {
conn = (PooledConnection)this.state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
conn = new PooledConnection(this.dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
++this.state.claimedOverdueConnectionCount;
this.state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
this.state.accumulatedCheckoutTime += longestCheckoutTime;
this.state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException var15) {
log.debug("Bad connection. Could not roll back");
}
}

对上述代码进行分析:protected final List idleConnections = new ArrayList();protected final List activeConnections = new ArrayList();

上述两个集合对象就是创建出来的两个连接池,一个是空闲池存放空闲的连接,一个是活动池存放被使用的连接。当一个请求进来时首先判断空闲池中是否为空(if (!this.state.idleConnections.isEmpty()) ),若不为空就分配一个连接;若为空查看活动池中是否达到最大数量,达到最大数量就把最先进入活动池的连接即使用时间最长的连接(long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();),进行一系列操作后清空返回。

                    conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
try {
if (!countedWait) {
++this.state.hadToWaitCount;
countedWait = true;
} if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
} long wt = System.currentTimeMillis();
this.state.wait((long)this.poolTimeToWait);
this.state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException var16) {
break;
}
}
} if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
} conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
this.state.activeConnections.add(conn);
++this.state.requestCount;
this.state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
} ++this.state.badConnectionCount;
++localBadConnectionCount;
conn = null;
if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
} throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
} if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
} throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
} else {
return conn;
}
}

2、事务

2.1、什么是事务

是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

数据库事务通常包含了一个序列的对数据库的读/写操作。包含有以下两个目的:

  1. 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
  2. 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

2.2、事务的四大特性ACID

并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

2.3、不考虑隔离性会产生的3个问题

并发事务导致的问题

第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。

脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

**幻读也叫虚读:一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。**幻读是事务非独立执行时发生的一种现象。

**不可重复读:**一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。

**Tips:**不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

**Tips:**幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

第二类丢失更新:是不可重复读的特殊情况。如果两个事物都读取同一行,然后两个都进行写操作,并提交,第一个事物所做的改变就会丢失。

2.4、数据库事务的隔离级别

事务的隔离级别有4种,由低到高分别为Read uncommittedRead committedRepeatable readSerializable 。而且,在事务的并发操作中可能会出现脏读不可重复读幻读。下面通过事例一一阐述它们的概念与联系。

**Read uncommitted(最低级别,任何情况都无法保证。)**读未提交,

顾名思义,就是一个事务可以读取另一个未提交事务的数据。

那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。

**Read committed(可避免脏读的发生。)**读提交

顾名思义,就是一个事务要等另一个事务提交后才能读取数据。

**Analyse:**这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。

那怎么解决可能的不可重复读问题?Repeatable read !

**Repeatable read(可避免脏读、不可重复读的发生。)**重复读,

就是在开始读取数据(事务开启)时,不再允许修改操作

**Analyse:**重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

那怎么解决幻读问题?Serializable!

Serializable(可避免脏读、不可重复读、幻读的发生。) 序列化

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

Tips:大多数数据库默认的事务隔离级别是Read committed,比如 Sql Server , Oracle。     Mysql 的默认隔离级别是Repeatable read。

**Tips:**隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。

**Tips:**设置数据库的隔离级别一定要是在开启事务之前。

2.5、Mybatis中有关事务的操作

可通过SqlSession session = factory.openSession(true);

openSession(true);给其传入一个布尔值进行决定是否是自动提交事务,还是手动提交事务,

该值默认false

2.5.1需要手动提交的分析

dao实现类中的操作

public void addUser(User user) {
SqlSession session = factory.openSession(true);
int i = session.insert("dao.IUserDao.addUser", user);
session.commit(); session.close();
}

对应测试类操作

@Test
public void testAddUser(){
//添加用户
User user = new User();
user.setUsername("wf");
user.setAddress("中国");
user.setSex("男");
user.setBirthday(new Date()); System.out.println(user); uesrdao.addUser(user); System.out.println(user); }

输出结果:

Opening JDBC Connection

Created connection 1075738627.

Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]

==> Preparing: select * from user where id=?

> Parameters: 48(Integer)

< Total: 1

Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]

Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]

Returned connection 1075738627 to pool.

这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进

行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在 CUD 操作中,

必须通过 sqlSession.commit()方法来执行提交操作。

2.5.2、需要自动提交的分析

dao实现类中的操作

public void addUser(User user) {
SqlSession session = factory.openSession(true);
int i = session.insert("dao.IUserDao.addUser", user);
session.commit(); session.close();
}

对应测试类操作

@Test
public void testAddUser(){
//添加用户
User user = new User();
user.setUsername("wf");
user.setAddress("中国");
user.setSex("男");
user.setBirthday(new Date()); System.out.println(user); uesrdao.addUser(user); System.out.println(user); }

输出结果:

Opening JDBC Connection

Created connection 633075331.

==> Preparing: insert into user (username,address,sex,birthday) value (?,?,?,?)

> Parameters: wf(String), 中国(String), 男(String), 2019-04-17 16:55:58.177(Timestamp)

< Updates: 1

==> Preparing: select last_insert_id();

> Parameters:

< Total: 1

Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@25bbf683]

Returned connection 633075331 to pool.

我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就

编程而言,设置为自动提交方式为 false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务

情况来决定提交是否进行提交。

SSM框架之Mybatis(5)数据库连接池及事务的更多相关文章

  1. spring+mybatis+c3p0数据库连接池或druid连接池使用配置整理

    在系统性能优化的时候,或者说在进行代码开发的时候,多数人应该都知道一个很基本的原则,那就是保证功能正常良好的情况下,要尽量减少对数据库的操作. 据我所知,原因大概有这样两个: 一个是,一般情况下系统服 ...

  2. 从0开始整合SSM框架-1.mybatis

    1.建立maven项目 2.首先引入mybatis需要引入的依赖(1).数据库驱动(2).mybatis核心包 <!-- mysql数据库驱动--> <!-- https://mvn ...

  3. SSM框架-初学Mybatis框架

    SSM(Spring+SpringMVC+Mybatis)是目前项目开发比较流行的一套组合框架,而Mybatis是负责数据库操作的那部分框架,具体 我也说不上来 传统的JDBC操作比较冗长而繁琐,而用 ...

  4. springboot activiti 整合项目框架源码 druid 数据库连接池 shiro 安全框架

    官网:www.fhadmin.org 工作流模块---------------------------------------------------------------------------- ...

  5. SSM框架之MyBatis框架实现简单的增删改查

    MyBatis框架介绍 MyBatis是一个优秀的数据持久层框架,在实体类和SQL语句之间建立映射关系是一种半自动化的ORM实现,其封装性要低于Hibernate,性能优越,并且小巧,简单易学,应用也 ...

  6. SSM框架-使用MyBatis Generator自动创建代码

    参考:http://blog.csdn.net/zhshulin/article/details/23912615 SSM搭建的时候用到MyBatis的代码自动生成的功能,由于MyBatis属于一种半 ...

  7. SSM框架之Mybatis(7)延迟加载、缓存及注解

    Mybatis(7)延迟加载.缓存及注解 1.延迟加载 延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据.延迟加载也称懒加载. **好处:**先从单表查询,需要时再从关联表去关 ...

  8. SSM框架之Mybatis(1)入门

    Mybatis(1)入门 1.mybatis的概述 mybatis是一个持久层框架,用java编写的. 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等 ...

  9. SSM框架——使用MyBatis Generator自动创建代码

    版权声明:本文为博主原创文章,未经博主允许不得转载. 这两天需要用到MyBatis的代码自动生成的功能,由于MyBatis属于一种半自动的ORM框架,所以主要的工作就是配置Mapping映射文件,但是 ...

随机推荐

  1. Ubuntu Server 16.04 LTS上怎样安装下载安装Nginx并启动

    场景 Linux-安装 Ubuntu Server 16.04 X64(图文教程详细版): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...

  2. wx-all

    学习这部分时也学了不少代码,一段一段往这上面写实在有点~~~ 直接把代码部署到代码该在的地方吧,哈哈哈 这是网址 把代码放这上面就好多了,不怕代码丢,还可保证代码完整,嘻嘻嘻~~~~~~

  3. Hack the Zico2 VM (CTF Challenge)

    下载链接: Download this VM here: https://download.vulnhub.com/zico/zico2.ova 端口扫描: ╰─ nmap -p1-65535 -sV ...

  4. 利用百度AI快速开发出一款“问答机器人”并接入小程序

    先看实现效果: 利用百度UNIT预置的智能问答技能和微信小程序,实现语音问答机器人.这里主要介绍小程序功能开发实现过程,分享主要功能实现的子程序模块,都是干货! 想了解UNIT预置技能调用,请参看我之 ...

  5. 「STM32 」IIC通讯原理及其实验

    I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工 ...

  6. CentOS自行编译升级Git

    上一篇升级Git的方式是通过更改yum的源,然后通过yum来安装,那么对于喜欢折腾的人来说,怎么通过Git的源代码自行安装呢? 我安装的是CentOS-7-x86_64-1908,自带的git是1.8 ...

  7. (办公)记事本_Linux常用的文件操作命令

    参考尚硅谷的谷粒学院的linux教程:http://www.gulixueyuan.com/course/300/task/7080/show 好吧,其实一个命令他要是讲超过20分钟,我就去看菜鸟教程 ...

  8. cinder安装与配置

    cinder是openstack中提供块存储服务的组件,主要是为虚拟机实例提供虚拟磁盘. 通过某种协议(SAS,SCSI,SAN,iSCSI等)挂接裸硬盘,然后分区.格式化创建的文件,或者直接使用裸硬 ...

  9. CodeForces - 158C(模拟)

    题意 https://vjudge.net/problem/CodeForces-158C 你需要实现类似 Unix / Linux 下的 cd 和 pwd 命令. 一开始,用户处于根目录 / 下. ...

  10. js new Date 创建时间默认是8点

    起因 最近在写一个页面,需要用到时间控制.然后我通过new Date()传入日期字符串创建了一个对象,并与当前时间做时间戳比较,结果12点刚过,就出问题了.举个栗子 // 假设当前时间是2019年12 ...