1.事务处理
1.1 事务处理类型
mysql
JDBC
DBUtils
1.2 Mysql事务处理
首先,打开小海豚,创建表,并输入数据。
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50),
money DOUBLE
);
INSERT INTO account(id,NAME,money) VALUES(NULL,'jack',10000);
INSERT INTO account(id,NAME,money) VALUES(NULL,'rose',20000);
打开CMD,进行操作
Start transaction;
commit;
roll back;
三个步骤,如果打开Start transaction,并且Update数据库中的数据,在小海豚刷新是没有任何变化的,执行commit后,刷新小海豚数据才会更新。’
1.3 mysql autocommit
每次在CMD中操作mysql语句,都是默认autocommit。
可以通过语句进行改变:show variables like ‘xxcommit’; —-》
–》 set autocommit = 0/1;
***Oracle 数据库不自动commit;
1.4 JDBC事务操作
conn.setAutoCommit(false) 开启事务
conn.commit() 提交事务
conn.rollbalck() 回滚事务
@Test
public void demo0() throws Exception{
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = (Connection) JDBCUtils.getConnection();
connection.setAutoCommit(false);
String sql = "update account set money = money + ? where name = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,-100);
preparedStatement.setString(2, "zhilong");
preparedStatement.executeUpdate();
preparedStatement.setInt(1,100);
preparedStatement.setString(2, "tommy");
preparedStatement.executeUpdate();
connection.commit();
int r = preparedStatement.executeUpdate();
System.out.println(r);
} catch (Exception e) {
// TODO: handle exception
try {
connection.rollback();
throw new RuntimeException("program error",e);
} catch (Exception e2) {
// TODO: handle exception
}
}finally {
if (connection != null) {
connection.close();
}
}
}
1.5 DBUtils事务操作
*conn.setAutoCommit(false) 开启事务
*new QueryRunner() 创建核心类,不设置数据源,手动管理连接。
*Query()、Update() 手动传递连接
*Dbutils.commitAndClose(conn) 或 DbUtils.rollbackandClose(conn) 关闭连接。
Connection connection = null;
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
try {
connection = C3P0Utils.getConnection();
connection.setAutoCommit(false);
String sql = "update account set money= money+? where name = ?";
Object[] params = {-100,"jack"};
int r = queryRunner.update(connection, sql, params);
Object[] params2 = {100,"rose"};
int r2 = queryRunner.update(connection, sql, params2);
// DbUtils.commitAndClose(connection);
DbUtils.commitAndCloseQuietly(connection);
} catch (Exception e) {
// TODO: handle exception
DbUtils.rollbackAndClose(connection);
}
1.6 分层
dao 数据库操作
Service 业务代码加上事务进行操作
domain Bean层
AccountDao:
public class AccountDao {
public void out(Connection conn,String outUser,Double money) throws Exception{
QueryRunner queryRunner = new QueryRunner();
String sql = "update account set money = money - ? where name = ?";
Object[] objects = {money,outUser};
int r = queryRunner.update(conn,sql, objects);
System.out.println(r);
}
public void in(Connection connection,String inUser,Double money) throws Exception{
QueryRunner queryRunner = new QueryRunner();
String sql = "update account set money = money - ? where name = ?";
Object[] objects = {money,inUser};
queryRunner.update(connection,sql,objects);
}
}
AccountService:
public class AccountService {
public void transfer(String outUser,String inUser,double money) throws Exception{
// 1,获得连接
Connection connection = C3P0Utils.getConnection();
try {
// 2开启事务
connection.setAutoCommit(false);
// 业务代码
AccountDao accountDao = new AccountDao();
accountDao.out(connection,outUser, money);
accountDao.in(connection,inUser, money);
// 3提交事务
DbUtils.commitAndCloseQuietly(connection);
} catch (Exception e) {
// TODO: handle exception
// 如果有异常就回滚
DbUtils.rollbackAndCloseQuietly(connection);
throw new RuntimeException(e);
}
}
}
Test:
public class test {
public static void main(String[] args) throws Exception {
String outUser = "jack";
String inUser = "rose";
double money = 100d;
try {
AccountService accountService = new AccountService();
accountService.transfer(outUser, inUser, money);
System.out.println("transfer successfully");
} catch (Exception e) {
// TODO: handle exception
System.out.println("transfer failed");
}
}
}
####1.7 ThreadLocal
用途:除了事务以外,JDK允许此类可以在一个线程中共享数据。
Threadlocal:底层就是一个Map,key存放的是当前线程,Value存放的是共享数据。
实现:C3P0Utils.getConnection() 内部使用Threadlocal,用于本地线程缓存连接,1).从ThreadLocal获得连接
2).如果没有,从连接池获得连接,并保存到ThreadLocalhost中。
3).获得连接,返回即可。
代码:
**C3P0Utils:**
public class C3P0Utils {
private static DataSource dataSource = new ComboPooledDataSource("itheima");
private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
public static ComboPooledDataSource getDataSource(){
return (ComboPooledDataSource) dataSource;
}
public static Connection getConnection() throws SQLException{
// 1.从本地线程变量获得
Connection connection = local.get();
// 2.如果没有,从连接池获得,并添加到ThreadLocal中
if (connection == null) {
connection = dataSource.getConnection();
local.set(connection);
}
return connection;
}
}
AccountService
public class AccountService {
public void transfer(String outUser,String inUser,double money) throws Exception{
java.sql.Connection connection = null;
try {
// 获得连接
connection = C3P0Utils.getConnection();
// 开启事务
connection.setAutoCommit(false);
// 业务操作
AccountDao accountDao = new AccountDao();
accountDao.out(outUser, money);
accountDao.in(inUser, money);
// 提交事务
DbUtils.commitAndCloseQuietly(connection);
} catch (Exception e) {
// TODO: handle exception
DbUtils.rollbackAndCloseQuietly(connection);
throw new RuntimeException(e);
}
}
}
AccountDao
public void out(String outUser,Double money) throws Exception{
QueryRunner queryRunner = new QueryRunner();
String sql = "update account set money = money - ? where name = ?";
Object[] objects = {money,outUser};
int r = queryRunner.update(C3P0Utils.getConnection(), sql,objects);
System.out.println(r);
}
public void in(String inUser,Double money) throws Exception{
QueryRunner queryRunner = new QueryRunner();
String sql = "update account set money = money - ? where name = ?";
Object[] objects = {money,inUser};
queryRunner.update(C3P0Utils.getConnection(),sql,objects);
}
}
Test
public class test {
public static void main(String[] args) {
String outUser = "zhilong";
String inUser = "tommy";
double money = 100d;
try {
AccountService accountService = new AccountService();
accountService.transfer(outUser, inUser, money);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
1.7 事务总结
*1.7.1 事务特性:ACID
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作,要么都发生,要么都不发生。
一致性(Consistency)事务前后数据的完整性必须保持一致。
隔离型(Isolation)事务的隔离性是指多个用户并发访问数据时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
*1.7.2并发访问问题
如果不考虑隔离性,事务存在3种并发访问问题。
脏读:一个事务读到另一个事务未提交的数据。
不可重复读:一个事务读到另一个事务已经提交的(Update)数据,引发另一个事务,在事务中多次查询结果不一致。
虚读和幻读:一个事务读到另一个事务已经提交的(insert)数据,导致另一个事务,在事务中多次查询结果不一致。
*1.7.3隔离级别:解决问题
Read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。存放,3个问题(脏读,不可重复读,虚读)。
解决,0个问题。
Read committed 读已提交,一个事务读到另一个事务已提交的数据。
存放:2个问题(不可重复读,虚读)
解决:1个问题(脏读)。
repeatable Read:可重复读,在一个事务中读到数据始终保持一致,无论另一个事务是否提交。
存放:1个问题(虚读)。
解决:2个问题(脏读,不可重复读)。
serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
存放:0个问题。
解决:1个问题(脏读,不可重复读,虚读)。
安全和性能对比
安全性:serializable>repeatable read>read committed>read uncommitted.
性能:serializable
A:
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25400 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25300 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25400 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
B:
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25400 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25300 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
mysql> select * from accoun
+----+---------+-------+
| id | name | money |
+----+---------+-------+
| 1 | jack | 25400 |
| 2 | rose | 20000 |
| 3 | zhilong | 9300 |
| 4 | Tommy | 5100 |
| 5 | Tommy | 5000 |
| 6 | hans | 4600 |
+----+---------+-------+
6 rows in set (0.00 sec)
>读已提交
A窗口设置隔离级别
AB同时开启事务
A查询
B更新、但不提交
A在查询?–数据不变,解决问题【脏读】。
B提交。
A在查询?–数据改变,存在问题【不可重复读】。
>可重复读:repeatable Read
A窗口设置隔离级别
AB同时开启事务
A查询
B更新,但不提交
A在查询
B提交
A在查询?—数据不变,解决【脏读】
B提交
A在查询? —数据不变,解决【不可重复读】
A提交或者回滚
A在查询 ?—数据改变,另一个事务【可重复读】
>串行化:serializable
A窗口设置隔离级别
AB同时开启事务
A查询
B跟新?–等待更新,如果A没有进一步操作,B将等待超时
A回滚
B窗口?–可以继续操作,等待结束。