JDBC的使用
1. SQL注入
什么是SQL注入
所谓 SQL 注入, 就是通过把含有 SQL 语句片段的参数插入到需要执行的 SQL 语句中,
最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。
SQL注入案例
private static void selectDepartmentsByIdAndName(String name, int id) {
String sql = "select * from departments where department_name = '" + name + "' and department_id = " + id;
System.out.println("sql = " + sql);
Connection connection = DBUtil.getConnection();
Statement statement = null;
ResultSet resultSet = null;
try {
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
System.out.println("resultSet.getObject(1) = " + resultSet.getObject(1));
System.out.println("resultSet.getObject(2) = " + resultSet.getObject(2));
System.out.println("resultSet.getObject(3) = " + resultSet.getObject(3));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(resultSet, statement, connection);
}
}
可以看到这个函数可以根据department_name和department_id 查询到一行数据. 但是当我们调用此函数是这样传参
selectDepartmentsByIdAndName("Te2' or 1=1 -- ", 272);
打印出来sql语句是这样的. 查询的是1=1, 永真的数据. 也就是全部的数据. 而传递过去的-- 则把后面的条件注释了. 这就是一个典型的SQL注入问题
select * from departments where department_name = 'Te2' or 1=1 -- ' and department_id = 272
2. PreparedStatement的使用
2.1 PreparedStatement特点
- PreparedStatment接口继承Statement接口
- PreparedStatement效率高于Statement
- PreparedStatement支持动态绑定参数
- PreparedStatement具备SQL语句预编译的能力
- 使用PreparedStatement可防止出现SQL注入问题
2.2 通过PreparedStatement对象向表中插入数据
private static void insertDepartment(String department_name) {
// 通过使用? 进行占位处理, 实现动态绑定数据, 防止sql注入
String sql = "insert into departments (department_id, department_name, manager_id, location_id) values (default, ?, ?, ?)";
Connection connection = DBUtil.getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
statement.setString(1, department_name);
statement.setInt(2, 205);
statement.setInt(3, 2400);
statement.execute();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(statement, connection);
}
}
2.3 使用PreparedStatement查询一条数据
/**
* 查询一条数据
* @param departmentId 从数据库中查询一条数据
* @return 返回查询到的一条数据
*/
private static Departments selectDepartment(int departmentId) {
String sql = "select * from departments where department_id = ?;";
Connection conn = null;
PreparedStatement statement = null;
Departments departments = null;
try {
conn = DBUtil.getConnection();
statement = conn.prepareStatement(sql);
statement.setInt(1, departmentId);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
departments = new Departments();
departments.setDepartmentId(resultSet.getInt(1));
departments.setDepartmentName(resultSet.getString(2));
departments.setLocationId(resultSet.getInt(3));
departments.setManagerId(resultSet.getInt(4));
}
} catch (SQLException e) {
e.printStackTrace();
}
return departments;
}
2.4 使用PreparedStatement查询多条数据
/**
* 查询多条数据
* @param departmentName 模糊查询的字符串, 匹配department_name
* @return 返回查询到的多条数据的容器
*/
private static List<Departments> selectDepartments(String departmentName) {
String sql = "select * from departments where department_name like ?;";
Connection conn = null;
PreparedStatement statement = null;
List<Departments> list = new ArrayList<>();
try {
conn = DBUtil.getConnection();
statement = conn.prepareStatement(sql);
statement.setString(1, "%" + departmentName + "%");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
Departments departments = new Departments();
departments = new Departments();
departments.setDepartmentId(resultSet.getInt(1));
departments.setDepartmentName(resultSet.getString(2));
departments.setLocationId(resultSet.getInt(3));
departments.setManagerId(resultSet.getInt(4));
list.add(departments);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
2.5 使用PreparedStatement批量添加数据
/**
* 批量添加数据
* @param list 添加的数据容器
*/
private static void addBatch(List<Departments> list){
String sql = "insert into departments (department_id, department_name, manager_id, location_id) values (default, ?, ?, ?);";
Connection conn = null;
PreparedStatement statement = null;
try {
conn = DBUtil.getConnection();
statement = conn.prepareStatement(sql);
for (Departments departments : list) {
statement.setString(1, departments.getDepartmentName());
statement.setInt(2, departments.getManagerId());
statement.setInt(3, departments.getLocationId());
statement.addBatch();
}
statement.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(statement, conn);
}
}
3. PreparedStatement的预编译能力
3.1 什么是预编译
3.1.1 SQL语句的执行步骤
- 语法和语义解析
- 优化sql语句, 指定执行计划
- 执行并返回结果
但是很多情况, 我们的一天sql语句可能会反复执行, 或者每次执行的时候只有个别的值不同(比如select的where字句值不同, update的set字句值不同,insert的values值不同). 如果每次都需要经过上面的词法语义解析, 语句优化, 指定执行计划等, 则效率就明显不行了.
所谓预编译语句就是将这类语句中的值用占位符替代, 可以视为将sql语句模板化或者说参数化
预编译语句的优势在于: 一次编译、 多次运行, 省去了解析优化等过程; 此外预编译语
句能防止 sql 注入
3.1.2 解析过程
-
硬解析 (statement使用形式)
在不开启缓存执行计划的情况下, 每次sql的处理都要经过: 语法和语义的解析, 优化处理器处理sql, 生成执行计划. 整个过程我们称之为硬解析.
-
软解析 (PreparedStatement使用形式)
如果开启了缓存执行计划, 数据库在处理 sql 时会先查询缓存中是否含有与当前 SQL
语句相同的执行计划, 如果有则直接执行该计划
3.2 预编译方式
3.2.1 依赖数据库驱动完成预编译(建议)
如果我们没有开启数据库服务端编译, 那么默认使用的是数据库驱动完成sql的预编译
3.2.2 依赖数据库服务器完成预编译
我们可以通过修改连接数据库的 URL 信息, 添加 useServerPrepStmts=true 信息开启服
务端预编译。
4. JDBC中的事务处理
在 JDBC 操作中数据库事务默认为自动提交。 如果事务需要修改为手动提交, 那么我们
需要使用 Connection 对象中的 setAutoCommit 方法来关闭事务自动提交。 然后通过
Connection 对象中的 commit 方法与 rollback 方法进行事务的提交与回滚。
//事务处理
public void deleteDempartments(String depratmentName){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = JdbcUtil.getConnection();
//关闭事务的自动提交
conn.setAutoCommit(false);
ps = conn.prepareStatement("delete from departments where department_name like ?");
ps.setString(1, "%"+depratmentName+"%");
ps.executeUpdate();
ps = conn.prepareStatement("insert into departments values(default,'开发部',2)");
ps.executeUpdate();
String str = null;
str.length();
conn.commit();
}catch(Exception e){
e.printStackTrace();
JdbcUtil.rollback(conn);
}finally{
JdbcUtil.closeResource(ps, conn, null);
}
}