本帖最后由 shjava 于 2017-9-25 18:43 编辑
【同步教程】JDBC注入、批处理及事物
作者:上海JavaEE教研部 V1.0 u SQL注入问题介绍 u SQL注入演示 u JDBC批处理 u 事物 u 全天总结 本章针对SQL注入、批处理以及事务的概念进行详细的讲解。
2.1 SQL注入问题介绍
所谓SQL注入,就是通过把SQL命令插入到Web 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 例如:用户在页面提交数据的时候人为的添加一些特殊字符,使得SQL语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录,如图2-1所示。
如淘宝账户登录页面: 如京东账户登录页面: 图2-1 淘宝和京东登录页面 整体登录的业务执行流程如图2-2所示
图2-2 整体登录业务流程图
快速入门登陆案例: 编写一个登录案例,要求通过用户名和密码到数据库中进行查询。如果用户存在显示登录成功,如果用户不存在就显示登录失败。
步骤一:建表并插入数据 【文件1-1】 创建表数据并添加数据 [Java] 纯文本查看 复制代码 1. -- 建表语句
2. create table user(
3. id int primary key auto_increment,
4. username varchar(30) not null,
5. password varchar(30) not null
6. );
7. insert intouser(id,username,password) values(null,'admin','admin');
步骤二:创建工程 【文件1-2】 User.java [Java] 纯文本查看 复制代码 1. public class User {
2. private int id;
3. private String username;
4. private String pasword;
5. public int getId() {
6. return id;
7. }
8. public void setId(int id) {
9. this.id = id;
10. }
11. public String getUsername() {
12. return username;
13. }
14. public void setUsername(String username) {
15. this.username = username;
16. }
17. public String getPasword() {
18. return pasword;
19. }
20. public void setPasword(String pasword) {
21. this.pasword = pasword;
22. }
23. @Override
24. public String toString() {
25. return "User [id=" + id + ", username=" +username + ", pasword=" + pasword + "]";
26. }
27. }
步骤三:编写UserDao 【文件1-3】 UserDao.java [Java] 纯文本查看 复制代码 1. public class UserDao{
2. public User login(User user){
3. Connection con = null;
4. Statement st = null;
5. ResultSet res = null;
6. try {
7. //获取连接
8. con = JdbcUtils.getConnection();
9. //创建数据库操作对象
10. st = con.createStatement();
11. //执行SQL
12. String SQL = "select * from User where username ='"+user.getUsername()+"' and password ='"+user.getPasword()+"'";
13. res = st.executeQuery(SQL);
14. //获取数据并封装
15. if(res.next()){
16. User queryUser = new User();
17. queryUser.setId(res.getInt("id"));
18. queryUser.setUsername(res.getString("username"));
19. queryUser.setPasword(res.getString("password"));
20. return queryUser;
21. }
22. } catch (Exception e) {
23. e.printStackTrace();
24. }finally{
25. JdbcUtils.release(con, st, res);
26. }
27. return null;
28. }
29. }
步骤四:编写UserAction 【文件1-4】UserAction.java [Java] 纯文本查看 复制代码 1. public class UserAction {
2. @Test
3. public void login() {
4. // 假设从页面获取到的数据为以下
5. User user = new User();
6. user.setUsername("admin");
7. user.setPassword("admin");
8. UserDao userDao = new UserDao();
9. // 调用UserDao中的login方法查询数据库
10. User queryUser = userDao.login(user);
11. // 如果能查询到数据,证明用户登录的账户和密码正确,如果返回的用户为null,证明用户登录的账户或者密码不正确
12. if (queryUser == null) {
13. // 用户登录失败
14. System.out.println("用户名和密码不正确,请重新输入!");
15. } else {
16. System.out.println("登录成功!");
17. }
18. }
19. }
说明: 上术案例是正常的登录案例,没有涉及到注入问题,目的是为下面注入问题做准备。
2.2 SQL注入演示
本章讲解的SQL注入: 在页面中输入SQL的片段,达到串改程序中SQL语句,如下2-3所示:
图2-3 SQL注入演示 由于读写“--”的时候,系统当做注释,后面的内容就不会解析,这样会造成系统不安全以及泄密。
【示例1】 如果知道用户名的情况下,可以使用SQL注入的方式规避密码验证,只需要使用"admin'-- " 将后面的SQL注释掉即可。将Dao层的测试代码修改如下:
注意:第五行("admin'-- ") 代码处"-- "后面有空格 【文件1-5】 UserAction.java [Java] 纯文本查看 复制代码 [/align][align=left]1. @Test
2. public void login() {
3. // 假设从页面获取到的数据为以下
4. User user = new User();
5. user.setUsername("admin'-- ");
6. user.setPassword("admin");
7. UserDao userDao = new UserDao();
8. // 调用UserDao中的login方法查询数据库
9. User queryUser = userDao.login1(user);
10. // 如果能查询到数据,证明用户登录的账户和密码正确,如果返回的用户为null,证明用户登录的账户或者密码不正确
11. if (queryUser == null) {
12. // 用户登录失败
13. System.out.println("用户名和密码不正确,请重新输入!");
14. } else {
15. System.out.println("登录成功!");
16. }
17. }
【示例2】 在不知道用户名和密码的情况下使用or关键字将语句条件进行更改。 SQL语句:SELECT * FROM USER WHEREusername = 'aaa' AND PASSWORD = 'bbb' OR '1' = '1' 由于OR关键字两边的任意一方条件成立即可,而1=1肯定是条件成立的,故也会执行该语句。 【文件1-6】 UserAction.java [Java] 纯文本查看 复制代码 [/align][align=left]1. @Test
2. public void login() {
3. // 假设从页面获取到的数据为以下
4. User user = new User();
5. user.setUsername("aaa");
6. user.setPassword("bbb'or '1'='1");
7. UserDao userDao = new UserDao();
8. // 调用UserDao中的login方法查询数据库
9. User queryUser = userDao.login1(user);
10. // 如果能查询到数据,证明用户登录的账户和密码正确,如果返回的用户为null,证明用户登录的账户或者密码不正确
11. if (queryUser == null){
12. // 用户登录失败
13. System.out.println("用户名和密码不正确,请重新输入!");
14. } else {
15. System.out.println("登录成功!");
16. }
17. }
2.2.1 SQL注入分析与解决方案SQL注入的原因分析: 之所以有SQL注入的问题,无非是在参数中设置了一些特殊字符,使SQL语句在拼接这些参数的时候因为特殊字符的原因改变了SQL语句原来的规则。 解决方案: 使用PreparedStatement 解决SQL注入问题,运行在SQL中参数以“?”占位符的方式表示。 举例: 原SQL语句: String SQL = "select * from user where username = 'admin' andpassword = 'admin' "; 使用?进行站位后的语句: String SQL = "select * from user where username = ? andpassword = ? "; PreparedStatement将带有“?”的SQL 发送给数据库完成预编译,预编译的SQL语句由于缺少两个参数,因此无法执行,需要通过PreparedStatement将预编译中被“?”占位的参数传递进来。而且由于SQL已经编译过,参数中特殊字符不再当做特殊字符编译,因此无法达到SQL注入的目的。
2.2.2PreparedStatement API 介绍 PreparedStatment : SQL只被编译一次,可以防止SQL的注入,SQL语句格式良好,方便阅读。 Statement : 调用几次编译几次。 PreparedStatment 使用方式基本和 statement的使用方式相同。 获取PreparedStatement对象:通过connection获取,API如图2-4。 如图2-4 PreparedStatment的API解析
注意: 上述方法在创建PreparedStatement 对象的时候需要先将SQL语句传递进去并进行预编译,因此SQL需要提前创建好。SQL语句中需要参数,使用“?”进行站位。然后获取到PreparedStatement对象后,在执行SQL语句之前需要先用参数把?替换掉。 可以使用setXxx(int parameterIndex,Xxx x)进行传递:Xxx为参数类型,如果String\int\double等,传递什么类型的参数就需要使用什么类型的setXxx(int parameterIndex,Xxx x)方法。
2.2.3 PreparedStatement案例目的: 修改UserDao中的login方法的代码,将Statement对象替换为PreparedStatement对象。 【文件1-7】 login2.java [Java] 纯文本查看 复制代码 1. public User login2(User user){
2. Connection con = null;
3. PreparedStatement ps = null;
4. ResultSet res = null;
5. try {
6. //获取连接
7. con = JdbcUtils.getConnection();
8. //创建数据库操作对象
9. String SQL = "select * from User where username = ?and password = ?";
10. //执行预编译
11. ps = con.prepareStatement(SQL);
12. //传递参数
13. ps.setString(1, user.getUsername());
14. ps.setString(2, user.getPasword());
15. //执行查询
16. res = ps.executeQuery();
17. //获取数据并封装
18. if(res.next()){
19. User queryUser = new User();
20. queryUser.setId(res.getInt("id"));
21. queryUser.setUsername(res.getString("username"));
22. queryUser.setPasword(res.getString("password"));
23. return queryUser;
24. }
25. } catch (Exception e) {
26. e.printStackTrace();
27. }finally{
28. JdbcUtils.release(con, ps, res);
29. }
30. return null;
31. } 结果: 再次测试发现,SQL注入的语句已经不起作用 了。
PreparedStatement优点: PreparedStatement是Statement的子接口,它的实例对象可以通过调Connection.preparedStatement(SQL)方法获得,相对于Statement对象而言: 1、PreperedStatement可以避免SQL注入的问题; 2、Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率; 3、PreperedStatement对于SQL中的参数,允许使用占位符的形式进行替换,简化SQL语句的编写,方便阅读,使代码的可读性更高。
2.3 JDBC批处理
2.3.1 批处理介绍和优点 在实际开发中,经常需要向数据库发送多条SQL语句,这时,如果逐条执行这些SQL语句,效率会很低。为此,JDBC提供了批处理提供了批处理机制,即同时执行多条SQL语句。 JDBC中提供两种批处理的方式: ●Statement批处理 ●PrepareStatement批处理 两种批处理的方式都可以批量的执行SQL语句,以达到提升效率的目的。 2.3.2 Statement 批处理
2.3.2.1常用方法 ●Statement.addBatch(SQL):添加批处理的SQL语句 ●Statement.executeBatch() :执行批处理命令 ●Statement.clearBatch() :清除批处理命令 2.3.2.2测试【示例】 使用statement执行批处理命令。 需求: 1、创建一个名叫batch_test的数据库; 2、切换到新建的数据库; 3、在该数据库中创建一张名为batch的表,字段有id,name,password; 4、向该表插入三条数据。
创建一个测试类 【文件1-8】 JdbcBatch.java [Java] 纯文本查看 复制代码 1. public class JdbcBatch {
2. @Test
3. public void statementBatch(){
4. Connection con = null;
5. Statement st = null;
6. //获取数据库连接
7. try {
8. con = JdbcUtil.getConnection();
9. //获取SQL操作对象
10. st = con.createStatement();
11. //创建数据库
12. String SQL1 = "create database batch_test";
13. //切换数据库
14. String SQL2 = "use batch_test";
15. //创建表
16. String SQL3 = "create table batch(id int primary keyauto_increment,name varchar(20),password varchar(20))";
17. //插入数据
18. String SQL4 = "insert into batchvalues(null,'zhangsan','123')";
19. String SQL5 = "insert into batchvalues(null,'lisi','123')";
20. String SQL6 = "insert into batchvalues(null,'wangwu','123')";
21. //将SQL语句添加到批处理中
22. st.addBatch(SQL1);
23. st.addBatch(SQL2);
24. st.addBatch(SQL3);
25. st.addBatch(SQL4);
26. st.addBatch(SQL5);
27. st.addBatch(SQL6);
28. //执行批处理
29. st.executeBatch();
30. } catch (Exception e) {
31. e.printStackTrace();
32. }finally{
33. //释放资源
34. JdbcUtil.release(con, st);
35. }
36. }
37. } 说明: 上述代码已经将创建一个名叫batch_test的数据库、切换到新建的数据库、在该数据库中创建一张名为batch的表,字段有id,name,password和向该表插入三条数据四条步骤以代码的形式表示出来。 2.3.2.3总结采用Statement.addBatch(SQL)方式实现批处理: 优点:可以向数据库发送多条不同类型的SQL语句。 缺点: SQL语句没有预编译,有注入风险。当向数据库发送多条语句相同,但参数不同的SQL语句时,需重复写上很多条SQL语句并且编译多次。例如: Insert into batch values(‘aa’,’111’); Insert into batch values(‘bb’,’222’); Insert into batch values(‘cc’,’333’); Insert into batch values(‘dd’,’444’);
2.3.3 Preparestatement批处理
2.3.3.1常用方法 ● Connection.prepareStatement(SQL): 预编译SQL语句; ● PreparedStatement.addBatch() : 增加批处理; ● PreparedStatement.executeBatch() : 执行批处理命令; ● PreparedStatement.clearBatch() : 清除批处理的命令。 2.3.3.2测试【示例】 需求: 在数据库中插入10500条数据。 注意: 使用PreparedStatement添加批处理的使用不需要添加SQL。如:Statement批处理:st.addBatch(SQL),而PreparedStatement批处理:st.addBatch()。
【文件1-9】prepareStatementBatch.java [Java] 纯文本查看 复制代码 1 @Test
2 public void prepareStatementBatch(){
3 Connection con = null;
4 PreparedStatement st = null;
5 //获取连接
6 try {
7 con = JdbcUtil.getConnection();
8 String SQL1 ="use batch_test";
9 String SQL2 = "insert into batchvalues(null,?,?)";
10 //预编译SQL
11 st = con.prepareStatement(SQL2);
12 //切换数据库
13 st.execute(SQL1);
14 for (int i = 1; i <=10500; i++) {
15 //发送SQL进行预编译
16 //传递参数
17 st.setString(1, "name"+i);
18 st.setString(2, "abc"+i);
19 st.addBatch();
20 if (i%1000==0) {
21 //没1000条语句执行一次批处理。
22 st.executeBatch();
23 //清楚批处理
24 st.clearBatch();
25 }
26 }
27 //执行剩下的语句
28 st.executeBatch();
29 } catch (Exception e) {
30 e.printStackTrace();
31 }finally{
32 //释放资源
33 JdbcUtil.release(con, st);
34 }
35 }
2.3.3.3总结PrepareStatement批处理的优缺点: 优点:发送的是预编译后的SQL语句,执行效率高。 缺点:只能在SQL语句相同,当参数不同的批处理中使用。 因此这种批处理的方式经常适用于在同一个表中批量插入或者更新数据。
2.4事务
2.4.1 什么是事务 事务(Transaction),一般是指要做的或所做的事情。在计算机 术语中是指访问并可能更新数据库中各种 数据项的一个程序执行单元(unit)。事务(Transaction)是访问并可能更新数据库中各种 数据项的一个程序执行单元(unit)。事务通常由 高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的 用户程序的执行所引起,并用形如begin transaction和end transaction语句(或 函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。 本章中表示一组SQL语句(insert、update、delete),要么全部执行成功,要么全部执行失败,不能成功或者失败其中一部分。
举例: a 和 b的账户中都有1000元, a给b转账100元, 转账会发生什么状况? 情况一: 转账成功: a:a-100=900 b:b+100=1100 总金额保持不变
情况二: 转账失败: a:a-100=900 发生异常了,下面的这条语句执行不到了,造成数据丢失,少了100 b:b+100=1100 总金额就少100
使用SQL语句描述上述过程: [Java] 纯文本查看 复制代码 1. 正常状态:
2. update account set salary =salary -100 where name = ‘a’;
3. update account set salary =salary+100 where name = ‘b’;
4.
5.
6. 非正常状态:
7. update account set salary =salary -100 where name = ‘a’;
8. # 发生异常了
9. update account set salary =salary+100 where name = ‘b’; 如果以上的事件经常在银行的系统上发生,那么显然会造成客户的损失。 如何解决这个问题? 由于异常的发生是不可预测的,因此无法从杜绝异常着手。那么能否产生一种机制,在操作一组SQL语句的时候,如果发生异常,可以使其恢复到转账之前的状态。事务的出现就是为了解决类似以上问题。 上述案例事物的状态: 正常状态: 开启事务 a=1000-100 b=1000+100 提交事务 (将修改后的数据持久化到数据库中)
非正常状态: 开启事务 a=1000-100 发生异常:回滚事务,将a的操作进行还原,让其回到操作之前的状态,而下面的操作就执行不到了。 b=1000+100 提交事务 事务的执行过程,如图2-5所示: 图2-5 事务的执行过程 事务的使用步骤: 1、开启事务 2、执行一组SQL语句 3、提交事务/回滚事务
2.4.2 MySQL中的事务管理 在MySQL 中,事务默认是“ 自动打开,自动提交”。 每一条SQL就是一个单独的事务,所以不需要 事务开启、事务回滚、事务提交。 MySQL中事务的使用: start transaction:开启事务,以后的SQL都在一个事务中。更改的内容不会自动提交。 Rollback :回滚事务,都失败的情况。事务结束,全部失败,数据恢复到事务未开启之前的状态。 Commit :提交事务,都成功的情况。事务结束,全部成功。
需求: 按照a给b转账的例子在mySQL中使用事务进行测试
准备工作: 1、创建财务表 account , 里面有 姓名列 name 和 余额列 salary; 2、插入初始数据a=1000,b=1000; 3、a给b转账100。
【示例:演示事务回滚】 步骤一:创建财务表account,表中有列名name和salary,并且插入初始数据 a 和 b各自1000元。 [Java] 纯文本查看 复制代码 10. #创建account表
11. create table account(
12. name varchar(20),
13. salary double
14. );
15. #插入初始数据:a 和 b 各自1000元
16. insert into accountvalues('a',1000);
17. insert into accountvalues('b',1000);
步骤二:开启事务后,a向b开始转账,然后回滚事务 [Java] 纯文本查看 复制代码 18. #开启事务
19. start transaction;
20. #执行一组SQL的操作
21. update account set salary =salary - 100 where name = 'a';
22. update account set salary =salary + 100 where name = 'b';
23. #查询结果
24. select * from account
25.
26. #事务回滚
27. rollback
28. #再次查询结果
29. select * from t_account t
【示例:演示事务提交】 [Java] 纯文本查看 复制代码 36 #开启事务
37 start transaction
38
39 #执行一组SQL的操作
40 update account set salary =salary - 100 where name = 'a';
41 update account set salary =salary + 100 where name = 'b';
42
43 #查询结果
44 select * from account
45
46 #事务回滚
47 commit
48
49 #再次查询结果
50 select * from account
2.4.3 JDBC中的事务管理
2.4.3.1JDBC的事务介绍: JDBC的事务管理,是通过Connection对象来完成的。当JDBC程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会 自动向数据库提交 在它上面的发送的SQL语句。若想关闭这种默认的提交方式,需要使用以下三个方法:如图2-6、2-7、2-8所示。 图2-6 setAutoCommit() API解析 说明: 参数为false,表示禁用自动提交模式,相当于 strart transaction:开启事务。 参数为true,表示自动提交,默认就是true。 相当于一条SQL语句就是一个事务。 开启事务:con.setAutoCommit(false);
图2-7 rollback()API解析 说明: 相当于rollback(), 回滚事务。 表示事务结束,取消当前事务的所有更改。 注意:con.rollback() 应该只在开启事务时使用,即con.setAutoCommit(false); 时才有效。
图2-8 commit() API解析 说明: 相当于commit() 提交事务。表示事务结束,更改有效。 注意:con.commit() 应该只在开启事务时使用,即conn.setAutoCommit(false);时才有效。
JDBC中事务的使用步骤: 1、开启事务:connection.setAutoCommit(false) 2、执行一组SQL语句 3、提交事务:connection.commit()/回滚事务:connection.rollback(); MySQL事务和jdbc事务的对比,如图2-9所示。 图2-9 MySQL事务和JDBC事务的对比 需求: a和b初始账户1000元,a向 b进行转账 100元。 使用JDBC演示事务的操作。
【示例:未添加事务】 步骤一:编写测试案例 【文件1-10】TransactionTest.java [Java] 纯文本查看 复制代码 1. public class TransactionTest {
2. @Test
3. public void demo1(){
4. Connection con = null;
5. PreparedStatement st = null;
6. try {
7. //获取数据库连接
8. con= JdbcUtil.getConnection();
9. //定义SQL
10. String SQL1 = "update account set salary = salary -100 where name = 'a'";
11. //执行SQL
12. String SQL2 = "update account set salary = salary +100 where name = 'b'";
13.
14. //对SQL1进行预编译
15. st=con.prepareStatement(SQL1);
16. //执行SQL1
17. st.executeUpdate();
18.
19. //对SQL2进行预编译
20. st=con.prepareStatement(SQL2);
21. //执行SQL2
22. st.executeUpdate();
23. } catch (Exception e) {
24. e.printStackTrace();
25. }finally{
26. JdbcUtil.release(con, st);
27. }
28. }
29. }
步骤二:异常测试 【文件1-11】Demo1.java [Java] 纯文本查看 复制代码 1. @Test
2. public void demo1() {
3. Connection con = null;
4. PreparedStatement st = null;
5. try {
6. // 获取数据库连接
7. con = JdbcUtil.getConnection();
8. // 定义SQL
9. String SQL1 = "update t_account set salary = salary- 100 where name = 'a'";
10. // 执行SQL
11. String SQL2 = "update t_account set salary = salary+ 100 where name = 'b'";
12. // 对SQL1进行预编译
13. st = con.prepareStatement(SQL1);
14. // 执行SQL1
15. st.executeUpdate();
16. // 发生异常
17. int x = 1 / 0;
18. // 对SQL2进行预编译
19. st = con.prepareStatement(SQL2);
20. // 执行SQL2
21. st.executeUpdate();
22. } catch (Exception e) {
23. e.printStackTrace();
24. } finally {
25. JdbcUtil.release(con, st);
26. }
27. }
步骤三:测试 结果:结果丢失了100,如图2-10所示。 图2-10 未添加事物测试结果 【示例:添加事务】 修改demo1方法: 【文件1-12】demo1.java [Java] 纯文本查看 复制代码 1. @Test
2. public void demo1() {
3. Connection con = null;
4. PreparedStatement st = null;
5. try {
6. // 获取数据库连接
7. con = JdbcUtil.getConnection();
8. //开启事务
9. con.setAutoCommit(false);
10. // 定义SQL
11. String SQL1 = "update t_account set salary = salary- 100 where name = 'a'";
12. // 执行SQL
13. String SQL2 = "update t_account set salary = salary+ 100 where name = 'b'";
14. // 对SQL1进行预编译
15. st = con.prepareStatement(SQL1);
16. // 执行SQL1
17. st.executeUpdate();
18. // 发生异常
19. int x = 1 / 0;
20. // 对SQL2进行预编译
21. st = con.prepareStatement(SQL2);
22. // 执行SQL2
23. st.executeUpdate();
24.
25. //提交事务
26. System.out.println("提交事务");
27. con.commit();
28. } catch (Exception e) {
29. e.printStackTrace();
30. //回滚事务
31. System.out.println("回滚事务");
32. try {
33. if (con!=null) {
34. con.rollback();
35. }
36. } catch (SQLException e1) {
37. e1.printStackTrace();
38. }
39.
40. } finally {
41. JdbcUtil.release(con, st);
42. }
43. } 注意: 1、开启事务 conn.setAutoCommit(false)必须在 建立connection之后设置。 2、抓异常时,最好选得大一些。因为 int x = 1/0 报的是算术异常,不在SQL异常范围内,所以最好改成Exception。
2.4.3.2事务的回滚点事务的回滚点介绍: 保存点的表示形式,保存点是可以从 Connection.rollback 方法引用的当前事务中的点。将事务回滚到保存点时,在该保存点之后所作的全部更改都将被撤消。
事务的回滚点有什么作用? JDBC使用接口Savapoint表示事务回滚点,当出现异常事务回滚时,使用savapoint 更新回滚点之前的数据。类似于单机游戏的存档和读档: 1、如果没有游戏的存档功能,每次玩单机游戏都会从第一关重新开始。 2、如果使用了游戏的存档功能,下次在玩游戏时,就会从存档处满血复活。
事务回滚点使用图例,如图2-11所示。 图2-11 事务回滚点使用图例
如何设置事务的回滚点? 使用connection.setSavepoint()方法,API如图2-12所示。 图2-12 setSavepoint() API解析 如何使用事务的回滚点把数据回滚到指定的位置? 使用connection.rollback()方法,把数据会滚到指定回滚点的位置,API如图2-13所示。 图2-13 rollback() API解析
【示例:测试事务的回滚点】 需求: 向数据库表emp中插入10000条数据,每隔1000条数据设置一个回滚点。在插入第9999条时发生异常,将数据回滚到9000存档处。
步骤一:创建一个emp表 [Java] 纯文本查看 复制代码 create table emp(
id int primary key auto_increment,
name varchar(30) not null,
age int
);
步骤二:编写测试代码 【文件1-13】Demo2.java [Java] 纯文本查看 复制代码 1. /**
2. * 事务回滚点的使用
3. */
4. //需求:向数据库表emp中插入10000条数据,每隔1000条数据设置一个回滚点。在插入第9999条是发生异常,将数据回滚到9000存档处。
5. @Test
6. public void demo2(){
7. Connection con = null;
8. PreparedStatement ps = null;
9. Savepoint savepoint = null;
10.
11. try {
12. //建立连接
13. con = JdbcUtils.getConnection();
14. //开启事务
15. con.setAutoCommit(false);
16. //对SQL进行预编译
17. String SQL = "insert into empvalues(null,?,?)";
18. ps = con.prepareStatement(SQL);
19. //执行一组SQL语句,向数据库插入10000条数据
20. for (int i = 1; i <= 10000; i++) {
21. //传参
22. ps.setString(1, "name"+i);
23. ps.setInt(2, 20);
24. //执行插入
25. ps.executeUpdate();
26.
27. //每1000条记录设置一个回滚点
28. if (i%1000==0) {
29. savepoint = con.setSavepoint();
30. }
31.
32. //第9999条记录发生异常
33. if (i==9999) {
34. int x = 1 /0;
35. System.out.println("第9999条记录发生异常......");
36. }
37. }
38. //执行提交事务
39. con.commit();
40. System.out.println("数据插入完毕......");
41. } catch (Exception e) {
42. e.printStackTrace();
43. System.out.println("发生异常了......");
44. //如果发生异常,事务回滚
45. try {
46. if (con !=null) {
47. //回滚到指定的回滚点
48. con.rollback(savepoint);
49. //提交事务,将回滚点之前的数据存入数据库中
50. con.commit();
51. }
52. } catch (SQLException e1) {
53. e1.printStackTrace();
54. }
55. }finally{
56. //释放资源
57. JdbcUtils.release(con, ps);
58. }
59.
60. } 注意: 1、设置回滚点,必须事务开启之后。 2、事务回滚,需要设置一个回滚点。 3、事务回滚到回滚点之后,还需要提交事务,才能将回滚点之前的数据持久化到数据库。
2.5全天总结
SQL注入问题:是指在进行SQL语句拼接的时候,因为传递的参数中有特殊字符,在进行SQL语句编译的时候,将这些字符作为特殊字符处理了,因此改变了原来的SQL语句的结构。 解决方案:PreparedStatement可以解决SQL注入问题,该api会对SQL语句进行一个预编译,然后参数通过?先进行一个占位,当要执行SQL语句的时候,只要将参数传递到编译后的SQL中去即可,而由于SQL已经被编译过了,因此会将传递过来的参数作为一个字符串来进行处理,包括其中的特殊字符也会作为参数的一部分。
JDBC批处理: Statement批处理 优点:可以对不同类型的SQL语句执行批处理。 缺点:可能会发生SQL注入问题,每一个SQL语句都要执行编译。 PreparedStatement批处理 优点:相同类型的SQL只编译一次,需要执行的时候,只要传递不同的参数即可,效率高,可以防止SQL注入 缺点:不能执行不同类型的SQL。
事务: 对一组SQL语句进行管理,将其视为一个整体,要么全部成功,要么全部失败! 事务的使用: 开启事务 执行一组SQL 提交事务/回滚事务
事务的回滚点的设置: 通过connection可以设置事务的回滚点,当发生异常之后,可以回滚到指定的回滚点,将之前的操作进行持久化-提交事务,这样的好处就是不需要从头开始执行操作。
|