jdbc2
一、事务
(一)事务的概念
- 什么是事务?
- 事务是应用程序中一个完整的业务逻辑,(包含多个小的单元,每一个小的单元分别对数据库中的数据进行crud操作。)我们通过事务保证所有的小单元,要么同时成功,要么同时失败。也就是说事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
- 事务的特性(ACID):
- (1)原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,不可分割。
- (2)一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
- (3)隔离性(Isolation):隔离性是当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,即多个并发事务之间互不影响。
- (4)持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
(二)MySQL的事务操作
- 管理自定义事务的语句:
- 开始事务: start transaction
- 提交事务:commit
- 回滚事务:rollback
- 场景描述:
- 场景描述:模拟银行转账业务。
转账人 【 张三 】
收账人 【 凤儿 】
金 额 第一次模拟:【 500 】 第二次模拟:【 1000 】
表结构:
账户id 账户名username 账户金额[money]
p001 张三 1000
p002 凤儿 1000
- 场景描述:模拟银行转账业务。
- 操作步骤:
- 步骤一:创建库 bankdb:
- 步骤二:创建表account;
- 第三步插入原始数据:
- 第四步:模拟转账
- 张三给风儿转500
- 张三给风儿再转1000
(三)JDBC事务的操作
- 对于jdbc事务,默认是的自动提交事务的,如果需要手动完成事务过程,需要通过Connection将数据库事务的提交设置为手动提交。
- jdbc设置事务的步骤:
/**
* jdbc实现编程式事务
*
*/
public class TransactionTest {
/**
* 转账业务逻辑方法
* @param conn 数据库连接
* @param id 账户编号
* @param money 转账的钱数
* @return 是否转账成功
* @throws SQLException
*/
public boolean transferMoney(Connection conn,String id, int money) throws SQLException {
String sql = "update account set money = money + ? where id = ? ";
PreparedStatement pre = conn.prepareStatement(sql);
pre.setInt(1, money);
pre.setString(2, id);
int row = pre.executeUpdate();
return row > 0;
}
@Test //第一次模拟转账500
public void transfer500() {
//1、获取数据库连接
Connection conn = JDBCUtils.getConnection();
try {
//开启手动提交事务
conn.setAutoCommit(false);
boolean bo = transferMoney(conn,"p001",-500);
System.out.println("张三转账风儿500是否成功" + bo);
boolean flg = transferMoney(conn,"p002",500);
System.out.println("风儿收钱500是否成功" + flg);
//转账过程顺利,提交事务
conn.commit();
} catch (SQLException e) {
try {
conn.rollback(); //回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
@Test //第二次模拟转账1000
public void transfer1000() {
//1、获取数据库连接
Connection conn = JDBCUtils.getConnection();
try {
//开启手动提交事务
conn.setAutoCommit(false);
boolean bo = transferMoney(conn,"p001",-1000);
System.out.println("张三转账风儿500是否成功" + bo);
boolean flg = transferMoney(conn,"p002",1000);
System.out.println("风儿收钱500是否成功" + flg);
//转账过程顺利,提交事务
conn.commit();
} catch (SQLException e) {
try {
conn.rollback(); //回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
- 1、开启数据库事务,即设置为手动事务
connection.setAutosetAutoCommit(false);默认的是true表示自动提交事务,如果需要手动提交需要设置为false. - 2、提交事务的方法: connection.commit();
- 3、回滚事务: connection.rollback();
(四)事务的隔离级别
https://www.cnblogs.com/ubuntu1/p/8999403.html
- 事务的四种隔离级别由低到高:
未提交读<已提交读<可重复读<可串行化 - 事务引发问题说明:
- 1)脏读
- 指一个事务读取到另一个事务未提交的数据。
- 2)不可重复读
- 指一个事务对同一行数据重复读取两次,但得到的结果不同。
- 3)虚读/幻读
- 指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。
- 4)丢失更新
- 指两个事务同时更新一行数据,后提交(或撤销)的事务将之前事务提交的数据覆盖了。
- 事务的隔离级别说明:
- 1)Read Uncommitted(读未提交)
- 一个事务在执行过程中,既可以访问其他事务未提交的新插入的数据,又可以访问未提交的修改数据。如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。
- 2)Read Committed(读已提交)
- 一个事务在执行过程中,既可以访问其他事务成功提交的新插入的数据,又可以访问成功修改的数据。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别可有效防止脏读。
- 3)Repeatable Read(可重复读取)
- 一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但不可以访问成功修改的数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。此隔离级别可有效防止不可重复读和脏读。
- 4)Serializable(可串行化)
- 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。
二、数据库连接池
Database Connection Pool
(一)连接池简介
- 为什么使用连接池?
- 数据库连接是一种关键的有限的昂贵的资源,传统数据库连接每发出一个请求都要创建一个连接对象,使用完直接关闭不能重复利用;关闭资源需要手动完成,一旦忘记会造成内存溢出;请求过于频繁的时候,创建连接极其消耗内存;而且一旦高并发访问数据库,有可能会造成系统崩溃。为了解决这些问题,我们可以使用连接池。
- 1。是连接可以重复利用减少创建连接的过程增加程序的效率
- 2.、可以通过连接池来控制创建连接的最大数量将其控制在合理范围内,避免内存溢出或者连接太多程序异常。
- 数据库连接是一种关键的有限的昂贵的资源,传统数据库连接每发出一个请求都要创建一个连接对象,使用完直接关闭不能重复利用;关闭资源需要手动完成,一旦忘记会造成内存溢出;请求过于频繁的时候,创建连接极其消耗内存;而且一旦高并发访问数据库,有可能会造成系统崩溃。为了解决这些问题,我们可以使用连接池。
- 连接池原理:
- 数据库连接池负责分配、管理和释放数据库连接,它的核心思想就是连接复用,通过建立一个数据库连接池,这个池中有若干个连接对象,当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。一旦连接池中的连接对象被用完了,判断连接对象的个数是否已达上限,如果没有可以再创建新的连接对象,如果已达上限,用户必须处于等待状态,等待其他用户释放连接对象,直到连接池中有被释放的连接对象了,这时候等待的用户才能获取连接对象,从而操作数据库。这样就可以使连接池中的连接得到高效、安全的复用,避免了数据库连接频繁创建、关闭的开销。这项技术明显提高对数据库操作的性能。
- 使用连接池的优势:
- (1)程序启动时提前创建好连接,不用用户请求时创建,给服务器减轻压力;
- (2)连接关闭的时候不会直接销毁connection,这样能够重复利用;
- (3)如果超过设定的连接数量但是还没有达到最大值,那么可以再创建;
- (4)如果空闲了,会默认销毁(释放)一些连接,让系统性能达到最优;
(二)手动实现连接池
- 实现步骤:
- 1、创建一个连接池的类
- 2、通过jdbcUtils来获取数据库连接
- 3、在初始化时创建一些连接放入数据库连接池等待使用
- 4、创建一个容器来存放数据库连接
- 5、需要提供一个获取连接的方法
- 6、需要一个外界归还连接的方法
- 代码实现:
package com.ujiuye.pool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import com.ujiuye.util.JDBCUtils;
/**
* 数据库连接池的类
*
*/
public class JDBCPools {
//存放连接的容器
private static List<Connection> pools = new LinkedList<Connection>();
//连接池的初始大小
private static int initsize = 3;
static {
putConn(initsize);
}
//存放连接到数据库连接池的方法
public static void putConn(int size) {
for (int i = 0; i < size; i++) {
pools.add(JDBCUtils.getConnection());
}
}
/**
* 外界获取数据库连接的方法
* @return 数据连接
*/
public static Connection getConnection() {
if(pools.isEmpty()) {// 判断连接池是否存在连接的方法
throw new RuntimeException("连接池连接已耗尽");
}else {
//将数据库中的连接从连接池取出
return pools.remove(0);
}
}
/**
* 归还连接到数据库连接池
* @param conn 数据库连接
* @param pre 预编译statement对象
* @param res 结果集对象
*/
public static void poolColse(Connection conn,PreparedStatement pre,ResultSet res) {
if(res != null) {
try {
res.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pre != null) {
try {
pre.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
//将用完的连接归还到数据库连接池
pools.add(conn);
}
}
}
- 问题:
- 1、自定义的连接池需要依赖JDBCUilts工具类
- 2、该连接池不能灵活配置连接池的连接数量
- 3、归还连接需要手动实现且功能单一,可配置型不强
(三)DBCP连接池
- 配置文件实例:
- DBCP是apache开源的一款小型数据库连接池,对并发支持不是很友好,主要针对中小型项目,是TomCat服务器内置的数据连接池。
- 使用步骤:
- 1、下载dbcp数据库连接池的jar。并导入项目类路径
- 2、编写dbcp的配置文件
- 3、获取连接池对象,从而获取连接
package com.ujiuye.pool;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.Test;
/**
* 通过DBCP数据库连接池来获取连接
*
*/
public class DBCPPools {
@Test
public void getDataSource() throws Exception {
//读取连接池的配置文件
InputStream in = DBCPPools.class.getClassLoader().getResourceAsStream("config/dbcp.properties");
//创建属性对象
Properties p = new Properties();
p.load(in); //将配置文件信息存放到属性对象中
//通过数据源工厂来创建一个数据库连接池
DataSource ds = BasicDataSourceFactory.createDataSource(p);
//从数据库连接池中获取连接
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
(四)c3p0连接池
- 配置文件实例:
- c3p0数据库连接池是针对小型项目的开源数据库连接池,主要使用spring和Hibernate 默认数据库连接池
- 1.下载并导包
- 2、创建配置文件
- 3、编写方法获取数据库连接池,然后获取连接
- 注意事项:
- 1、c3p0数据库连接池的配置会文件名称必须是c3p0.properties
- 2.c3p0的配置文件必须放在src路径下
- 3、c3p0的配置文件的键需要加上c3p0.的前缀
- 代码实现:
Druid 阿里巴巴开源的数据库连接
package com.ujiuye.pool;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
/**
* 通过DBCP数据库连接池来获取连接
*
*/
public class Druidpools {
@Test
public void getDataSource() throws Exception {
//读取连接池的配置文件
InputStream in = Druidpools.class.getClassLoader().getResourceAsStream("config/druid.properties");
//创建属性对象
Properties p = new Properties();
p.load(in); //将配置文件信息存放到属性对象中
//通过数据源工厂来创建一个数据库连接池
DataSource ds = DruidDataSourceFactory.createDataSource(p);
//从数据库连接池中获取连接
Connection conn = ds.getConnection();
System.out.println(conn);
}
}