1. JDBC批处理
1.1 基本概念
批量处理允许将相关的SQL语句分组到批次中处理, 并通过对数据库的一次调用提交他们.
当一次向数据库发送多个SQL语句时, 可以减少连接数据库的开销, 从而调高性能
批处理实现方式:
PreparedStatement:
1. 使用占位符创建SQL语句
2. 使用preparedStatement() 方法创建PreparedStatement对象
3. 使用setAutoCommit() 将auto-commit设置为false
4. 使用addBatch() 方法在创建的语句对象上添加需求的SQL语句到批处理中
5. 在创建的语句对象上使用executeBatch() 方法执行所有SQL语句
6. 使用commit()方法提交所有更改
1.2 代码演示:
/*
* PrepareStatement对象进行批处理
*/
public class Demo1 {
@Test
public void test1(){
List<Employees> list=new ArrayList<Employees>();
list.add(new Employees(206, "tom1", "123", 123));
list.add(new Employees(207, "tom2", "123", 123));
list.add(new Employees(208, "tom3", "123", 123));
list.add(new Employees(209, "tom4", "123", 123));
list.add(new Employees(210, "tom5", "123", 123));
list.add(new Employees(211, "tom6", "123", 123));
list.add(new Employees(212, "tom7", "123", 123));
Connection conn=JdbcUtils.openConn();
try {
conn.setAutoCommit(false); //关闭自动提交
String sql="INSERT INTO Employees (id, first, last, age) VALUES(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (Employees e : list) {
ps.setObject(1, e.getId());
ps.setObject(2, e.getFirst());
ps.setObject(3, e.getLast());
ps.setObject(4, e.getAge());
ps.addBatch();
}
int[] arrays = ps.executeBatch(); //执行批处理
conn.commit(); //手动提交
System.out.println("执行完毕:"+Arrays.toString(arrays));
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.3 总结
优点:
发送的是预编译后的SQL语句,执行效率高
缺点:
只能应用在SQL语句相同,但参数不同的批处理中.因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据
注意:
JDBC的批处理不能加入select语句,否则会抛异常!
2. 事务
2.1 事务的概念
事务:
逻辑上的一组操作, 组成这组操作的各个单元, 全部成功或者全部失败
例如:
A-->B转账
update from account set money=money+100 where name='B';
update from account set money=money-100 where name='A';
这两条SQL语句要么同时成功 要么同时失败, 这种组操作就是事务!
2.2 事务的语法
2.2.1 在MySQL数据库中开启事务
start transaction - 开启事务
commit - 提交事务
trollback - 回滚事务
2.2.2 在JDBC中开启事务
-
模拟转账成功时的业务场景
public static void main(String[] args) { Connection conn = null; PreparedStatement st = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123"); conn.setAutoCommit(false);//通知数据库开启事务(start transaction) String sql1 = "update account set money=money-100 where name='A'"; st = conn.prepareStatement(sql1); st.executeUpdate(); String sql2 = "update account set money=money+100 where name='B'"; st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit) System.out.println("成功!!!"); // } catch (Exception e) { try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } }
-
模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
public static void main(String[] args) { Connection conn = null; PreparedStatement st = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123"); conn.setAutoCommit(false);// 通知数据库开启事务(start transaction) String sql1 = "update account set money=money-100 where name='A'"; st = conn.prepareStatement(sql1); st.executeUpdate(); // 用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作 int x = 1 / 0; String sql2 = "update account set money=money+100 where name='B'"; st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit();// 上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit) System.out.println("成功!!!"); } catch (Exception e) { e.printStackTrace(); } }
-
模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库手动回滚事务
public static void main(String[] args) { Connection conn = null; PreparedStatement st = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123"); conn.setAutoCommit(false);// 通知数据库开启事务(start transaction) String sql1 = "update account set money=money-100 where name='A'"; st = conn.prepareStatement(sql1); st.executeUpdate(); // 用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作 int x = 1 / 0; String sql2 = "update account set money=money+100 where name='B'"; st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit();// 上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit) System.out.println("成功!!!"); } catch (Exception e) { try { //捕获到异常之后手动通知数据库执行回滚事务的操作 conn.rollback(); System.out.println("程序回滚啦"); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } }
2.3 设置事务回滚点
在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点:
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit();//回滚后必须通知数据库提交事务
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
Savepoint sp = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123");
conn.setAutoCommit(false);// 通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 设置事务回滚点
sp = conn.setSavepoint();
String sql2 = "update account set money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
// 程序执行到这里出现异常,后面的sql3语句执行将会中断
int x = 1 / 0;
String sql3 = "update account set money=money+100 where name='C'";
st = conn.prepareStatement(sql3);
st.executeUpdate();
conn.commit();
} catch (Exception e) {
try {
/**
* 我们在上面向数据库发送了3条update语句,
* sql3语句由于程序出现异常导致无法正常执行,数据库事务而已无法正常提交,
* 由于设置的事务回滚点是在sql1语句正常执行完成之后,sql2语句正常执行之前,
* 那么通知数据库回滚事务时,不会回滚sql1执行的update操作
* 只会回滚到sql2执行的update操作,也就是说,上面的三条update语句中,sql1这条语句的修改操作起作用了
* sql2的修改操作由于事务回滚没有起作用,sql3由于程序异常没有机会执行
*/
conn.rollback(sp);//回滚到设置的事务回滚点
//回滚了要记得通知数据库提交事务
conn.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
2.4 事务的4大特性 ACID
原子性 Atomicity:
原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败.
比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败!
一致性 Consistency
官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态.
以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性
隔离性 Isolation
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
持久性 Durability
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
2.5 不考虑隔离级别可能会引发的问题
2.5.1 脏读
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中
这时,另外一个事务也访问这个数据,然后使用了这个数据
例如:
A工资为1000,事务A中把他的工资减去100,再把B的工资加上100,但事务A尚未提交
与此同时,事务B正在查询工资表,读取到A的工资为900,B的工资为1100
随后,事务A发生异常,而回滚了事务.A的工资又回滚为1000,B的工资也回滚为1000
最后,最终事务B读取到A的工资为900,B的工资为1100,事务B做了一次脏读
脏读最严重的事情、读取数据是不真实的!
2.5.2 不可重复读
一个事务在读取某一项数据的时候, 多次重复读取到的数据不同, 就叫做不可重复读
例如:
事务A中有读取某项数据N次的命令, 事务B对事务A所读取的数据做出了修改操作, 导致事务A在本次事务中读取到的这项数据值发生了改变, 就出现了不可重复读错误.
2.5.3 虚读(幻读)
一个事务A在读取某项数据的时候另一个事务B对数据的结构做出了修改, 导致A事务读取到的数据条数改变, 就是虚读
2.6 数据库的事务隔离级别
2.6.1 隔离级别类型
注意:
事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低
2.6.2 MySQL相关API
select @@tx_isolation
查询当前事务隔离级别
select @@global.tx_isolation
查看系统会话隔离级别
set session transaction isolatin level repeatable read
设置当前会话隔离级别
set global transaction isolatin level repeatable read
设置系统会话隔离级别