前言
这一篇主要是基于上一篇的基础应用的一个扩展内容的学习。
一、对于JDBC公用功能的封装
说明:增删改查的操作中,获取连接和关闭资源都是重复的,基于封装的思想,可以将这些操作封装在一个静态方法中,需要的时候再去调用方法。
//创建DBUtils类,并创建获取连接和关闭资源的方法
public class DBUtils {
private static String driverClass;
private static String url;
private static String user;
private static String password;
static {
//用ResourceBundle来读取properties文件中的配置信息;使用静态代码块,给类进行初始化
ResourceBundle rsb = ResourceBundle.getBundle("dbinfo"); //dbinfo是配置文件的名字
driverClass=rsb.getString("driverClass");
url=rsb.getString("url");
user=rsb.getString("user");
password=rsb.getString("password");
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接,并把Connection对象返回
public static Connection getConnection() throws Exception {
return DriverManager.getConnection(url, user, password);
}
//关闭资源
public static void closeAll(ResultSet res, Statement stmt, Connection conn){
if(res!=null){
try{
res.close();
}catch (Exception e){
e.printStackTrace();
}
res=null;
}
if(stmt!=null){
try{
stmt.close();
}catch (Exception e){
e.printStackTrace();
}
stmt=null;
}
if(conn!=null){
try{
conn.close();
}catch (Exception e){
e.printStackTrace();
}
conn=null;
}
}
}
二、查询操作的方法
说明:在用户表中查询一个用户,并返回该用户的User对象(User对象的创建与使用在上一篇有说明)
//根据传入的用户名和密码查询出用户是否存在(模拟用户登录功能)
public User findUser(String name, String pwd){
Connection conn=null;
Statement stmt = null;
ResultSet resultSet = null;
User u=null;
try {
conn = DBUtils.getConnection();
String sql = "select * from users where name='"+name+"' and accountid='"+pwd+"'";
stmt = conn.createStatement();
resultSet = stmt.executeQuery(sql);
if(resultSet.next()){
u = new User();
u.setId(resultSet.getInt("id"));
u.setName(resultSet.getString("name"));
u.setAge(resultSet.getShort("age"));
u.setStatus(resultSet.getInt("status"));
u.setScore(resultSet.getShort("score"));
u.setAccountid(resultSet.getString("accountid"));
return u;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.closeAll(resultSet,stmt,conn);
}
return u; //如果查询的user不存在就返回null
}
三、SQL注入问题及解决方法
根据上面查询的sql语句,设想出一种情况,当用户传入pwd的参数时,输入的参数如下:
aaa’ or ‘1’='1
那么整个的sql语句就变成:select * from users where name=‘xxx’ and ‘aaa’ or ‘1’=‘1’;
此时这个查询条件会永远成功,那么无论输入的用户名和密码是否是数据库存在的数据,最后都能查询成功,从而登录成功
解决办法:把Statement替换成PreparedStatement对象来使用,PreparedStatement接收一个预编译的sql语句。代码如下:
public User findUser(String name, String pwd){
Connection conn=null;
PreparedStatement stmt = null; //用PreparedStatement替换Statement
ResultSet resultSet = null;
User u=null;
try {
conn = DBUtils.getConnection();
String sql = "select * from users where name=? and accountid=?"; //需要传入值的地方用?作为占位符
stmt = conn.prepareStatement(sql); //传入sql语句
stmt.setString(1,name); //给sql语句中的?替换对应的值
stmt.setString(2,pwd);
resultSet = stmt.executeQuery(); //执行语句,此时就不用再传入sql语句了
if(resultSet.next()){
u = new User();
u.setId(resultSet.getInt("id"));
u.setName(resultSet.getString("name"));
u.setAge(resultSet.getShort("age"));
u.setStatus(resultSet.getInt("status"));
u.setScore(resultSet.getShort("score"));
u.setAccountid(resultSet.getString("accountid"));
return u;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.closeAll(resultSet,stmt,conn);
}
return u;
}
四、PreparedStatement的好处
1.代码可读性更高
这里对比一下sql语句的两种写法:
//这里需要把参数都替换成形参,可读性比较差
String sql = "select * from users where name='"+name+"' and accountid='"+pwd+"'";
//把参数都用?来作为占位符,清晰明了
String sql = "select * from users where name=? and accountid=?";
2.性能可以得到提高
因为PreparedStatement接收的是一个预编译的sql语句,而预编译的语句可能会重复调用,所以语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行。这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配。那么在任何时候就可以不需要再次编译而可以直接执行。
而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配。
3.安全性
则是上一节上所说的sql注入的问题,这里不再重复赘述。
总结
这一篇主要是介绍了如何封装JDBC中重复使用的那些功能,SQL注入的问题以及解决方式,同时介绍了PreparedStatement这个对象以及它的有点,并且,在日后JDBC的使用中,建议用这个对象永久的替换Statement对象。JDBC的基础学习就到这里啦~