jdbc mysql事务源代码_20.JDBC开发(3)事务和池(我的JavaEE笔记)

主要内容:

事务

使用数据库连接池优化程序性能

一、事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。数据库开启事务命令:

start transaction 开启事务

rollback 回滚事务

commit 提交事务

当我们开启事务后,可输入多条sql语句让数据库执行,但是如果我们在让sql语句执行之后最后没有使用commit提交事务,则前面执行的所有sql语句无效,这就相当于回到了开启事务之前的状态,当然有时候这种方式并不太好,我们可以自己设置回滚点,当我们sql语句出错时可以回到设置的那个点处的状态。而rollback可以每次回滚一条语句。

二、使用事务

当jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它前面发送的sql语句。若向关闭这种默认提交方式,让多条sql在一个事务中执行,可使用下列语句:

jdbc控制事务语句

connection.setAutoCommit(false); start transaction

connection.rollback(); rollback

connection.commit(); commit

设置事务回滚点

Savepoint sp = conn.setSavepoint();

conn.rollback(sp);

conn.commit(); //回滚后必须要提交

例:

创建数据库:

create database day16;

CREATE TABLE account(

id INT PRIMARY KEY AUTO_INCREMENT,

NAME VARCHAR(40),

money FLOAT

)CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO account(NAME,money) VALUES('aaa',1000);

INSERT INTO account(NAME,money) VALUES('bbb',1000);

INSERT INTO account(NAME,money) VALUES('ccc',1000);

Demo1.java

package cn.itcast.demo;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import cn.itcast.utils.JdbcUtils;

public class Demo1 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

ResultSet result = null;

try {

conn = JdbcUtils.getConnection();

String sql1 = "update account set money=money-100 where name='aaa'";

String sql2 = "update account set money=money+100 where name='bbb'";

conn.setAutoCommit(false);//开启事务

ps = conn.prepareStatement(sql1);

ps.executeUpdate();

//int x = 1/0;//模拟异常,sql语句不会执行

ps = conn.prepareStatement(sql2);

ps.executeUpdate();

System.out.println("ok");

} catch (Exception e) {

try {

conn.rollback();//手动通知数据库手动回滚

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JdbcUtils.release(conn, ps, result);

}

}

}

例:设置回滚点

Demo2.java

package cn.itcast.demo;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Savepoint;

import cn.itcast.utils.JdbcUtils;

public class Demo2 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

ResultSet result = null;

Savepoint point = null;

try{

conn = JdbcUtils.getConnection();

String sql1 = "update account set money = money - 100 where name = 'aaa'";

String sql2 = "update account set money = money + 100 where name = 'bbb'";

String sql3 = "update account set money = money + 100 where name = 'ccc'";

conn.setAutoCommit(false);

ps = conn.prepareStatement(sql1);

ps.executeUpdate();

point = conn.setSavepoint();//设置回滚点

ps = conn.prepareStatement(sql2);

ps.executeUpdate();

//int x = 1/0;

ps = conn.prepareStatement(sql3);

ps.executeUpdate();

conn.commit();

}catch(Exception e){

try {

conn.rollback(point);//手动通知回滚,同时指定回滚点

//回滚之后记得提交,上面我们回滚了,就表明最后的提交语句没有执行,那此时如果不提交

//,数据库在没有收到提交的情况下,会自动回滚所有的sql语句

conn.commit();

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JdbcUtils.release(conn, ps, result);

}

}

}

说明:这里如果中间出现异常,则只有第一条语句生效。

三、事务的特性(ACID)

原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都是执行成功,要么都失败。

一致性(Consistency)

事务必须使数据库从一个一致性状态变换到另外一个一致性状态。比如,在转账中账户的总额是不变的。

隔离性(Isolation)

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

持久性(Durability)

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

四、 事务的隔离级别

多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

如果不考虑隔离性,可能会引发如下问题:

脏读

指一个事务读取了另外一个事务未提交的数据。

例如:a花钱让b给办点事,a向b转账之后但未提交,但是b此时会发现账户多了钱,然后将事办完之后a却不提交,此时b的账户的钱就会变回原来的数目,相当于白干活了。

不可重复读

在一个事务内读取表中的某一行数据,多次读取的结果不同。

如a开启一个事务后,查询余额为200,此时b转账100,那么a此时查询就是300,两次结果不一致。当然有些时候这样是正确的,但是有时候却不是,如在统计时我们不能让多次的统计结果不一致。

和脏读的区别:脏读是读取前一事务未提交的数据,不可重复读是重新读取了前一事务已经提交的数据。

虚读(幻读)

是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

比如一个表第一次查询有2条数据,此时另外一个事务插入了一条数据,此时再次查询就变成了3条数据,两次查询结果不一致。

和不可重复读的区别:不可重复读是读取到的数据结果不同,而虚读是指读取到多个事务导致结果不一致。

五、事务隔离性的设置语句

数据库共定义了四种隔离级别:

Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)

Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)

Read committed:可避免脏读情况发生(读已提交)。

Read uncommitted:最低级别,以上情况均无法保证。(读未提交)

set transaction isolation level设置事务隔离级别(数据库操作)

select @@tx_isolation查询当前事务隔离级别(数据库操作)

例:设置隔离级别

Demo3.java

package cn.itcast.demo;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import cn.itcast.utils.JdbcUtils;

public class Demo3 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

ResultSet result = null;

try {

conn = JdbcUtils.getConnection();

//查询程序肯定至少要到这个级别

conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

conn.setAutoCommit(false);

String sql = "select * from account where name='aaa'";

ps = conn.prepareStatement(sql);

result = ps.executeQuery();

if(result.next()){

System.out.println(result.getFloat("money"));

}

Thread.sleep(1000*10);

result = ps.executeQuery();

if(result.next()){

System.out.println(result.getFloat("money"));

}

} catch (Exception e) {

e.printStackTrace();

}finally{

JdbcUtils.release(conn, ps, result);

}

}

}

六、使用数据库连接池优化程序性能

应用程序直接获取连接的缺点:用户每次都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。

bb514bb89fab

1.png

这时我们可以使用数据库连接池优化程序性能:

bb514bb89fab

2.png

编写连接池需要实现java.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:

Connection getConnection()

Connection getConnection(String username,String password)

实现DataSource接口,并实现连接池功能的步骤:

1.在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。

2.实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。

3.当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把Collection还给数据库。

Collection保证将自己返回到LinkedList中是此处编程的难点。

示例:模拟数据库连接池

JdbcPool.java

package junit.test;

import java.io.InputStream;

import java.io.PrintWriter;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.sql.SQLFeatureNotSupportedException;

import java.util.LinkedList;

import java.util.Properties;

import java.util.logging.Logger;

import javax.sql.DataSource;

public class JdbcPool implements DataSource {

//后面涉及到大量的增删改查,所以使用LinkedList类

private static LinkedList list = new LinkedList();

static {

try {

InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");

Properties properties = new Properties();

properties.load(in);

String driver = properties.getProperty("driver");

String url = properties.getProperty("url");

String username = properties.getProperty("username");

String password = properties.getProperty("password");

Class.forName(driver);

for(int i = 0; i < 10; i++){

Connection conn = DriverManager.getConnection(url, username, password);

list.add(conn);

}

} catch (Exception e) {

e.printStackTrace();

}

}

@Override

public Connection getConnection() throws SQLException {

if(list.size() > 0){

//如果连接池中还有Connection连接则从池中删除并返回给调用者

Connection conn = list.removeFirst();

return conn;

}else{

throw new RuntimeException("数据库正忙");

}

}

其他需要实现的方法我们并不关心,这里省略

}

说明:

这里有个问题是如果调用者使用完Connection链接之后调用方法conn.close();那么此链接将不会返回给数据库连接池,而是直接返回给了数据库,这样链接会用一个少一个,显然不行。也就是说不能这样,或者说close方法不够用,我们需要增强一下,让其不要返还给数据库,而是返还给连接池。

对于类的某个方法不能达到我们的要求时需要对其进行增强,而增强的方式有三种:1.写一个子类,覆盖其close方法;2.写一个Connection的包装类,增强close方法;3.使用动态代理,返回一个代理对象出去,拦截close方法的调用,达到对close方法增强的功能。

第一种不行,因为我们在要返回Connection的时候对象中已经封装了相关信息,即使我们写一个子类也仅仅表明此子类有和父类相同的功能,但是却没有父类中已经封装好了的信息,不能对数据库进行操作。

第二种:写一个包装类。

/*

* 用包装设计模式对某个对象进行增强步骤:

* 1、写一个类,实现与被增强对象(这里要增强的对象是mysql的连接对象connection)相同的接口(这里的接口是Connection)

* 2.定义一个变量,指向被增强对象

* 3、定义一个构造方法,接收被增强对象(也就是将我们要增强的对象传递进来进行增强)

* 4、覆盖想增强的方法(这里是close方法)、

* 5、对于不想增强的方法,直接调用被增强对象的方法,如this.conn.unwrap(iface)

* */

class MyConnection implements Connection{

private Connection conn ;

private List pool;//这里我们需要将数据库连接池传递进来,因为之后我们使用的链接都是增强之后的链接

public MyConnection() {

}

public MyConnection(Connection conn , List pool){

this.conn = conn;

this.pool = pool;

}

//这里我们只是需要增强close方法,其他方法直接调用父类的方法即可

@Override

public void close() throws SQLException {

pool.add(conn);

}

@Override

public T unwrap(Class iface) throws SQLException {

return this.conn.unwrap(iface);

}

其他方法和上面这个方法类似,此处省略

}

说明:之后返回就不是返回Connection对象了,而是return new MyConnection(conn, list);,但是显然我们可以看到这种方式太麻烦,因为其中的方法太多。

第三种:使用动态代理

@Override

public Connection getConnection() throws SQLException {

if(list.size() > 0){

//如果连接池中还有Connection连接则从池中删除并返回给调用者

final Connection conn = list.removeFirst();

//第一个参数指的是使用哪个类装载器,第二个参数指明我们要对那个对象进行增前,第三个参数指明增强对象完成什么功能

return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(),

new InvocationHandler() {

//使用动态代理之后其实不管之后我们调用Connection的什么方法(commit、rollback...)其实都是调用下面的invoke方法

@Override

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {

//如果调用的方法不是close方法,那么我们使用原来Connection的方法

if(!method.getName().equals("close")){

return method.invoke(conn, args);

}else{

//如果调用close方法,我们将链接返还给数据库连接池

return list.add(conn);

}

}

});

}else{

throw new RuntimeException("数据库正忙");

}

}

说明:其实动态代理是使用的拦截技术,这里我们不详细讲,在后面将过滤器会详细说明。

那么我们可以对之前的数据库工具类做一些改进:

JdbcUtils.java

package junit.test;

import java.sql.Connection;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

public class JdbcUtils {

private static JdbcPool pool = new JdbcPool();

public static Connection getConnection() throws SQLException{

return pool.getConnection();

}

public static void release(Connection conn, Statement ps , ResultSet result){

if(result != null){

try {

result.close();

} catch (Exception e) {

e.printStackTrace();

}

result = null;

}

if(ps != null){

try {

ps.close();

} catch (Exception e) {

e.printStackTrace();

}

ps = null;

}

if(conn != null){

try {

conn.close();

} catch (Exception e) {

e.printStackTrace();

}

conn = null;

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值