JDBC 存储过程和事务管理

一、存储过程

1.什么是存储过程

存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。

2.存储过程的优点

(1)存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。

(2)当对数据库进行复杂操作时(如对多个表进行Update,Insert,Query,Delete时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。

(3)存储过程可以重复使用,可减少数据库开发人员的工作量

(4)安全性高,可设定只有某此用户才具有对指定存储过程的使用权

3.存储过程的缺点

(1)如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等,这时候估计比较繁琐了。
(2)可移植性差
由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。

4.存储过程数据库操作(Navicat 操作)

4.1调用入参的存储过程

(1)在navicat 的Student中 创建添加数据的函数(add_date)

(2)代码实例

public static void add(Student stu) throws Exception{

//通过工具类,获取数据库链接对象

Connection conn= DBUtil.getConn();

//创建 sql 语句

String sql = "insert Student (name,age,address) value (?,?,?)";

//创建 预加载 的sql 语句执行对象

PreparedStatement ptmt=conn.prepareStatement(sql);

//给名字赋值

ptmt.setString(1, stu.getName());

//给年龄赋值

ptmt.setInt(2, stu.getAge());

ptmt.setString(3, stu.getAddress());



//执行 sql 语句

ptmt.execute();



}

4.2调用出参存储过程

(1)创建记录数据条数的函数(get_record_count)

(2)代码实例

4.3调用无参存储过程

(1)创建一个打印所有数据的函数(select_all)

(2)代码实例

public class StudentProcedureDao {

/**

* 1、查询数据

*/

public ResultSet select_all() {

ResultSet rs = null;

// 1 获取数据库连接对象

Connection conn = DBUtil.getConn();

try {

// 2 创建存储过程调用对象

CallableStatement cs = conn.prepareCall("call select_all()");

// 3 执行存储过程

cs.execute();

// 4 获取结果集

rs = cs.getResultSet();

} catch (SQLException e) {

e.printStackTrace();

}

return rs;

}

二、事务管理

1、事务

事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换成另一种状态。
当操作序列中的所有操作都成功执行时,事务的状态未成功。当任一操作失败时,事务的状态为失败,此时必须将程序状态返回至事务未执行时的状态,即回滚。

2、四个特性

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

3、数据库的两种事务模式

  • 自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务。
  • 手动提交模式:必须由数据库客户程序显示指定事务开始边界和结束边界。 默认是自动提交模式

4、数据库读写中的并发事务问题

  • 脏读(Dirty Read):在事务的执行过程中,读取到了其他事务的 未提交 的数据,即读到了脏数据。
  • 不可重复读(Unrepeatable Read):在事务的执行过程中,读到了其他事务 修改后 的数据,换句话说在该事务中的不同时间点读取到了不一致的数据,即不可重复读。
  • 幻读/虚读(Phantom Read):在事务的执行过程中,读取到了其他事务对 记录数 修改后的数据,对同一张表的两次查询的 COUNT(*) 不一致。

5、不可重复读与幻读的区别:

  • 不可重复读:强调的是数据 内容 的不一致,主要针对 UPDATE 的修改。
  • 幻读:强调的是 记录数 的不一致,主要针对 INSERT/DELETE 的修改。

6、四个隔离级别

并发事务问题的产生原因是,未能遵守事务的隔离特性

  • 串行化(SERIALIZABLE):所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
  • 可重复读(REPEATABLE_READ):该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • 读已提交(READ_COMMITTED):该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • 读未提交(READ_UNCOMMITTED):该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。

不同隔离级别可避免的并发问题

隔离级别脏读不可重复读幻读
串行化YYY
可重复读YY
读已提交Y
读未提交

7、应用

当一个事务未完成之前,写操作不会真正写入数据库。同时需要注意,一个事务是以提交(commit)或者回滚(rollback)结束,否则会发生死锁,导致表被锁死,之后的任何正确操作都不会成功。

MySQL

查看隔离级别语句:SELECT @@TX_ISOLATION;
启用事务:START TRANSACTION;
设置隔离级别:set [global/session]  transaction isolation level;
提交:COMMIT;
回滚:ROLLBACK;
# 开启事务
START TRANSACTION;

# 执行事务 SQL 语句
# SQL1
UPDATE Test_Table
SET name = hhh
WHERE id = 1;
# SQL2
UPDATE Test_Table
SET name = xxx
WHERE id = 2;

# 提交事务
COMMIT;

JDBC

//设置隔离级别
conn.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED); 
//启用事务
conn.setAutoCommit(false);
//提交
conn.commit();
//回滚
conn.rollback();
try {  
    conn = jdbcTest.getConnection(sHostName, sPortNumber, sSid, userName, password);  
    conn.setTransactionIsolation(TRANSACTION_REPEATABLE_READ);  
    conn.setAutoCommit(false);  

    String SQL = "insert into Test_Table values('name1','age1')";  
    jdbcTest.excute(conn, SQL);  

    SQL = "insert into Test_Table values('name2','age2')";  
    jdbcTest.excute(conn, SQL);  

    conn.commit();  
} catch (SQLException e) {  
    if(conn!=null){    
        try {    
           conn.rollback();    
        } catch (SQLException e1) {    
           e1.printStackTrace();    
        }    
    }          
    e.printStackTrace();  
} finally {  
    if (conn != null) {  
        try {  
            conn.close();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }  
}