事务处理与使用连接池管理连接

事务的概念和MySQL事务支持:

 事务是由一步或者多步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。

 事务具备4个特性:原子性(Atomicity),一致性(Consistency),隔离性(Isolation)和持续性(Durabilty)。这4个特性也简称为ACID性。

  原子性:事务是应用中最小的执行单位

  一致性:事务执行的结果,必须是数据库从一个一致性状态,变成另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行中断,某个事务尚未完成而被中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时,数据库就处于一种不正确的状态。比如说银行转账,A向B转账1000元,数据库从A账户减少了1000元,给B账户增加了1000元,如果全部执行成功,数据库处于一致性状态;如果仅仅执行完A账户金额的修改,而没有增加B账户的金额,则数据就处于不一致性状态:因此,一致性时通过原子性来保证的。

  隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都时隔离的。也就是说,并发执行的事务之间不能看到对象的中间状态,并发执行的事务之间不能互相影响。

  持续性:持续性也成为持久性,指事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存进物理数据库。

 数据库的事务由下列语句组成:

  一组DML语句,经过这组DML语句修改后的数据将保持较号的一致性。

  一条DDL语句

  一条DCL语句

  DDL语句和DCL语句只有一条,因为DDL和DCL语句都会导致事务立即提交。

 当事务所包含的全部数据库操作都成功执行后,应该提交事务(commit),使这些事务修改永久生效。事务的提交一共有两种方式:显示提交与自动提交:

  显示提交:使用commit

  自动提交:执行DDL或者DCL语句,或者程序正常退出。

 当事务所包含的全部数据库操作执行失败后,应该回滚(rollback)事务,是该事务中所做的修改全部失效。事务回滚有两种方式:显示回滚与自动回滚:

  显示回滚:使用rollback

  自动回滚:数据库操作执行失败,系统错误或者强行退出

 

 MYSQL的事务:

  在默认的情况下,MYSQL是关闭事务的,即开启自动提交,在默认的情况下,当用户在MYSQL控制台输入一条DML语句时,这叫DML语句将会立即保存到数据库里(持续性)。如果要开启MYSQL的事务支持,可以显示调用如下命令:

set autocommit=0 #开启事务
set autocommit=1 #自动提交

  自动提交和开启事务恰好相反,如果开启自动提交,就是关闭事务;如果开启事务就是关闭自动提交

  一旦在MYSQL的命令行窗口中输入了set autocommit=0 开启了事务,该命令行窗口里的所有DML语句都不会立即生效,上一个事务结束后第一条DML语句将开始一个新的事务,而后续执行的所有SQL语句都在该事务中,除非显式使用commit来提交事务,或者正常退出,或者运行DDL,DCL语句导致事务隐式提交。当然也可以使用rollback来回滚结束事务,使用rollback结束事务将导致本次事务中的DML语句所做的修改全部失效。

  PS:这里要注意一点的是,在命令行窗口的开启事务是对该命令行窗口有效,其他窗口还是处于mysql的默认状态,即自动提交。

  除此之外,若不想关闭整个命令行窗口的自动提交(即关闭事务),而只是想临时性地开始事务,则可以使用MySQL提供的start transaction或begin两个命令,它们都表示临时性地开始一次事务,处于start transaction或者begin后的DML语句不会立即生效,除非使用commit显示提交事务,或者执行DDL,DCL语句的隐式提交事务才会立即生,如下面的SQL语句:

#临时开始事务
begin:
    #向student_table表中插入3条记录
    insert into tb_test values(null,'张三''1');
    insert into tb_test values(null,'李四''2');
    insert into tb_test values(null,'赵五''3');
    
    #查询表中的记录 (1select * from tb_test;

    #回滚事务
    rollback;

    #查询表中的记录  (2)
    select * from tb_test;

  执行上面SQL语句中的第一个查询语句将会看到刚刚插入的3条记录,其他窗口进行查询是看不到该3条记录的,因为该3条记录未提交,也就是还没有写入数据库中。当回滚事务后,执行上面SQL语句的第二个查询语句,该窗口也和其他窗口一样,也看不到那3条记录,因为回滚事务后,对事务中的所有DML语句全都失效,因此该窗口再也查询不出那三条记录。

  MYSQL还提供了savepoint来设置事务的中间点,通过使用设置savepoint设置事务的中间点可以让事务回滚到指定中间点,而不是回滚全部事务。如下SQL语句:

savepoint a #设置中间点
rollback to a; 使用rollback回滚到指定中间点

 

JDBC的事务支持:

 JDBC连接也提供了事务支持,JDBC连接的事务支持由Connection提供,Connection默认是自动提交,即关闭事务(一次性只能执行一条DML语句),在这种情况下,每条SQL语句一旦执行,便会立即提交到数据库,永久生效,无法对其进行回滚操作。

   可以使用Connection 的setAutoCommit(false)方法来关闭自动提交,开启事务。

   可以使用Connection 的setAutoCommit(true)方法来关闭事务,开启自动提交,这也是Connection的默认状态。

 一旦开启了事务之后,程序可以向平常一样创建Statement对象,创建了Statement对象之后,可以执行任意多条DML语句,尽管程序都执行了多条DML语句,但这些DML语句所做的修改都不会生效,因为没有提交事务。因此当编写完DML语句后要加上Connection.commit()方法来提交事务,让DML语句生效。如多条DML语句中执行失败,则应该用Connection的rollback()方法来回滚事务。实际上,当Connection遇到一个未处理的SQLExcepition异常时,系统将会非正常退出,即事务隐式回滚;若程序捕获了改异常,则需要在异常处理块中显式地回滚事务。

 下面程序示范了JDBC的事务支持:

public class JDBC {
    public void insertInTransaction(String[] sqls){
        try{
            conn.setAutoCommit(false);
            Statement statement = conn.createStatement();
            for(String sql : sqls){
                statement.execute(sql);
                System.out.println("SQL语句执行正常");
            }
            conn.commit(); //提交事务
        }catch(Exception e){
            if(conn != null){
                try {
                    conn.rollback();
                    System.out.println("事务回滚");
                } catch (SQLException e1) {e1.printStackTrace();}
            }
        }
    }
    
    Connection conn = null;
    Statement statement = null;
    public JDBC(String paramFile){
        try{
            Properties props = new Properties();
            props.load(new FileInputStream(paramFile));
            driver = props.getProperty("driver");
            url = props.getProperty("Url");
            user = props.getProperty("user");
            password = props.getProperty("password");
            conn =DriverManager.getConnection(url,user,password);
            statement =conn.createStatement();
            
        }catch(Exception e){}
    }
    
    public static void main(String[] args) throws SQLException {
        String[] sqls = new String[]{
            "insert into tb_test values(null,'AA',1)",
            "insert into tb_test values(null,'BB',2)",
            "insert into tb_test values(null,'CC',3)",
            "insert into tb_test values(null,'00','00')",  //错误的,因为第三个字段的数据类型为int,不能传入字符串进去
            "insert into tb_test values(null,'DD',4')",
        };
        jdbc_2.insertInTransaction(sqls);
    }
}

 上述代码中,一共提交了5条DML语句,其中第四条是错误的,但第四条DML语句执行时,因为是错误的,会出现SQLException异常,程序将这个异常捕获,并执行了事务回滚,去查看对应数据库表,可以发现前三条DML语句在程序中尽管执行了,但还未写入到数据库中。保证了数据库的一致性。运行效果如下:

SQL语句执行正常
SQL语句执行正常
SQL语句执行正常
SQL语句执行正常
事务回滚

 

使用连接池管理连接:

 数据库连接的建立以及关闭都是极其耗费系统资源的操作,在多层结构的应用环境中,这种资源的耗费对系统性能影响尤其明显。当获得的数据库连接,一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完后立即关闭连接。频繁地打开,关闭连接造成系统性能低下。

 为了解决这个问题,Java提供了数据库连接池,当应用程序启动时,系统会主动建立足够的数据库连接,并将这些连接组成一个连接池。每次应用程序请求数据库连接时,无需重新打开连接,而是从连接池中取出已有的连接使用,当应用程序使用完毕后,该连接不用完毕,而是直接将连接归回连接池。通过使用连接池,将大大提高程序的运行效率。

 数据库连接池是Connection对象的工厂,数据库连接池的常用参数如下:

  1. 数据库的初始连接数

  2. 连接池的最大连接数。

  3. 连接池的最小连接数

  4. 连接池每次增加的容量

 JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由商用服务器(如WebLogic,WebSphere)等提供实现,也有一些开源组织提供实现(如DBCP和C3P0)。这里推荐大家都使用C3P0。

 

 DBCP数据源:

  DBCP数据源是由Apache软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:common-pool。如果要使用DBCP连接池,需要将如下的两个jar文件添加到系统或者开发软件中:

   commons-dbcp.jar:连接池的实现 

   commons-pool.jar:连接池实现的依赖库

  Tomcat的连接池正是采用该连接池实现的。数据库连接池既可以与应用服务器整合使用,也可以由应用程序独立使用。下面的代码示范了使用DBCP来获取数据库连接方式:

BasicDataSource ds = new BasicDataSource(); //创建数据源对象
ds.setDriverClassName("com.mysql.jdbc.Driver"); //设置连接池所需的驱动
ds.setUrl("jdbc://mysql://localhost:8080/test"); //设置连接数据库的URL
ds.setUsername("root"); //设置连接数据库的用户名
ds.setPassword("root");  //设置连接数据库的密码
ds.setInitialSize(5); //设置连接池的初始连接数
ds.setMaxActive(20); //设置连接池最多可有20个活动连接数
ds.setMinIdle(2); //设置连接池最少由2个空闲的连接

  数据源与数据库连接不同,数据源无须创建多个,它是产生数据库连接的工厂,因此整个应用只需有一个数据源便可,建议是将数据源设置成类成员变量,并且在应用开始时立即初始化数据源对象。

Connection conn = ds.getConnection(); //从数据库连接池中获取连接
conn.close() //关闭连接,并将该连接还给数据库连接池

 

 C3P0数据源:

  相比之下,C3P0数据源性能更胜一筹,Hibernate就推荐使用该连接池。C3P0连接池不仅可以自动清理不在使用的Connection,还可以自动清理Statement和ResultSet。若要使用C3P0,需要将c3p0-0.9.1.2.jar (有不同的版本)的jar文件添加到系统或者开发软件中。

  下面代码通过c3p0连接池获得数据库连接:

ComboPooledDataSource ds = new ComboPooledDataSource(); //创建连接池实例
            ds.setDriverClass("com.mysql.jdbc.Driver"); //设置连接池连接数据库所需的驱动
            ds.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test"); //设置连接数据库的URL
            ds.setUser("root");  //设置连接数据库的用户名
            ds.setPassword("root"); //设置连接数据库的密码
            ds.setMaxPoolSize(40); //设置连接池的最大连接数
            ds.setMinPoolSize(10); //设置连接池的最小连接数
            ds.setInitialPoolSize(10); //设置连接池的初始连接数
            ds.setMaxStatements(180); //设置连接池的缓存Statement的最大数

 C3P0获取数据库的连接操作代码与DBCP源获取数据库的连接是一样的;关闭连接的操作也是一样的:

Connection conn = ds.getConnection(); //从数据库连接池中获取连接
conn.close() //关闭连接,并将该连接还给数据库连接池

 

posted @ 2019-09-01 22:08  HJLのH  阅读(931)  评论(0编辑  收藏  举报