Javaweb-JDBC进阶-事务与连接池详细学习笔记

Javaweb-JDBC进阶-事务与连接池

事务

银行转账!张三转1000块到李四的账户,这其实需要两条SQL语句:

给张三的账户减去1000元

给李四的账户加上1000元

如果在第一条SQL语句执行成功后,在执行第二条SQL语句之前,程序被中断了(可能是抛出了某个异常,也可能是其他什么原因),那么李四的账户没有加上10000元,而张三却减去了10000元,这肯定是不行的!

你现在可能已经知道什么是事务了吧!事务中的多个操作,要么同时成功,要么同时失败!不可能存在成功一半的情况!也就是说给张三的账户减去10000元如果成功了,那么给李四的账户加上10000元的操作也必须是成功的;否则给张三减去10000元,以及给李四加上10000元都是失败的!

事务四大特性(ACID)

  • 原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败
  • 一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变
  • 隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。
  • 持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据

Oracle中的事务

在默认情况下,每执行一条增、删、改SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务

结束事务:

  • commit(提交):sql语句所作的影响持久化到数据库中
  • rollback(回滚):取消事务所有操作,回到原点

JDBC事务

Connection的三个方法与事务相关:

  • setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值就是true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置false,那么就相当于开启了事务了
  • commit():提交结束事务
  • rollback():回滚结束事务

事务一致性代码示例

测试用表,用于新建表

drop table ACCOUNT cascade constraints;

/*==============================================================*/
/* Table: ACCOUNT                                               */
/*==============================================================*/
create table ACCOUNT  (
   id                 NUMBER(10),
   name               VARCHAR(20),
   sal                NUMBER(10)
);

代码

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.Reader;
import java.sql.*;

public class TestJDBC {
    public static void main(String[] args) {


        /*定义url*/
        String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; //@后面是主机ip+端口号+实例名,在此均采用默认
        /*用户名与密码*/
        String username ="scott" ;
        String password ="tiger";

        /*获取连接Connection*/
        Connection conn = null  ;

        /*定义数据库的sql执行对象*/
//        Statement stmt = null ;
        PreparedStatement pstmt = null ;
        PreparedStatement pstmt1 = null ;

        /*定义查询语句返回的结果集*/
        ResultSet rs = null ;

        /*更新操作*/
        String sql  = "update account t set t.sal = t.sal- ? where t.id = ?" ;
        String sql1 = "update account t set t.sal = t.sal+ ? where t.id = ?" ;

        try {
            /*注册驱动*/
            Class.forName("oracle.jdbc.OracleDriver");
            conn = DriverManager.getConnection(url, username, password);

            /*开启事务*/
            conn.setAutoCommit(false);
            
            pstmt = conn.prepareStatement(sql) ;
            pstmt1 = conn.prepareStatement(sql1) ;

            pstmt.setInt(1,10000);
            pstmt.setInt(2,1);
            int count  = pstmt.executeUpdate();
            System.out.println(count);

            /*模拟异常*/
            if (true)
                throw new Exception() ;

            pstmt1.setInt(1,1000);
            pstmt1.setInt(2,2);
            int count1  = pstmt1.executeUpdate();
            
            /*如果没有异常,事务提交*/
            conn.commit();

        } catch (Exception e) {
            e.printStackTrace();

            try {
                /*异常情况下事务回滚*/
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {   /*关闭资源*/
            try {

                if (rs!=null)//防止空指针
                    rs.close();
                if (conn!=null) //防止空指针
                    conn.close();
                if (pstmt!=null)//防止空指针
                    pstmt.close();
                if (pstmt1!=null)//防止空指针
                    pstmt1.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

事务的并发读问题

  • 脏读:读取到另一个未提交数据的事务
时间 转账事务A 取款事务B
T1 开始事务
T2 开始事务
T3 查询账户余额为1000元
T4 取出500元把余额改为500元
T5 查看账户余额为500元(脏读)
  • 虚读(幻读):读到已提交的事务
时间 统计金额事务A 转账事务B
T1 开始事务
T2 开始事务
T3 统计总存款数为10000元
T4 新增一个存款账户,存款为100元
T5 提交事务
T6 再次统计总存款数为10100元
  • 不可重复读:读到已提交的数据
时间 取款事务A 转账事务B
T1 开始事务
T 开始事务
T3 查询账户余额为1000元
T4 查询账户余额为1000元
T5 取出100元,把余额改为900元
T6 提交事务
T7 查询账户余额为900元(与T4读取的一不一致)

不可重复读与虚读有些相似,都是两次查询的结果不同。后者是查询到了另一个事务已提交的新插入数据,而前者是查询到了另一个事务已提交的更新数据

四大隔离级别

隔离级别 脏读 不可重复读 虚读
READ UNCOMMITTED 允许 允许 允许
READ COMMITTED 不允许 允许 允许
REPEATABLE READ 不允许 不允许 允许
SERIALIZABLE 不允许 不允许 不允许

1 SERIALIZABLE(串行化)

当数据库系统使用SERIALIZABLE隔离级别时,一个事务在执行过程中完全看不到其他事务对数据库所做的更新。当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。

2 REPEATABLE READ(可重复读)

当数据库系统使用REPEATABLE READ隔离级别时,一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。

3 READ COMMITTED(读已提交数据)

当数据库系统使用READ COMMITTED隔离级别时,一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新

4 READ UNCOMMITTED(读未提交数据)

当数据库系统使用READ UNCOMMITTED隔离级别时,一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新

你可能会说,选择SERIALIZABLE,因为它最安全!没错,它是最安全,但它也是最慢的!而且也最容易产生死锁。四种隔离级别的安全性与性能成反比!最安全的性能最差,最不安全的性能最好!

MySQL的默认隔离级别为REPEATABLE READ

Oracle数据库支持READ COMMITTEDSERIALIZABLE这两种事务隔离级别。不支持脏读

Oracle的默认隔离级别是READ COMMITTED

JDBC设置隔离级别

con. setTransactionIsolation(int level)

参数可选值如下:

  • Connection.TRANSACTION_READ_UNCOMMITTED

  • Connection.TRANSACTION_READ_COMMITTED

  • Connection.TRANSACTION_REPEATABLE_READ

  • Connection.TRANSACTION_SERIALIZABLE

事务总结:

  • 事务的特性:ACID

  • 事务开始边界与结束边界:开始边界(con.setAutoCommit(false)),结束边界(con.commit()或con.rollback())

  • 事务的隔离级别: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多个事务并发执行时才需要考虑并发事务。

数据库连接池

概念

用池来管理Connection,这可以重复使用Connection,有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了

JDBC数据库连接池接口(DataSource)

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商可以让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池

DBCP(DataBase Connection Pool)

DBCP是Apache提供的一款开源免费的数据库连接池

Hibernate3.0之后不再对DBCP提供支持!因为Hibernate声明DBCP有致命的缺欠!DBCP因为Hibernate的这一毁谤很是生气,并且说自己没有缺欠

导包下载地址

java使用DBCP

创建dbcp.properties文件并配置


driverClassName=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
username=scott
password=tiger

initialSize=10
maxActive=5
maxIdle=5
minIdle=3
maxWait=-1

initialSize :连接池启动时创建的初始化连接数量(默认值为0)
maxActive :连接池中可同时连接的最大的连接数(默认值为8)
maxIdle:连接池中最大的空闲的连接数超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个)
minIdle:连接池中最小的空闲的连接数
maxWait :最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为60000ms,避免因线程池不够用,而导致请求被无限制挂起)

代码

import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.Reader;
import java.sql.*;
import java.util.Properties;


public class TestJDBC {
    public static void main(String[] args) {

/*        *//*定义url*//*
        String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl"; //@后面是主机ip+端口号+实例名,在此均采用默认
        *//*用户名与密码*//*
        String username ="scott" ;
        String password ="tiger";*/

        /*定义数据库的sql执行对象*/
        PreparedStatement pstmt = null ;
        /*定义输入流*/
        InputStream in =null ;
        /*定义配置文件*/
        Properties prop = null ;
        /*定义连接*/
        Connection conn = null ;


        /*更新操作*/
        String sql  = "update account t set t.sal = t.sal- ? where t.id = ?" ;

        try {

            /*读取连接池配置文件*/
            in = TestJDBC.class.getClassLoader().getResourceAsStream("dbcp.properties");
            /*创建配置文件对象*/
            prop = new Properties( );
            /*加载配置文件*/
            prop.load(in);
            /*创建数据库连接池*/
            DataSource ds = BasicDataSourceFactory.createDataSource(prop);
            /*从连接池获得连接*/
            //conn = DriverManager.getConnection(url, username, password);//pass
            conn =  ds.getConnection() ;
            /*预编译执行sql语句*/
            pstmt = conn.prepareStatement(sql) ;
            pstmt.setInt(1,100);
            pstmt.setInt(2,1);
            int count = pstmt.executeUpdate();
            System.out.println(count);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {   /*关闭资源*/
            try {
                if (in!=null)//防止空指针
                    in.close();
                if (conn!=null) //防止空指针
                    conn.close();
                if (pstmt!=null)//防止空指针
                    pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

发布了161 篇原创文章 · 获赞 93 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/JunSIrhl/article/details/103692459
今日推荐