JDBC学习笔记-下篇

本文介绍JDBC中公用功能的封装方法,包括获取数据库连接和关闭资源等操作。探讨了查询操作的具体实现,并讨论了SQL注入问题及其解决方案。通过使用PreparedStatement替代Statement,提高了代码的可读性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

这一篇主要是基于上一篇的基础应用的一个扩展内容的学习。


一、对于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的基础学习就到这里啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值