二.使用PreparedStatement实现CRUD操作

194 阅读4分钟

一.概述

在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

  • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
  • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
  • CallableStatement:用于执行 SQL 存储过程 image.png

二. Statement操作数据表的弊端:

  • 问题一:存在拼串操作,繁琐
  • 问题二:存在SQL注入问题 SQL

注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段 或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法。

对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了,原因是 PreparedStatement使用占位符'?'来来写SQL语句所以会先预编译SQL文件,导致运行的时候不会产生错误运行过程。

三. 增删改操作

  1. 连接数据库
  2. 预编译SQL语句并填充占位符
  3. 执行
  4. 关闭资源
  • 获取test数据库连接的通用方法getConnection
//获取test数据库连接的通用方法
public static Connection getConnection() throws Exception {
    //1.读取配置文件
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
    Properties pros = new Properties();
    pros.load(is);
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String driverClass=pros.getProperty("driverClass");
    String url = pros.getProperty("url");
    //2.加载驱动
    Class.forName(driverClass);
    //3.获取连接
    Connection con = DriverManager.getConnection(url,user,password);
    return con;
}
  • 关闭数据库连接的通用方法closeConnection
//关闭数据库连接的通用方法1
public static void closeConnection(Connection con,Statement ps){
    try {
        if(ps != null)
            ps.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    try {
        if(con != null)
            con.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
  • 增删改操作的通用方法update
//增删改操作的通用方法
public static void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        //1.获取数据库的连接
        conn = getConnection();
        //2.预编译sql语句,返回PreparedStatement的实例
        ps = conn.prepareStatement(sql);
        //3.填充占位符
        for(int i = 0;i < args.length;i++){
            ps.setObject(i + 1, args[i]);//小心参数声明错误!!
        }
        //4.执行
        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //5.资源的关闭
       closeConnection(conn, ps);
    }

}

注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。

四.查询操作

Java与SQL的类型转换 image.png

4.1 ResultSet与ResultSetMetaData

4.1.1 ResultSet

ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。

  • next()方法:一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象 的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。 相当于Iterator对象的 hasNext() 和 next() 方法的结合体。

image.png

4.1.2 ResultSetMetaData

可用于获取关于 ResultSet 对象中列的类型和属性信息的对象(获取元数据)

  • 常用方法
    • getColumnName(int column):获取指定列的名称
    • getColumnLabel(int column):获取指定列的别名 }
    • getColumnCount():返回当前 ResultSet 对象中的列数。
    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
    • isNullable(int column):指示指定列中的值是否可以为 null。
    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

注意:若bean中声明的属性名与表中字段名不相同,则需要在SQL语句中给字段起别名为其对应的属性名,此时只能调用getColumnLabel(int column),若相同也可以使用该方法不会报错,所以建议在开发时直接使用getColumnLabel(int column)即可;

  • 实际问题
    • 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
    • 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
    • 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法

image.png

4.2 单行查询操作

  1. 连接数据库
  2. 预编译SQL文件并设置占位符
  3. 执行SQL并获取结果集
  4. 通过结果集获取表的元数据并获取列数
  5. 获取数据表对应的类的运行时对象
  6. 通过结果集获取各个字段的值
  7. 通过元数据获取各个字段的名称(别名)
  8. 获运行时类的属性
  9. 给属性赋上表中对应字段的值
  10. 关闭资源
//Select语句查找单条记录的通用方法
public static <T> T getInstanceQuery(Class<T> clazz,String sql,Object ...args)  {
    Connection con =null;
    PreparedStatement ps =null;
    ResultSet rs =null;
    try {
        //1.连接数据库
        con = getConnection();
        //2.预编译sql语句
        ps = con.prepareStatement(sql);
        if(ps!=null){
            for (int i=0;i<args.length;i++){
                //设置通配符值(填充占位符)
                ps.setObject(i+1,args[i]);
            }
        }
        //3.执行sql并获取sql语句返回的结果集
        rs =ps.executeQuery();
        //4.获取结果集的元数据并获取列数
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();//列数

        if (rs.next()){//指针下移,若数据为空则返回false,反之true
            //5.获取数据表对应的类的运行时对象
             T t = clazz.newInstance();
             for (int i=0;i<columnCount;i++){
                 //6.获取各个字段的值
                 Object columnVlaue = rs.getObject(i+1);
                //7.获取各个字段的名称
                 String columnName=rsmd.getColumnLabel(i+1);

                 //8.获运行时类的属性
                 Field field = clazz.getDeclaredField(columnName);
                 field.setAccessible(true);//打开权限
                 //9.给属性赋上表中对应字段的值
                 field.set(t,columnVlaue);
             }
            return t;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        closeConnection(con,ps,rs);
    }
    return null;

}

4.3 多行查询操作

只需要把单行查询时的 if (rs.next())改为while(rs.next()),在返回一个list即可

//Select语句查找多条记录的通用方法
public static <T> List<T> getInstanceQuerys(Class<T> clazz, String sql, Object ...args){
    ArrayList<T> list = null;
    Connection con =null;
    ResultSet rs =null;
    PreparedStatement ps =null;
    try {
         con =getConnection();
        ps = con.prepareStatement(sql);

        if(ps!=null){
            for (int i=0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }
        }

        rs = ps.executeQuery();

        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();

        list = new ArrayList<T>();
        while (rs.next()){//修改的地方
            T t = clazz.newInstance();
            for (int i=0 ;i<columnCount;i++){
                String columnName = rsmd.getColumnLabel(i+1);
                Object columnValue = rs.getObject(i+1);
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(t,columnValue);
            }
            list.add(t);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        closeConnection(con,ps,rs);
    }
    return null;
}