前言
- 务必先看完我写的带有MySQL标签的文章与JDBC深入这篇文章
- 之前,JDBC深入这篇文章中使用
Statement
语句执行SQL语句处,我说了会存在SQL注入问题,各大网站在2016年之前都对SQL注入登录的用户进行账号冻结,现在只是提示你账号密码错误。黑客们在洪荒时期可能会使用这种方法攻击某些小网站,非常有效。 - 这里,我还想在JDBC连接中,谈谈事务处理的问题,这在实际开发中具有重要作用
SQL注入问题
什么业务会遇到SQL注入问题?
最普遍的就是用户登录功能的实现
登录业务模拟SQL注入
JDBC程序运行的时候,通常会提供一个输入的入口,可以让用户输入用户名和密码,用户输入用户名和密码之后,提交信息,java程序收集到用户信息,Java程序连接数据库验证用户名和密码是否合法
- 合法:显示登录成功
- 不合法:显示登录失败
我们来看下面的代码,关键在于Statement
的对象来执行select
语句
Statement stmt = conn.createStatement();
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";
rs = stmt.executeQuery(sql);
以上正好完成了sql语句的拼接,上述代码的含义是:
- 发送sql语句给DBMS,DBMS进行sql编译。
- 正好将用户提供的“非法信息”编译进去。导致了原sql语句的含义被扭曲了
当前程序存在的问题,如果我们使用下列方式登录,隐患非常大
用户名:随意
密码:随意' or '1'='1
无论用户名在数据库中是否存在,or '1' = '1'
永远为真,从而可以顺利登录
这种现象就是SQL注入
解决方案
我们分析一下为什么会出现SQL注入?
- 用户是计算机专业人士
- 用户提供的信息“非法”(含有SQL关键字)并参与了SQL语句编译
所以关键在于我们要让用户提供的信息不参与SQL语句的编译过程,这样的话,即使用户提供的信息中含有SQL语句的关键字,但是它没有参与编译,不起作用
而要想用户信息不参与SQL语句的编译,那么我们必须使用java.sql.PreparedStatement
PreparedStatement
这是一个接口,
- 它继承了
java.sql.Statement
它是属于预编译的数据库操作对象
原理:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”,完美解决了SQL注入问题
关于PreparedStatement
具体使用和代码细节见下
// 获取预编译的数据库操作对象
// SQL语句的框子。其中一个?,表示一个占位符,一个?将来接收一个“值”,注意:占位符不能使用单引号括起来。
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
// 程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译。
ps = conn.prepareStatement(sql);
// 给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始。)
ps.setString(1, loginName);
ps.setString(2, loginPwd);
// 执行sql
rs = ps.executeQuery();
对比Statement和PreparedStatement
-
Statement
存在sql
注入问题,PreparedStatement
解决了SQL注入问题。 -
Statement
是编译一次执行一次。PreparedStatement
是编译一次,可执行N次,PreparedStatement
效率较高一些。 -
PreparedStatement
会在编译阶段做类型的安全检查。
JDBC事务使用
JDBC中的事务是自动提交的,什么是自动提交?
只要执行任意一条DML语句,则自动提交一次。这是JDBC默认的事务行为。
但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败(比如银行转账)
模拟JDBC事务
准备SQL脚本
drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2) // 注意:7表示有效数字的个数,2表示小数位的个数。
);
insert into t_act(actno,balance) values(111,20000);
insert into t_act(actno,balance) values(222,0);
commit;
select * from t_act;
JDBC开发最终常用代码
public class JDBCTest11 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root","333");
// 将自动提交机制修改为手动提交
conn.setAutoCommit(false); // 开启事务
// 3、获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
// 给?传值
ps.setDouble(1, 10000);
ps.setInt(2, 111);
int count = ps.executeUpdate();
//String s = null;
//s.toString();
// 给?传值
ps.setDouble(1, 10000);
ps.setInt(2, 222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
// 程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
conn.commit(); // 提交事务
} catch (Exception e) {
// 回滚事务
if(conn != null){
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 6、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}