jdbc事务处理和连接池

时间:2023-12-14 19:53:08

JDBC:

* JDBC概念:Java DataBase Connectivity(Java数据库连接)

SUN公司提供的一组连接数据库API.

* JDBC开发步骤:

* 1.注册驱动.

* 2.获得连接.

* 3.执行SQL.

* 4.释放资源.

* JDBC入门案例:

* JDBC的API详解:

* DriverManager:

* 管理驱动:

* Class.forName(“com.mysql.jdbc.Driver”);

* 获得连接:

* getConnection(String url,String username,String password);

* Connection:

* 创建执行SQL的对象:

* createStatement();

* prepareStatement(String sql);

* 管理事务:

* setAutoCommit(boolean flag);

* commit();

* rollback();

* Statement:

* 执行SQL语句

* executeQuery(String sql);

* executeUpdate(String sql);

* execute(String sql);

* 执行批处理

* addBatch(String sql);

* executeBatch();

* clearBatch();

* ResultSet:

* 结果集:

* 默认只能向下不可以修改.

* 多条记录:

while(rs.next()){

rs.getXXX();

}

* 一条记录:

if(rs.next()){

}

* 滚动结果集:(了解)

JDBC开发CRUD的操作:

* 资源释放:

* Connection的资源是非常稀有的,应该做到晚创建,早释放!

* 抽取工具类:

* 提取了一个配置文件db.properties

EE开发中的DAO模式:

* DAO模式主要用来解决持久层的问题!

* 操作数据库!

DAO模式编写了一个登陆案例:

* 页面---->Servlet---->UserService---->UserDao

* 演示了SQL注入漏洞:

* 解决:

* PreparedStatement:对SQL进行预编译!

大文件读写:

批处理:

1.1 JDBC中事务管理:

1.1.1 事务:

事务:指的是逻辑上一组操作!一组操作要么全都成功,要么全都失败!

1.1.2 MYSQL数据库中操作事务

MYSQL的数据库的事务是自动提交的!!!Oracle不是自动提交的!!!

MySQL数据库,写一个SQL就是一个事务!!!

创建一个表:

create database day18;

use day18;

create table account(

id int primary key auto_increment,

name varchar(20),

money double

);

insert into account values (null,'美美',10000);

insert into account values (null,'小边',10000);

insert into account values (null,'冠希',10000);

insert into account values (null,'白鸽',10000);

insert into account values (null,'小白',10000);

MYSQL中管理事务:

有两种方式管理事务:

一、使用命令方式:

* start transaction; --- 开启事务

* commit; --- 事务提交

* rollback; --- 事务回滚

二、设置MYSQL数据库参数:

* show variables like '%commit%';

+--------------------------------+-------+

| Variable_name | Value |

+--------------------------------+-------+

| autocommit | ON |

| innodb_commit_concurrency | 0 |

| innodb_flush_log_at_trx_commit | 1 |

+--------------------------------+-------+

* 设置自动提交参数为OFF(0) ON(1)

set autocommit = 0;

1.1.3 JDBC中使用事务:
JDBC的事务

通过Connection对象中的

* setAutoCommit(false);

* commit();/rollback();

案例:

@Test

// 冠希给美美转账 1000元

public void demo1(){

Connection conn = null;

PreparedStatement stmt = null;

try{

// 获得连接

conn = JDBCUtils.getConnection();

// 管理事务:事务不是自动提交!

conn.setAutoCommit(false);

// 编写一个SQL语句

String sql = "update account set money = money + ? where name = ?";

// 预编译SQL

stmt = conn.prepareStatement(sql);

// 设置参数:

// 扣去冠希的1000元

stmt.setDouble(1, -1000);

stmt.setString(2, "冠希");

stmt.executeUpdate();

// 如果现在没有事务的保证:当扣除冠希的1000元之后,会发生什么情况?

// 这个时候 冠希的钱就被扣除了,但是美美没有收到钱!!!

// 所以多条SQL要由事务保证:事务就是保证逻辑上的一组操作要么全都成功,要么全都失败!

int a = 10 / 0;

// 为美美增加1000元

stmt.setDouble(1, 1000);

stmt.setString(2, "美美");

stmt.executeUpdate();

// 提交事务:

conn.commit();

}catch(Exception e){

// 异常发生了:

try {

conn.rollback();

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JDBCUtils.release(stmt, conn);

}

}

JDBC事务的保存点:(了解)

保存点的作用:回滚的时候不用回滚到最初始的状态!!!

Connection中有

* setSavepoint() --- 设置一个保存点

* rollback(SavePoint savePoint); --- 事务回滚到保存点的位置

@Test

// 小边跟美美说你借我1w,设置一个保存点,我还你10w.但是小边的账号余额不足.回滚到保存点的位置!

public void demo2(){

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try{

// 获得连接

conn = JDBCUtils.getConnection();

// 开启事务:

conn.setAutoCommit(false);

// 编写一个sql语句.

String sql = "update account set money = money + ? where name = ?";

// 预编译SQL

stmt = conn.prepareStatement(sql);

// 扣除美美的1w元

stmt.setDouble(1, -10000);

stmt.setString(2, "美美");

stmt.executeUpdate();

// 给小边增加1w元

stmt.setDouble(1, 10000);

stmt.setString(2, "小边");

stmt.executeUpdate();

// 设置一个保存点

Savepoint savePoint = conn.setSavepoint();

// 小边还给美美10w

stmt.setDouble(1, -100000);

stmt.setString(2, "小边");

stmt.executeUpdate();

stmt.setDouble(1, 100000);

stmt.setString(2, "美美");

stmt.executeUpdate();

sql = "select * from account where name = ?";

stmt = conn.prepareStatement(sql);

// 设置参数

stmt.setString(1, "小边");

rs = stmt.executeQuery();

if(rs.next()){

if(rs.getDouble("money")<0){

conn.rollback(savePoint);

System.out.println("上当了!!!");

}

}

// 事务提交

conn.commit();

}catch(Exception e){

try {

conn.rollback();

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JDBCUtils.release(rs, stmt, conn);

}

}

1.1.4 事务的特性:(面试)

原子性:

* 强调的是事务的不可分割!

一致性:

* 事务执行的前后,数据的完整性保持一致!

隔离性:

* 一个事务的执行,不应该受到另一个事务的打扰!

持久性:

* 事务执行结束!数据就永久的保存数据库中!

如果不考虑事务的隔离性:引发哪些问题?

* 引发三种读的问题.

* 脏读 :一个事务读到了另一个事务未提交的数据!!!

* 不可重复读 :一个事务读到了另一个事务已经提交数据(另一个事务中做的是update操作),导致多次查询结果在一个事务中不一致!

* 虚读(幻读) :一个事务读到了另一个事务已经提交数据(另一个事务中做的是insert操作),导致在事务中做的统计的结果不一致!!

* 如何解决问题?

* 数据库提供了四个隔离级别解决三种读问题!!!

* read uncommitted :未提交读.那么脏读、不可重复读、虚读都是有可能发生!!

* read committed :已提交读.避免脏读。但是不可重复读和虚读是有可能发生!!

* repeatable read :重复读.避免脏读、不可重复读。但是虚读是有可能发生的!!

* serializable :串行的.避免脏读、不可重复读、虚读的发生!!

* 安全性:

read uncommitted < read committed < repeatable read < serializable

* 效率性:

read uncommitted > read committed > repeatable read > serializable

***** 数据库中使用的时候一般不会采用最低和最高的!!!

* MYSQL中使用的是repeatable read Oracle中read committed

通过命令查看事务的隔离级别:

* select @@tx_isolation;

设置数据库隔离级别:

* set session transaction isolation level 隔离级别;

演示脏读发生:

脏读:一个事务读到了另一个事务未提交的数据!!!

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别为read uncommitted

* set session transaction isolation level read uncommitted;

* 查看A窗口的隔离级别

* A:READ-UNCOMMITTED

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口中完成转账的代码:但是事务不要提交!

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

5.在A窗口中进行查询:

* select * from account;

* 发现钱已经到账了!!!脏读.(A窗口已经读到B窗口还没有提交的数据)

演示不可重复读的发生(避免脏读)

不可重复读:一个事务读到另一个事务已经提交的数据!(另一个事务update)导致当前的事务多次的查询结果不一致

* 避免脏读:设置隔离级别为read committed

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.在A窗口中设置数据库隔离级别为read committed

* set session transaction isolation level read committed;

* A:READ-COMMITTED

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口完成转账代码:

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

***** 先不提交事务!

5.在A窗口进行查询!

* select * from account;

* 这个时候钱没有到账!(避免脏读了 一个事务没有读到另一个事务未提交的数据)

6.在B窗口提交事务:

* commit;

7.在A窗口再次去查询!

* select * from account;

* 发现这次结果和上次的结果不一致(两次查询是在一个事务中的!):不可重复读.

演示避免不可重复读

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别:repeatable read

* set session transaction isolation level repeatable read;

* A:REPEATABLE-READ

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口完成转账:

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

事务没有提交

5.在A窗口查询:

* select * from account;

* 数据没有变化:避免脏读!

6.在B窗口提交事务:

* commit;

7.在A窗口再次查询:

* select * from account;

* 数据还是没有变化:避免了不可重复读!

演示隔离级别为serializable:

Serializable:串行的!可以避免脏读、不可重复读、虚读!

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别为 Serializable

* set session transaction isolation level Serializable;

* A:SERIALIZABLE

* B:REPEATABLE-READ

3.在两个窗口分别开启事务:

* start transaction;

4.在B窗口中插入一条记录:

* 事务不提交:

5.在A窗口中进行查询:

* select * from account;

* 屏幕卡住了:因为另一个事务没有提交.这个事务就不能执行,串行的!!!

1.1.5 JDBC中如何设置隔离级别

通过Connection对象的:

* setTransactionIsolation(int level) ;

Connection中有以下的常量:

static int TRANSACTION_READ_COMMITTED

指示不可以发生脏读的常量;不可重复读和虚读可以发生。

static int TRANSACTION_READ_UNCOMMITTED

指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。

static int TRANSACTION_REPEATABLE_READ

指示不可以发生脏读和不可重复读的常量;虚读可以发生。

static int TRANSACTION_SERIALIZABLE

指示不可以发生脏读、不可重复读和虚读的常量。

1.1.6 丢失更新:(扩展)

第一类丢失更新

A事务撤销时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。

第二类丢失更新

A事务提交时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。

解决丢失更新问题:

* 悲观锁:假设丢失更新一定存在!

* 使用的是数据库的一种锁机制:排他锁.

* 乐观锁:假设丢失更新不一定存在!

1.2 连接池

1.2.1 什么是连接池

连接池:就是一个装了很多连接一个容器.(内存中的一块空间,装了很多连接.)

1.2.2 使用连接池:

1.编写一个类 实现DataSource接口.

2.重写getConnection()的方法.

自定义连接池的代码:

public class MyDataSource implements DataSource{

private List<Connection> list = new ArrayList<Connection>();

// 提供一个构造方法:

public MyDataSource(){

// 创建3个连接

for (int i = 1; i <=3; i++) {

// 向list集合放入连接

list.add(JDBCUtils.getConnection());

}

}

// 从连接池中获得连接

public Connection getConnection() throws SQLException {

// 从连接池中获得连接.连接池的扩容

if(list.isEmpty()){

for (int i = 1; i <=3; i++) {

// 向list集合放入连接

list.add(JDBCUtils.getConnection());

}

}

Connection conn = list.remove(0);

return conn;

}

// 用完之后,不是销毁连接而是归还到连接池

public void addBack(Connection conn){

list.add(conn);

}

public PrintWriter getLogWriter() throws SQLException {

return null;

}

public void setLogWriter(PrintWriter out) throws SQLException {

}

public void setLoginTimeout(int seconds) throws SQLException {

}

public int getLoginTimeout() throws SQLException {

return 0;

}

public <T> T unwrap(Class<T> iface) throws SQLException {

return null;

}

public boolean isWrapperFor(Class<?> iface) throws SQLException {

return false;

}

public Connection getConnection(String username, String password)

throws SQLException {

return null;

}

}

****** 自定义连接池中在使用的使用!需要让程序员额外记住一些自定义的方法例如addBack();而且在构造的时候不能使用接口构造.

能不能有一种方式实现:使用接口构造连接池,而且在归还的时候不需要额外提供方法???

* 原来的Connection的close方法是将连接对象销毁!现在是否可以增强close方法,使方法的逻辑改变,归还连接而不是销毁连接?

* 增强Java中类的方法有几种方式?

* 继承 :

class Person{

public void run(){

System.out.println("跑步1000米...");

}

}

class SuperPerson extends Person{

@Override

public void run() {

// super.run();

System.out.println("跑步10000米...");

}

}

测试:

@Test

public void demo1(){

Person p = new SuperPerson();

p.run(); // 子类的方法.

}

***** 继承这种增强某个类的方法使用条件的:

* 必须能够控制这个类的构造!

* 装饰者模式 :

代码:

interface Bird{

public void fly();

}

// 被增强的类

class MaQue implements Bird{

public void fly() {

System.out.println("飞200米...");

}

}

// 增强的类

class DaMaQue implements Bird{

private Bird bird;

public DaMaQue(Bird bird){

this.bird = bird;

}

public void fly() {

bird.fly();

System.out.println("飞500米...");

}

}

测试:

@Test

public void demo1(){

// Bird bird = new MaQue();

// bird.fly();

Bird bird = new DaMaQue(new MaQue());// IO流

bird.fly();

}

***** 装饰者这种增强某个类的方法使用条件的:

* 1.增强类和被增强类实现相同的接口.

* 2.在增强的类中获得到被增强类的引用.

***** 缺点:

* 接口中的方法特别多!

* 动态代理 :

1.2.3 DBCP连接池:

DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。

使用DBCP连接池

1.引入jar包:

* commons-dbcp-1.4.jar

* commons-pool-1.5.6.jar

2.核心类:

* BasicDataSource

* 手动设置参数.

* BasicDataSourceFactory

* 采用配置文件的方式.

1.2.4 C3P0连接池:

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等

使用C3P0连接池

1.导入jar包:

* c3p0-0.9.1.2.jar

2.ComboPooledDataSource

1.2.5 Tomcat内置连接池:(JNDI技术.)

JNDI:Java Naming and Directory Interface,Java命名和目录接口.

* 对一个Java对象起一个名字.通过这个名称查找到该对象.

JNDI连接池:

* 需要有一个配置:<Context>标签的配置.

* <Context>标签:在Tomcat学习的时候使用.配置Tomcat的虚拟路径.

* <Context>标签可以配置在三个地方:

* Tomcat/conf/context.xml:可以被Tomcat下的所有的虚拟主机、虚拟路径访问!

* Tomcat/conf/Catalina/localhost/context.xml:可以Tomcat下的localhost虚拟主机下的所有路径访问!

* 当前的工程下/META-INF/context.xml:只能被当前的工程访问!

使用JNDI连接池:

* 1.在工程的/META-INF/创建一个context.xml

<Context>

<Resource name="jdbc/EmployeeDB" auth="Container"

type="javax.sql.DataSource" username="root" password="123"

driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day18"

maxActive="8" maxIdle="4"/>

</Context>

* 2.Tomcat维护这些连接.这些连接是由Tomcat服务器创建的.

* 必须在Tomcat/lib下copy一个数据库驱动包.

* 3.操作连接池这个类必须是运行在Tomcat服务器中的类!

* 运行在Tomcat中的类是Servlet!!!