事务
指的是一组操作,里面包含许多个单一的逻辑,如果都成功了,就执行提交(commit)只要有一个逻辑没有执行成功,那么都算失败,所有的数据都回归到最初的状态(回滚rollback)
为什么要有事务
为了确保逻辑的成功
使用代码方式演示事务
代码里面的事务,主要是针对连接来的,通过conn,setAutoCommit(false)来关闭自动提交的设置
public void testTransaction(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtil.getConn();
//连接,事务默认就是自动提交的,关闭自动提交
conn.setAutoCommit(false);
String sql = "update account set money = money - ? where id = ?"
ps = conn.prepareStatement(sql);
//扣钱
ps.setInt(1,100);
ps.setInt(2,1);
ps.executeUpdate();
//此处为演示数据库错误
int a = 10/0
//加钱,给ID为2 加100块钱
ps.setInt(1,-100);
ps.setInt(2,2);
ps.executeUpdate();
}catch(SQLException e){
try{
conn.rollback();
}catch(SQLException e1){
e1.printStackTrace();
}
e1.printStackTrace();
}finally{
JDBCUtil.release(conn,ps,rs);
}
}
事务的特性ACID
1.原子性
指的是事务中包含的逻辑不可分割
2.一致性
指的是事务执行前后,数据完整性
3.隔离性
指的是事务在执行期间不应该受到其他事务的影响
4.持久性
指的是事务执行成功,那么数据应该持久保存到磁盘上
事务的安全隐患
读:
1.脏读
一个事务读到另外一个事务还未提交的数据
A,B两个窗口,当A窗口的隔离级别为Read Uncommitted(读未提交),B窗口正在执行更新,但是还未提交,此时A窗口已经进行查询
解决办法:
设置A窗口的隔离级别为读已提交(这个隔离级别能够屏蔽脏读的现象,但是引发另一个问题,不可重复读)
A,B两个窗口都开启事务,在B窗口执行更新操作
在A窗口执行的两次查询结果不一致,一次是在B窗口提交事务之前,一次是在B窗口提交事务之后
2.不可重复读
一个事务读到了另外一个事务提交的数据,造成了前后两次查询结果不一致
3.幻读
一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致
如果想把以上问题都解决,那么要用到最高的隔离级别:Serializable(可串行化)
set session transaction isolayion level serializable;
如果有一个连接的隔离级别设置了串行化,那么谁先打开了事务,谁就有了先执行的权利,后来打开事务的只能等前面的事务提交或者回滚才能执行。但是这种隔离级别一般比较少用,容易造成性能上的问题,效率比较低
按效率分,隔离级别从高到低:
读未提交>读可提交>可重复读>可串行化
按拦截程度分,隔离级别从高到低
可串行化>可重复读>读已提交>读未提交
写:
丢失更新
A和B同时开启事务,A先提交更新,B后提交更新,B的更新会冲掉A的更新
解决方法:
1.悲观锁(排他锁)
select * from account for update
A和B同时开启事务,如果A先查询,此时B界面会卡住,无法查询,要等到A提交或者回滚,B才可以进行更新数据
2.乐观锁
想要实现需要程序员自己写代码
select * from account
A和B同时开启事务,A事务先提交,数据库version变为1,此时B事务提交的时候,要比对数据库的version和自己的version,如果不一样,不允许提交
事务总结
1.常用代码
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
2.事务只是针对连接对象,如果再开一个连接对象,那么默认提交
3.事务是会自动提交的
4.安全隐患有读和写两方面,读会产生脏读、不可重复读、幻读等隐患,写会产生丢失更新的隐患
5.隔离级别有四种,读未提交引发脏读),读已提交(解决脏读,引发不可重复读),可重复读(解决脏读、不可重复读,未解决的是幻读),可串行化(解决:脏读、不可重复读、幻读)
6.mySql默认的隔离级别是可重复读,Oracle默认的隔离级别是读已提交
数据库连接池
一开始先在内存中开辟一块空间(集合),先往池子里面放置多个连接对象,后面需要连接的话,直接拿池子中的练级对象,不要自己创建连接对象,使用完毕要记得归还连接,确保对象能循环利用
数据库连接池的简单搭建
/*
*1.开始创建10个连接
*2.来的程序通过getConnection获取连接
*3.用完归还
*4.扩容
*/
puublic class MyDataSource implements DataSource{
public MyDataSource(){
for(int i=0;i<10;i++){
//JDBCUtil是自己写的jdbc工具类
Connection conn = JDBCUtil.getConnection();
list.add(conn);
}
}
public Connection getConnection()throws SQLException{
//检查池子里面还有没有连接
if(list.size()==0){
for(int i=0;i<5;i++){
//JDBCUtil是自己写的jdbc工具类
Connection conn = JDBCUtil.getConnection();
list.add(conn);
}
Connection conn = list.remove(0);
return conn;
}
//归还
public void addBack(Connection conn){
list.add(conn);
}
}
数据库连接池的简单使用
public void testPool(){
Connection conn = null;
PreparedStatement ps = null;
MyDataSource dataSource = new MyDataSource();
try{
conn = dataSource.getConnection();
String sql = "insert into account values(null,"xxx",10)";
ps = con.prepareStatement(sql);
ps.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
ps.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
自定义数据库连接池的缺点
以上代码其实是我们自己写的一个数据库连接池,它实现了一个addBack()方法,但是这个方法DataSource接口中并没有定义,如果要用这个方法还得另外去记。
所以,这种自定义数据库的写法的缺点是无法面向接口编程。
面向接口编程:
解决方法:
装饰者模式----面向接口编程
写一个ConnectionWrap.class
public class ConnectionWrap implements Connection{
Connection connection = null;
List<Connection> list;
public ConnectionWrap(Connection connection,List<Connection> list){
super();
this.connection = connection;
this.list = list
}
//重写close()方法
public void close() throws SQLException{
connection.close();
}
}
修改自定义的MyDataSource.class
puublic class MyDataSource implements DataSource{
public MyDataSource(){
for(int i=0;i<10;i++){
//JDBCUtil是自己写的jdbc工具类
Connection conn = JDBCUtil.getConnection();
list.add(conn);
}
}
public Connection getConnection()throws SQLException{
//检查池子里面还有没有连接
if(list.size()==0){
for(int i=0;i<5;i++){
//JDBCUtil是自己写的jdbc工具类
Connection conn = JDBCUtil.getConnection();
list.add(conn);
}
Connection conn = list.remove(0);
//把这个对象抛出去的时候,对这个对象进行包装
Connection connection = New ConnectionWrap(conn,list);
return connection;
}
}