JDBC之事务、连接池、dbutils

来源:互联网 发布:u9数据字典怎么用 编辑:程序博客网 时间:2024/05/18 02:13

事务


在实际程序开发中,因业务的不同,总是会涉及到对数据库的多次操作。例如:银行转账!

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

l  给张三的账户减去10000元;

l  给李四的账户加上10000元。

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

所以事务就是保证对数据库的多个操作,要么完全成功,要么完全失败。

总结一句话不成功便成仁


事务的四大特性(ACID)

l  原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。

l  一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和应该是不变的。

l  隔离性(Isolation):隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。

l  持久性(Durability):一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。


Mysql中的事务:

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

l  开启事务:starttransaction

l  结束事务:commit(提交)rollback(回滚)

下面演示zs给li转账10000元的示例:

START TRANSACTION;

UPDATE account SET balance=balance-10000WHERE id=1;

UPDATE account SET balance=balance+10000WHERE id=2;

ROLLBACK

执行结果:回滚结束,事务执行失败。

------------------------------------------------------------------------------------

START TRANSACTION;

UPDATE account SET balance=balance-10000WHERE id=1;

UPDATE account SET balance=balance+10000WHERE id=2;

COMMIT

执行结果:提交结束,事务执行成功

------------------------------------------------------------------------------------

START TRANSACTION;

UPDATE account SET balance=balance-10000WHERE id=1;

UPDATE account SET balance=balance+10000WHERE id=2;

quit

执行结果:退出,mysql会自动回滚。

----------------------------------------------------------


jdbc中的事务

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

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

[java] view plain copy
  1. public void transfer(boolean b) {  
  2.     Connection con = null;  
  3.     PreparedStatement pstmt = null;  
  4.       
  5.     try {  
  6.         con = JdbcUtils.getConnection();  
  7.         //手动提交,开启事务  
  8.         con.setAutoCommit(false);  
  9.           
  10.         String sql = "update account set balance=balance+? where id=?";  
  11.         pstmt = con.prepareStatement(sql);  
  12.           
  13.         //操作  
  14.         pstmt.setDouble(1, -10000);  
  15.         pstmt.setInt(21);  
  16.         pstmt.executeUpdate();  
  17.           
  18.         // 在两个操作中抛出异常  
  19.         if(b) {  
  20.             throw new Exception();  
  21.         }  
  22.           
  23.         pstmt.setDouble(110000);  
  24.         pstmt.setInt(22);  
  25.         pstmt.executeUpdate();  
  26.           
  27.         //提交事务  
  28.         con.commit();  
  29.     } catch(Exception e) {  
  30.         //回滚事务  
  31.         if(con != null) {  
  32.             try {  
  33.                 con.rollback();  
  34.             } catch(SQLException ex) {}  
  35.         }  
  36.         throw new RuntimeException(e);  
  37.     } finally {  
  38.         //关闭  
  39.         JdbcUtils.close(con, pstmt);  
  40.     }  
  41. }  



jdbc中的保存点

保存点是JDBC3.0的东西!当要求数据库服务器支持保存点方式的回滚。可以通过boolean b = con.getMetaData().supportsSavepoints(); 这个方法进行效验。

保存点的作用是允许事务回滚到指定的保存点位置。在事务中设置好保存点,然后回滚时可以选择回滚到指定的保存点,而不是回滚整个事务!

注意,回滚到指定保存点并没有结束事务!!!只有回滚了整个事务才算是结束事务了!

Connection 类设置保存点,以及回滚到指定保存点的方法:

  1. 设置保存点 :Savepoint.setSavepoint();//使用到Savepoint
  2. 回滚到指定保存点 :voidrollback(Savepoint);

[java] view plain copy
  1. /* 
  2.  * 李四对张三说,如果你给我转1W,我就给你转100W。 
  3.  * ========================================== 
  4.  *  
  5.  * 张三给李四转1W(张三减去1W,李四加上1W) 
  6.  * 设置保存点! 
  7.  * 李四给张三转100W(李四减去100W,张三加上100W) 
  8.  * 查看李四余额为负数,那么回滚到保存点。 
  9.  * 提交事务 
  10.  */  
  11. @Test  
  12. public void fun() {  
  13.     Connection con = null;  
  14.     PreparedStatement pstmt = null;  
  15.       
  16.     try {  
  17.         con = JdbcUtils.getConnection();  
  18.         //手动提交  
  19.         con.setAutoCommit(false);  
  20.           
  21.         String sql = "update account set balance=balance+? where name=?";  
  22.         pstmt = con.prepareStatement(sql);  
  23.           
  24.         //操作1(张三减去1W)  
  25.         pstmt.setDouble(1, -10000);  
  26.         pstmt.setString(2"zs");  
  27.         pstmt.executeUpdate();  
  28.           
  29.         //操作2(李四加上1W)  
  30.         pstmt.setDouble(110000);  
  31.         pstmt.setString(2"ls");  
  32.         pstmt.executeUpdate();  
  33.           
  34.         // 设置保存点  
  35.         Savepoint sp = con.setSavepoint();  
  36.           
  37.         //操作3(李四减去100W)  
  38.         pstmt.setDouble(1, -1000000);  
  39.         pstmt.setString(2"ls");  
  40.         pstmt.executeUpdate();        
  41.           
  42.         //操作4(张三加上100W)  
  43.         pstmt.setDouble(11000000);  
  44.         pstmt.setString(2"zs");  
  45.         pstmt.executeUpdate();  
  46.           
  47.         //操作5(查看李四余额)  
  48.         sql = "select balance from account where name=?";  
  49.         pstmt = con.prepareStatement(sql);  
  50.         pstmt.setString(1"ls");  
  51.         ResultSet rs = pstmt.executeQuery();  
  52.         rs.next();  
  53.         double balance = rs.getDouble(1);  
  54.      <span style="white-space:pre">       </span>//如果李四余额为负数,那么回滚到指定保存点  
  55.         if(balance < 0) {  
  56.             con.rollback(sp);<span style="white-space:pre">           </span>//发现李四余额小于0,回滚到指定还原点!即撤销了李四给张三转账100万的操作  
  57.             System.out.println("张三,你上当了!");  
  58.         }  
  59.           
  60.         //提交事务  
  61.         con.commit();<span style="white-space:pre">                   </span>//注意,一定要提交事务,因为回滚到指定保存点不会结束事务!保存点之前的操作没有被回滚,只能提交了才能真正把没有回滚的操作执行了。  
  62.     } catch(Exception e) {  
  63.         //回滚事务  
  64.         if(con != null) {  
  65.             try {  
  66.                 con.rollback();  
  67.             } catch(SQLException ex) {}  
  68.         }  
  69.         throw new RuntimeException(e);  
  70.     } finally {  
  71.         //关闭  
  72.         JdbcUtils.close(con, pstmt);  
  73.     }  
  74. }  


事务的隔离级别

我也没搞清楚。我觉得没有必要死记。在这就说说我的理解:我认为就是控制并发事务问题的东东。

五大并发事务问题:两类更新,三类读

 1   脏读:读取到另一个事务未提交数据;

 2   不可重复读:两次读取不一致;

 3   幻读(虚读):读到另一事务已提交数据。

例子:

1  脏读(dirty read):读到未提交更新数据

 

时间

转账事务A

取款事务B

T1

 

开始事务

T2

开始事务

 

T3

 

查询账户余额为1000元

T4

 

取出500元把余额改为500元

T5

查看账户余额为500元(脏读)

 

T6

 

撤销事务,余额恢复为1000元

T7

汇入100元把余额改为600元

 

T8

提交事务

 

  

    A事务查询到了B事务未提交的更新数据,A事务依据这个查询结果继续执行相关操作。但是接着B事务撤销了所做的更新,这会导致A事务操作的是脏数据。(这是绝对不允许出现的事情)


2  虚读(幻读)(phantom read):读到已提交插入数据

 

时间

统计金额事务A

转账事务B

T1

 

开始事务

T2

开始事务

 

T3

统计总存款数为10000元

 

T4

 

新增一个存款账户,存款为100元

T5

 

提交事务

T6

再次统计总存款数为10100元

 

 

  A事务第一次查询时,没有问题,第二次查询时查到了B事务已提交的新插入数据,这导致两次查询结果不同。(在实际开发中,很少会对相同数据进行两次查询,所以可以考虑是否允许虚读)


3  不可重复读(unrepeatable read):读到已提交更新数据

 

时间

取款事务A

转账事务B

T1

 

开始事务

T2

开始事务

 

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(读未提交数据)

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


MySQL的默认隔离级别为REPEATABLE READ


连接池


可以理解为用来管理connection的容器。将connection交予池来管理,这可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。

当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。


JDBC的数据库连接池接口:DateSource

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


DBCP

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

连接池类  BaseicDataSource

如何使用:

1.导包:commons-dbcp-1.4.jar 依赖 commons-pool-1.6.jar 别忘了驱动包(3个)

2.代码:

[java] view plain copy
  1. import java.sql.Connection;  
  2. import java.sql.SQLException;  
  3.   
  4. import org.apache.commons.dbcp.BasicDataSource;  
  5. import org.junit.Test;  
  6.   
  7. public void fun1() throws SQLException {  
  8.         BasicDataSource ds = new BasicDataSource();  
  9.         ds.setUsername("root");  
  10.         ds.setPassword("123");  
  11.         ds.setUrl("jdbc:mysql://localhost:3306/mydb1");  
  12.         ds.setDriverClassName("com.mysql.jdbc.Driver");  
  13.           
  14.         ds.setMaxActive(20);   <span style="white-space:pre">                 </span>//最大连接数  
  15.         ds.setMaxIdle(10);<span style="white-space:pre">                  </span>//最大空闲数  
  16.         ds.setInitialSize(10);<span style="white-space:pre">                  </span>//初始化连接数  
  17.         ds.setMinIdle(2);<span style="white-space:pre">                   </span>//最小空闲数  
  18.         ds.setMaxWait(1000);<span style="white-space:pre">                    </span>//最大等待毫秒数  
  19.           
  20.         Connection con = ds.getConnection();  
  21.         System.out.println(con.getClass().getName());  
  22.         con.close();<span style="white-space:pre">                        </span>//关闭连接只是把连接归还给池,更多详细设置请参考帮助文档的BasicDateSorice类。  
  23. }  


C3P0

C3P0也是开源免费的连接池!C3P0被很多人看好!

连接池类:ComboPooledDataSource

如何使用:

1.导包:c3p0-0.9.2-pre1.jar 、 c3p0-oracle-thin-extras-0.9.2-pre1.jar 、mchange-commons-0.2.jar 和驱动

2.可以加入配置文件,也可以不加。如果使用配置文件,配置文件必须命名为c3p0-config.xml  放在src下。

3.代码

[java] view plain copy
  1. public void fun1() throws PropertyVetoException, SQLException {  
  2.     ComboPooledDataSource ds = new ComboPooledDataSource();  
  3.     ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb1");  
  4.     ds.setUser("root");  
  5.     ds.setPassword("123");  
  6.     ds.setDriverClass("com.mysql.jdbc.Driver");  
  7.       
  8.     ds.setAcquireIncrement(5);<span style="white-space:pre">          </span>//每次增量  
  9.     ds.setInitialPoolSize(20);<span style="white-space:pre">          </span>//初始化连接数  
  10.     ds.setMinPoolSize(2);<span style="white-space:pre">               </span>//最少连接数  
  11.     ds.setMaxPoolSize(50);<span style="white-space:pre">              </span>//最多连接数  
  12.       
  13.     Connection con = ds.getConnection();  
  14.     System.out.println(con);  
  15.     con.close();  
  16. }  
使用配置文件。

配置文件c3p0-config.xml

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <c3p0-config>  
  3.     <default-config><span style="white-space:pre">                                      </span><!-- 默认配置 -->  
  4.         <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>  
  5.         <property name="driverClass">com.mysql.jdbc.Driver</property>  
  6.         <property name="user">root</property>  
  7.         <property name="password">123</property>  
  8.         <property name="acquireIncrement">3</property>  
  9.         <property name="initialPoolSize">10</property>  
  10.         <property name="minPoolSize">2</property>  
  11.         <property name="maxPoolSize">10</property>  
  12.     </default-config>  
  13.     <named-config name="oracle-config"><span style="white-space:pre">                               </span><!-- 命名配置  -->  
  14.         <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>  
  15.         <property name="driverClass">com.mysql.jdbc.Driver</property>  
  16.         <property name="user">root</property>  
  17.         <property name="password">123</property>  
  18.         <property name="acquireIncrement">3</property>  
  19.         <property name="initialPoolSize">10</property>  
  20.         <property name="minPoolSize">2</property>  
  21.         <property name="maxPoolSize">10</property>  
  22.     </named-config>  
  23. </c3p0-config>  

代码

[java] view plain copy
  1. public void fun2() throws PropertyVetoException, SQLException {  
  2.     ComboPooledDataSource ds = new ComboPooledDataSource(); <span style="white-space:pre">            </span>//不用定配置文件名称,因为配置文件名必须是c3p0-config.xml,这里使用的是默认配置。  
  3.     Connection con = ds.getConnection();  
  4.     System.out.println(con);  
  5.     con.close();  
  6. }  
  7. public void fun2() throws PropertyVetoException, SQLException {  
  8.     ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config") ;<span style="white-space:pre">     </span>//使用命名为orcale-config的配置  
  9.     Connection con = ds.getConnection();  
  10.     System.out.println(con);  
  11.     con.close();  
  12. }  


DBUtils

是Apache Commons组件的项目,开源免费。对jdbc进行简单的封装


主要类:

DbUitls   ---- 都是静态方法,一系列的close()。

QueryRunner  ---- 增删改查都靠它 

update() : 执行insert  update  delete 

query() :执行select 语句

batch() :执行批处理

ResultSetHandler  ---- 结果集处理接口,其提供的实现类可以把结果集转换成不同类型。

  • MapHandler  ----  单行处理器!把结果集转换成Map<String,Object>,其中列名为键
  • MapListHandler   ----  多行处理器!把结果集转换成List<Map<String,Object>>
  • BeanHandler   ----  单行处理器!把结果集转换成Bean,该处理器需要Class参数,即Bean的类型
  • BeanListHandler   ---- 多行处理器!把结果集转换成List<Bean>  
  • ColumnListHandler   ----  多行单列处理器!把结果集转换成List<Object>,使用ColumnListHandler时需要指定某一列的名称或编号,例如:new ColumListHandler(“name”)表示把name列的数据放到List中。
  • ScalarHandler   ----    单行单列处理器!把结果集转换成Object。一般用于聚集查询,例如select count(*) from tab_student。

详细请看DBUtils的API,在DBUtils包的apidocs文件夹下!!!!

如何使用:

1.导包:commons-dbutils-1.6.jar  依赖  commons-logging-1.2.jar  照旧驱动包

2.代码


基本使用:

[java] view plain copy
  1. <span style="white-space:pre">    </span>@Test  
  2.     public void fun1() throws SQLException {  
  3.         QueryRunner qr = new QueryRunner();  
  4.         String sql = "insert into user values(?,?,?)";  
  5.         qr.update(JdbcUtils.getConnection(), sql, "u1""zhangSan""123");  
  6.     }  
  7.     @Test  
  8.     public void fun2() throws SQLException {  
  9.         QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());//这种方式在创建QueryRunner时传递池对象,那么在调用update()时不用在传递connection了。  
  10.         String sql = "insert into user values(?,?,?)";  
  11.         qr.update(sql, "u1""zhangSan""123");  
  12.     }  


[java] view plain copy
  1. </pre><pre>  

QueryRunner之查询

[java] view plain copy
  1. import java.sql.Connection;  
  2. import java.sql.SQLException;  
  3. import java.util.HashMap;  
  4. import java.util.List;  
  5. import java.util.Map;  
  6.   
  7. import org.apache.commons.dbutils.QueryRunner;  
  8. import org.apache.commons.dbutils.handlers.MapHandler;  
  9. import org.apache.commons.dbutils.handlers.MapListHandler;  
  10. import org.junit.Test;  
  11.   
  12. public class JdbcDemo {  
  13.     @Test  
  14.     public void fun1() throws SQLException {  
  15.         DataSource ds = JdbcUtils.getDataSource();  
  16.         QueryRunner qr = new QueryRunner(ds);  
  17.         String sql = "select * from tab_student where number=?";  
  18.         Map<String,Object> map = qr.query(sql, new MapHandler(), "S_2000");<span style="white-space:pre">       </span>//把一行记录转换成一个Map,其中键为列名称,值为列值  
  19.         System.out.println(map);  
  20.     }  
  21.       
  22.     @Test  
  23.     public void fun2() throws SQLException {  
  24.         DataSource ds = JdbcUtils.getDataSource();  
  25.         QueryRunner qr = new QueryRunner(ds);  
  26.         String sql = "select * from tab_student";  
  27.         List<Map<String,Object>> list = qr.query(sql, new MapListHandler());<span style="white-space:pre">        </span>//把转换集转换成List<Map>,其中每个Map对应一行记录  
  28.         for(Map<String,Object> map : list) {  
  29.             System.out.println(map);  
  30.         }  
  31.     }  
  32.       
  33.     @Test  
  34.     public void fun3() throws SQLException {  
  35.         DataSource ds = JdbcUtils.getDataSource();  
  36.         QueryRunner qr = new QueryRunner(ds);  
  37.         String sql = "select * from tab_student where number=?";  
  38.         Student stu = qr.query(sql, new BeanHandler<Student>(Student.class), "S_2000");<span style="white-space:pre">       </span>//把结果集转换成一个Bean对象,在使用BeanHandler时需要指定Class,即Bean的类型  
  39.         System.out.println(stu);  
  40.     }  
  41.       
  42.     @Test  
  43.     public void fun4() throws SQLException {  
  44.         DataSource ds = JdbcUtils.getDataSource();  
  45.         QueryRunner qr = new QueryRunner(ds);  
  46.         String sql = "select * from tab_student";  
  47.         List<Student> list = qr.query(sql, new BeanListHandler<Student>(Student.class));<span style="white-space:pre">    </span>//把结果集转换成List<Bean>,其中每个Bean对应一行记录  
  48.         for(Student stu : list) {  
  49.             System.out.println(stu);  
  50.         }  
  51.     }  
  52.       
  53.     @Test  
  54.     public void fun5() throws SQLException {  
  55.         DataSource ds = JdbcUtils.getDataSource();  
  56.         QueryRunner qr = new QueryRunner(ds);  
  57.         String sql = "select * from tab_student";  
  58.         List<Object> list = qr.query(sql, new ColumnListHandler("name"));<span style="white-space:pre">         </span>//多行单例处理器,即获取name列数据  
  59.         for(Object s : list) {  
  60.             System.out.println(s);  
  61.         }  
  62.     }  
  63.       
  64.     @Test  
  65.     public void fun6() throws SQLException {  
  66.         DataSource ds = JdbcUtils.getDataSource();  
  67.         QueryRunner qr = new QueryRunner(ds);  
  68.         String sql = "select count(*) from tab_student";  
  69.         Number number = (Number)qr.query(sql, new ScalarHandler());<span style="white-space:pre">             </span>//单行单列处理器,一般用于聚合查询,在使用ScalarHandler时可以指定列名,如果不指定,默认为第1列。  
  70.   
  71. <span style="white-space:pre">        </span>//对聚合函数的查询结果,有的驱动返回的是Long,有的返回的是BigInteger,所以这里我们把它转换成Number,Number是Long和BigInteger的父类!然后我们再调用Number的intValue()或longValue()方法就OK了。  
  72.         int cnt = number.intValue();<span style="white-space:pre">                                </span>  
  73.         System.out.println(cnt);  
  74.     }  
  75.   
  76. }  


QueryRunner之批处理

[java] view plain copy
  1. @Test  
  2. public void fun10() throws SQLException {  
  3.     DataSource ds = JdbcUtils.getDataSource();  
  4.     QueryRunner qr = new QueryRunner(ds);  
  5.     String sql = "insert into tab_student values(?,?,?,?)";  
  6.     Object[][] params = new Object[10][];<span style="white-space:pre">                               </span>//表示 要插入10行记录,注意,这里是二维数组,这个二维数组有10个一维数组。  
  7.     for(int i = 0; i < params.length; i++) {  
  8.         params[i] = new Object[]{"S_300" + i, "name" + i, 30 + i, i%2==0?"男":"女"};  
  9.     }  
  10.     qr.batch(sql, params);<span style="white-space:pre">                                      </span>//执行批处理