JDBC三:使用PreparedStatement实现CRUD

使用PreparedStatement实现CRUD

导读
  1. JDBC对数据库的操作有很多是固定格式,故可以将固定操作分离出来形成工具类,方便调用,提高开发效率。

  2. PreparedStatement下形成的CRUD通用程序

  3. 两种思想

    面向接口的编程思想

    ORM编程思想

  4. 两种技术

    元数据 + 反射

一、操作和访问数据库
  1. CRUD

    Create(增加)、Retrieve(查询)、Update(修改)、Delete(删除)

  2. 作用

    数据库连接被用于向数据库发送命令和SQL语句,并接收数据库服务器返回的结果。

  3. java.sql包中的3个接口

    Statement:用于执行静态SQL语句并返回它所生成结果的对象(已弃用)

    PreparedStatement

    CallableStatement

  4. JDBC编写步骤与执行流程

    在这里插入图片描述

二、Statement接口
  1. 作用

    Statement接口:它的作用就是向数据库中发送一条SQL语句,对数据库进行操作

    Statement对象:通过Connection接口的实例对象调用方法实现

  2. 问题一:存在拼串操作,繁琐
    String sql = "SELECT user,password FROM user_table WHERE user = '" + user + "'AND password = '" + password + "'";
    
  3. 问题二:存在SQL注入问题
    String sql = "SELECT user,password FROM user_table WHERE user = '1' or' AND password = '=' 1' or '1' = '1'";
    
    /* 
      上述的记录在表中没有,但是依然能够将表中数据全部查出,因为下面的拼接语句到了SQL中,会被解析为下图:
      '1' = '1'是恒等式,故WHERE条件相当于不存在,故会将表中记录全部查询出来,形成错误查询
    */ 
    

    在这里插入图片描述

  4. 被弃用

    由于Statement接口的各种问题,所以它的功能被它的子接口PreparedStatement接口替代

三、PreparedStatement接口操作 - - 增删改
  1. 作用

    和Statement接口作用一样,向数据库中发送SQL语句

    PreparedStatement对象:通过Connection接口的实例对象调用prepareStatement()方法得到

  2. 具体实现
    public class PreparedStatementTest {
        @Test
        public void testPreparedStatement1() {
            InputStream is = null;
            Connection conn = null;
            PreparedStatement pst = null;
            try {
                // 1.提供数据库连接的四个基本信息
                is = PreparedStatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
                Properties ps = new Properties();
                ps.load(is);
                String url = ps.getProperty("url");
                String user = ps.getProperty("user");
                String password = ps.getProperty("password");
                String driver = ps.getProperty("driver");
                // 2.注册驱动
                Class.forName(driver);
                // 3.创建连接
                conn = DriverManager.getConnection(url, user, password);
                // 4.预编译sql语言,并通过调用Connection对象的prepareStatement方法生成一个PrepareStatement对象
                String sql = "insert into customers(name, email, birth) values(?,?,?)";
                pst = conn.prepareStatement(sql);
                // 5.填充占位符,使用PreparedStatement对象操作
                pst.setString(1,"哪吒");
                pst.setString(2,"nezha@163.com");
                DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
                TemporalAccessor date_n = formatter.parse("1000-01-01");
                pst.setString(3,formatter.format(date_n));
                // 6.执行“增”操作
                pst.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 7.关闭资源
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (pst != null) {
                        pst.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    
    
  3. 优化程序,新建一个JDBC基本操作的工具类,将连接和关闭的固定操作分别放在两个方法中
    package jdbcutils;
    
    import java.io.InputStream;
    import java.sql.*;
    import java.util.Properties;
    
    /**
     * @author e_n
     * @version 1.0.0
     * @ClassName JdbcUtils.java
     * @Description JDBC连接和关闭工具类
     * @CreateTime 2022/01/21 11:14
     */
    public class JdbcUtils {
        /*
         * @title getConnection
         * @description 建立数据库的Connection连接
         * @author e_n
         * @param
         * @return java.sql.Connection
         * @throws
         * @updateTime 2022/1/21 11:27
         */
        public static Connection getConnection() throws Exception {
            // 1.提供数据库连接的四个基本信息
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties ps = new Properties();
            ps.load(is);
            String url = ps.getProperty("url");
            String user = ps.getProperty("user");
            String password = ps.getProperty("password");
            String driver = ps.getProperty("driver");
            // 2.注册驱动
            Class.forName(driver);
            // 3.创建连接
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        }
        /*
         * @title closeResource
         * @description 关闭连接资源和Statement的操作
         * @author e_n
         * @param conn,ps
         * @return void
         * @throws
         * @updateTime 2022/1/21 11:32
         */
        public static void closeResource(Connection conn, Statement ps) {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    
  4. 优化后的修改操作
        @Test
        public void testUpdate() {
            Connection conn = null;
            PreparedStatement ps = null;
            try {
                // 1.获取数据库的连接
                conn = JdbcUtils.getConnection();
                // 2.预编译sql语句,生成PreparedStatement对象
                String sql = "update customers set name = ? where id = ?";
                ps = conn.prepareStatement(sql);
                // 3.填充占位符,使用通用方法setObject()
                ps.setObject(1,"二郎神");
                ps.setObject(2,1);
                // 4.执行
                ps.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 5.关闭资源
                JdbcUtils.closeResource(conn,ps);
            }
        }
    
  5. 根据上述迭代,规范出一个使用于某数据库下不同表的 - - 通用增删改方法

    	// 增删改通用方法,提取了变化的sql语句作为参数1和将填充占位符的操作作为可变参数
    	@Test
        public void commonUpdate(String sql, Object ...args) {
            Connection conn = null;
            PreparedStatement ps = null;
            try {
                // 1.获取数据库的连接
                conn = JdbcUtils.getConnection();
                // 2.预编译sql语句,生成PreparedStatement对象
                ps = conn.prepareStatement(sql);
                // 3.利用循环及通用方法setObject(),填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i+1,args[i]);
                }
                // 4.执行
                ps.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 5.关闭资源
                JdbcUtils.closeResource(conn,ps);
            }
        }
    
    	// 测试通用方法
        @Test
        public void testCommonUpdate() {
            String sql = "update customers set name = ? where id = ?";
            commonUpdate(sql,"如来佛",1);
        }
    
四、PreparedStatement操作 - - 查询
  1. 查询操作的特点

    查询需要返回一个结果集,故执行时使用executeQuery( )方法,返回一个ResultSet对象,从而对结果集进行进一步操作。

  2. 处理结果集

    通过ResultSet类中的next( )方法可以实现迭代器作用,从而逐条处理结果集中的记录

  3. next( )方法的作用

    ① 判断下一条记录是否有数据

    ② 并将指针下移,指向下一条数据

  4. ORM编程思想(Object relational mapping)

    将查询后的结果集中的数据存储在类的对象中,通过对类的反射操作可实现通用性。

    一个数据表对应一个Java类

    表中的一条记录对应java类的一个对象

    表中的一个列对应Java类中的一个属性

  5. JavaBean

    存放用于映射查询后的结果集的类

    JavaBean 是一种用Java语言写成的可重用组件。为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。

    JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,set和get方法获取。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制(反射机制)发现和操作这些JavaBean 的属性。

  6. Java与SQL对应数据类型转换表
    Java类型SQL类型
    booleanBIT
    byteTINYINT
    shortSMALLINT
    intINTEGER
    longBIGINT
    StringCHAR、VARCHAR、LONGVARCHAR
    byte arrayBINARY、VARBINARY
    java.sql.DateDATE
    java.sql.TimeTIME
    java.sql.TimestampTIMESTAMP
  7. 创建一个查询的通用方法所需解决的问题

    从3到6,是实现查询并输出结果集的必备知识,形成通用方法则需要解决下面问题

    ① sql:查询语句的列的类型及数目不固定,故结果集中的列因不同的sql也不固定

    ② 如何将不固定的字段存储到JavaBean中对应类的对象的属性中

  8. 反射 + 元数据

    元数据:动态获取结果集中的列的信息(ResultSetMetaData),如

    ① 列的个数:getColumnCount( )

    ② 列的别名:getColumnLabel( )

    反射:动态操作结果集在JavaBean中对应类的属性,如获取属性名,给属性赋值

    参考博文:Java五十九: 注解 AnnotationJava六十九: 反射

  9. 针对某一具体的表的查询,且其查询结果集中只有一条记录的通用查询方法
    @Test
        /*
         * @title testCommonRetrieve
         * @description  具体表的单行查询操作的通用方法
         * @author e_n
         * @param
         * @return void
         * @throws
         * @updateTime 2022/1/21 22:21
         */
        public Customers testCommonRetrieve(String sql, Object...args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 1.建立连接
                conn = JdbcUtils.getConnection();
                // 2.预编译sql,生成PreparedStatement对象
                ps = conn.prepareStatement(sql);
                // 3.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i+1,args[i]);
                }
                // 4.执行并返回结果集
                rs = ps.executeQuery();
                // 5.处理结果集
                // 5.1 获取该结果集的元数据,通过元数据获取结果集中数据的信息
                ResultSetMetaData rsmd = rs.getMetaData();
                // 5.2 获取结果集中列的个数
                int columnCount = rsmd.getColumnCount();
                // 5.3 处理结果集中的一行数据
                if (rs.next()) {
                    // 5.4 处理该行记录的每一个字段
                    // 5.4.1 创建该行记录在JavaBean中对应类的对象
                    Customers cust = new Customers();
                    // 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历
                    for (int i = 0; i < columnCount; i++) {
                        // 5.4.3 通过结果集对象获取该字段的值
                        Object columnValue = rs.getObject(i + 1);
                        // 5.4.4 通过元数据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同
                        String columnLabel = rsmd.getColumnLabel(i+1);
                        // 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中
                        Field field = Customers.class.getDeclaredField(columnLabel);
                        // 确保私有属性可以访问
                        field.setAccessible(true);
                        // 给该字段对应的属性赋值
                        field.set(cust,columnValue);
                    }
                    return cust;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 6.关闭资源
                JdbcUtils.closeResource(conn,ps,rs);
            }
            return null;
        }
    
    	// 测试方法
        @Test
        public void test1() {
            // 将sql语句中的列名更换为与bean文件夹中对应类的属性名相同的别名,特别注意:where后面的条件查询仍然用列名,不能用别名
            String sql = "select cust_id id,cust_name name,cust_email ,birth from customers where cust_id = ?";
            Customers customers = testCommonRetrieve(sql, 4);
            System.out.println(customers);
        }
    

    特别注意:where后面的条件查询仍然用列名,不能用别名

  10. 针对不同表,且其查询结果集中只有一条记录的通用查询方法

    使用泛型解决不同表对应bean文件夹中不同类的问题

    @Test
        /*
         * @title testCommonRetrieve
         * @description  不同表的单行查询操作的通用方法,Class<T> cl
         * @author e_n
         * @param
         * @return void
         * @throws
         * @updateTime 2022/1/21 22:21
         */
        public <T> T testCommonRetrieve1(Class<T> clazz, String sql, Object...args)  {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 1.建立连接
                conn = JdbcUtils.getConnection();
                // 2.预编译sql,生成PreparedStatement对象
                ps = conn.prepareStatement(sql);
                // 3.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i+1,args[i]);
                }
                // 4.执行并返回结果集
                rs = ps.executeQuery();
                // 5.处理结果集
                // 5.1 获取该结果集的元数据,通过元数据获取结果集中数据的信息
                ResultSetMetaData rsmd = rs.getMetaData();
                // 5.2 获取结果集中列的个数
                int columnCount = rsmd.getColumnCount();
                // 5.3 处理结果集中的一行数据
                if (rs.next()) {
                    // 5.4 处理该行记录的每一个字段
                    // 5.4.1 创建该行记录在JavaBean中类的对象,此处用泛型实现
                    T t = clazz.newInstance();
                    // 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历
                    for (int i = 0; i < columnCount; i++) {
                        // 5.4.3 通过结果集对象获取该字段的值
                        Object columnValue = rs.getObject(i + 1);
                        // 5.4.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同
                        String columnLabel = rsmd.getColumnLabel(i+1);
                        // 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中
                        Field field = clazz.getDeclaredField(columnLabel);
                        // 确保私有属性可以访问
                        field.setAccessible(true);
                        // 给该字段对应的属性赋值
                        field.set(t,columnValue);
                    }
                    return t;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 6.关闭资源
                JdbcUtils.closeResource(conn,ps,rs);
            }
            return null;
        }
    	
    	// 测试方法
        @Test
        public void test2() {
            Customers customers = new Customers();
            String sql = "select cust_id id,cust_name name,cust_email email,cust_birth birth from customers where cust_id" +
                    " = ?";
            Customers customers1 = testCommonRetrieve1(Customers.class, sql, 4);
            System.out.println(customers1);
        }
    
  11. 不同表的通过查询方法

    使用ArrayList集合存储结果集中的多条查询记录对应存放的对象(5.3,5.4.6),返回值变为List< T >

    public <T> List<T> CommonRetrieve(Class<T> clazz, String sql, Object...args)  {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 1.建立连接
                conn = JdbcUtils.getConnection();
                // 2.预编译sql,生成PreparedStatement对象
                ps = conn.prepareStatement(sql);
                // 3.填充占位符
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i+1,args[i]);
                }
                // 4.执行并返回结果集
                rs = ps.executeQuery();
                // 5.处理结果集
                // 5.1 创建该结果集的元数据,通过元数据获取结果集中数据的信息
                ResultSetMetaData rsmd = rs.getMetaData();
                // 5.2 获取结果集中列的个数
                int columnCount = rsmd.getColumnCount();
                // 5.3 创建用于存储结果集中多条记录的集合
                ArrayList<T> tSet = new ArrayList<>();
                while (rs.next()) {
                    // 5.4 处理该行记录的每一个字段
                    // 5.4.1 创建该行记录在JavaBean中类的对象,此处用泛型实现
                    T t = clazz.newInstance();
                    // 5.4.2 利用通过元素据获得的结果集中列的个数,结合for循环对结果集中的列进行遍历
                    for (int i = 0; i < columnCount; i++) {
                        // 5.4.3 通过结果集对象获取该字段的值
                        Object columnValue = rs.getObject(i + 1);
                        // 5.4.4 通过元素据获取该字段的别(列)名,该列名与该行记录对应的对象中的属性名相同
                        String columnLabel = rsmd.getColumnLabel(i+1);
                        // 5.4.5 通过反射,给Customers对象中对应的属性赋值,实现将结果集中的数据存储到对象中
                        Field field = clazz.getDeclaredField(columnLabel);
                        // 确保私有属性可以访问
                        field.setAccessible(true);
                        // 给该字段对应的属性赋值
                        field.set(t,columnValue);
                    }
                    // 5.4.6 将此次循环产生的对象加入新建的集合中
                    tSet.add(t);
                }
                return tSet;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 6.关闭资源
                JdbcUtils.closeResource(conn,ps,rs);
            }
            return null;
        }
    
        @Test
        public void test3() {
            // sql语句查询出多条结果
            String sql = "select cust_id id,cust_name name,cust_email email,cust_birth birth from customers where cust_id" +
                    " > ?";
            List<Customers> list = CommonRetrieve(Customers.class, sql, 1);
            // 使用Lambda表达式表示
            list.forEach(System.out::println);
        }
    
五、查询操作使用的思想及技术
  1. PreparedStatement对查询进行操作:

    ① 难点在于对结果集的处理:

    用到ORM编程思想及新建一个bean文件夹内置一个与表名相同的类,通过类的对象对查询后的结果集数据进行处理

    ② 通用方法使用了元数据、反射、泛型、ArrayList集合等Java核心知识,其中:

    元数据和反射处理不确定列的通用问题,泛型处理不同表的通用问题,ArrayList处理结果集中有多条记录的问题。

  2. 面向接口的编程思想

    ① 只需要面向JDBC这套接口进行编程就可以了,在代码的import导入中不会出现任何第三方API

    ② 类的多态性:

    在连接数据库的入口方法中,加载了一个配置文件jdbc.properties,里面的driver=com.mysql.cj.jdbc.Driver就是第三方的API的位置,在该API的Driver类的静态代码块中,自动通过JDK中的sql包下的DriverManager对该第三方API中自身Driver类新建对象进行驱动注册。

    在这里插入图片描述

    通过调用自建工具类中获取连接MySQL的方法,返回一个JDK中的Conection对象。但是Connection接口中的方法是没有实现功能的,后面代码中通过该Connetion对象调用的一系列方法其实都是第三方API中对应的实现类中的方法,而通过这些方法返回的其它对象也是JDK中各接口的对象,从而无需导入任何第三方的API即可完成编程。

    在这里插入图片描述

六、PreparedStatement优势

通过利用占位符预编译sql语句

  1. 可以解决拼串操作和SQL注入问题

  2. 可以操作Blob数据

  3. 实现更高效的批量操作

    只需对预编译的sql语句检验一次,之后即使有上万次的添加也只需添加数据即可,无需再检验

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

e_nanxu

感恩每一份鼓励-相逢何必曾相识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值