JDBC操作数据库(三) ——抽取工具类
JDBC工具类抽取
前面已经使用JDBC把查询和修改数据库代码抽取出来方法,同样这里我们可以抽取出一个工具类DBUtil
1.直接把查询和修改的方法放入工具类中
public class DBUtil {
public static void query(String sql,IRowMapper rowMapper) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
rowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (resultSet!=null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement!=null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (connection!=null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static boolean update(String sql) {
Connection connection = null;
Statement statement= null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
statement= connection.createStatement();
return statement.executeUpdate(sql)>0;
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (statement!=null) {
statement.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
try {
if (connection !=null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}
}
执行查询方法创建的接口
interface IRowMapper{
void rowMapper(ResultSet rs);
}
2.把查询和修改的方法中重复的代码也抽取成方法
从上面可以看出数据库查询和和更改主要步骤可以分为六步
- 加载驱动
- 建立连接
- 创建Statement对象
- 书写SQL语句
- 执行SQL语句
- 释放资源
而这两种方法中的第1,2,6都是相同的,所以可以把它们也单独的抽取出一个方法方法出来
public class DBUtil {
static {
try {
//1、加载驱动,因为驱动只需要加载一次,所以放在静态代码块中
//这样在类加载的时候驱动就会加载,且只加载一次
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接方法
private static Connection getConnection() {
try {
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void query(String sql,IRowMapper rowMapper) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//2、获取连接
connection = getConnection();
//3、创建Statement对象
statement = connection.createStatement();
//4、执行SQL语句
resultSet = statement.executeQuery(sql);
//多态,5、处理结果
rowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
close(resultSet,statement,connection);
}
}
public static boolean update(String sql) {
Connection connection = null;
Statement statement= null;
try {
//2、获取连接
connection = getConnection();
//3、创建语Statement对象
statement= connection.createStatement();
//4、执行SQL 5、处理结果
return statement.executeUpdate(sql)>0;
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
close(statement,connection);
}
return false;
}
//释放资源方法
private static void close(Statement statement,Connection connection) {
try {
if (statement!=null) {
statement.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
try {
if (connection !=null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//方法重载,因为在query方法和update方法中需要关闭的对象个数不同。
private static void close(ResultSet resultSet,Statement statement,Connection connection) {
try {
if (resultSet!=null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
close(statement,connection);
}
}
执行查询方法创建的接口
interface IRowMapper{
void rowMapper(ResultSet rs);
}
3.抽取的另一种方法
把Connection,Statement,以及ResultSet直接声明放在类中,并只写一个close方法
public class DBUtil {
static Connection connection = null;
static Statement statement = null;
static ResultSet resultSet = null;
//1.加载驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//2.建立连接,3.获取Statement对象
private static Statement getStatement() {
try {
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
return connection.createStatement();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void query(String sql, IRowMapper rowMapper) {
try {
statement = getStatement();
//4.执行SQL语句 5.处理结果
resultSet = statement.executeQuery(sql);
rowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
} finally {
close(resultSet, statement, connection);
}
}
public static boolean update(String sql) {
try {
statement = getStatement();
//4.执行SQL语句 5.处理结果
return statement.executeUpdate(sql) > 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
close(resultSet, statement, connection);
}
return false;
}
//6.释放资源的方法(注意关闭顺序)
private static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
//在关闭resultSet对象的时候需要把它置为null,
//否则在先执行query方法之后resultSet不为空,
//在每执行update方法的时候会重复关闭resultSet
resultSet=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
执行查询方法创建的接口
interface IRowMapper{
void rowMapper(ResultSet rs);
}
*这种方法需要注意
对于只使用一个close方法的情况,如果在先执行query方法之后resultSet将不为空,在以后每执行update方法的时候会重复关闭resultSet,所以需要在关闭resultSet之后把它置为null
4.SQL注入
SQL注入:利用现有应用程序,通过输入恶意的SQL语句得到一个存在安全漏洞的程序的数据库,而不是按照设计者意图去执行SQL语句。
对于刚才的工具类如果会存在一定的安全隐患这里我们简单模拟一个登陆系统,
输入数据库正确的账号和密码,如果登陆成功则进入欢迎界面
- 在数据库中先插入数据
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String userName = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
String sql = "select * from user_info where user_name = '"+userName+"' and password = '"+password+"'";
IRowMapper rowMapper = new IRowMapper() {
@Override
public void rowMapper(ResultSet resultSet) {
try {
while(resultSet.next()) {
String name = resultSet.getString("user_name");
System.out.println(name + "欢迎您");
return;
}
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("您输入的账号或者密码错误");
}
};
DBUtil.query(sql, rowMapper);
}
运行测试代码
//输入正确的用户名和密码
请输入用户名
admin
请输入密码
admin
admin欢迎您
//输入错误的密码
请输入用户名
admin
请输入密码
123
您输入的账号或者密码错误
SQL注入输入
请输入用户名
SQL注入。。。。' or 1=1;#
请输入密码
SQL注入。。。。。。
//随意输入的用户名和密码,就可以把数据库中的第一个用户查询出来,并登陆成功
tom欢迎您
这里即使输入数据库并不存在的账号和密码可以把数据库中所有的账号数据查询出来,它是利用现有应用程序,通过输入恶意的SQL语句得到一个存在安全漏洞的程序上的数据库。
5.完善DBUtil工具类(防止SQL注入 )
使用PreparedStatement代替Statement
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static Connection getConnection() {
try {
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void query(String sql, IRowMapper rowMapper) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
rowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
} finally {
close(resultSet, statement, connection);
}
}
public static boolean update(String sql) {
Connection connection = null;
Statement statement = null;
try {
connection = getConnection();
statement = connection.createStatement();
return statement.executeUpdate(sql) > 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
close(statement, connection);
}
return false;
}
private static void close(Statement statement, Connection connection) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
close(statement, connection);
}
//完善后的类新增了update方法和query方法的重载,
//由于修改和查询都有可能出现SQL注入所以把这两个方法都进行了重载了
//update方法的重载,由于传入的参数不确定,所以这里使用了动态参数
public static boolean update(String sql, Object... params) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
//传入参数是一个数组,所以这里遍历一个params数组得到所有的参数
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
return preparedStatement.executeUpdate() > 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
close(preparedStatement, connection);
}
return false;
}
//query方法的重载,由于传入的参数不确定,所以这里也使用了动态参数
public static void query(String sql, IRowMapper rowMapper, Object... params) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
resultSet = preparedStatement.executeQuery();
rowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
} finally {
close(resultSet, preparedStatement, connection);
}
}
}
执行查询方法创建的接口
interface IRowMapper{
void rowMapper(ResultSet rs);
}
测试方法
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String userName = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
// String sql = "select * from user_info where user_name = '"+userName+"' and password = '"+password+"'";
String sql = "select * from user_info where user_name = ? and password = ?";
IRowMapper rowMapper = new IRowMapper() {
@Override
public void rowMapper(ResultSet resultSet) {
try {
while(resultSet.next()) {
String name = resultSet.getString("user_name");
System.out.println(name + "欢迎您");
}
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println("您输入的账号或者密码错误");
}
};
DBUtil.query(sql, rowMapper,userName,password);
}
结果
//尝试使用SQL注入的方式登录
请输入用户名
SQL注入。。。。' or 1=1;#
请输入密码
SQL注入。。。。。。
您输入的账号或者密码错误
//输入正确的用户名和密码
请输入用户名
lucy
请输入密码
234
lucy欢迎您
使用Statement时如果使用拼接的SQL语句容易出现安全隐患,而PreparedStatement可以有效的解决SQL注入的安全隐患。查看PreparedStatement源代码可以看出它继承了Statement类,所以在调用DBUtil的close方法的时候可以直接传入PreparedStatement对象close(resultSet, preparedStatement, connection)不需要要再对方法进行重载。
PreparedStatement源码如下
public interface PreparedStatement extends Statement {
总结
在使用JDBC操作数据库的时候,可以把相应的代码抽取成一个工具类,增加代码的复用性。
使用JDBC操作数据库的时候,应该避免SQL语句的拼接防止SQL注入,最好使用PreparStatement对SQL语句进行预处理这样也可以防止SQL注入。