栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JDBC三:使用PreparedStatement实现CRUD

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JDBC三:使用PreparedStatement实现CRUD

使用PreparedStatement实现CRUD 导读

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

    PreparedStatement下形成的CRUD通用程序

    两种思想

    面向接口的编程思想

    ORM编程思想

    两种技术

    元数据 + 反射

一、操作和访问数据库
    CRUD

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

    作用

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

    java.sql包中的3个接口

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

    PreparedStatement:

    CallableStatement:

    JDBC编写步骤与执行流程

二、Statement接口
    作用

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

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

    问题一:存在拼串操作,繁琐
    String sql = "SELECT user,password FROM user_table WHERe user = '" + user + "'AND password = '" + password + "'";
    
    问题二:存在SQL注入问题
    String sql = "SELECt user,password FROM user_table WHERe user = '1' or' AND password = '=' 1' or '1' = '1'";
    
     
    

    被弃用

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

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

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

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

    具体实现
    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();
                }
            }
        }
    }
    
    
    优化程序,新建一个JDBC基本操作的工具类,将连接和关闭的固定操作分别放在两个方法中
    package jdbcutils;
    
    import java.io.InputStream;
    import java.sql.*;
    import java.util.Properties;
    
    
    public class JdbcUtils {
        
        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;
        }
        
        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();
            }
        }
    }
    
    优化后的修改操作
        @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);
            }
        }
    

    根据上述迭代,规范出一个使用于某数据库下不同表的 - - 通用增删改方法

    	// 增删改通用方法,提取了变化的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操作 - - 查询
    查询操作的特点

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

    处理结果集

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

    next( )方法的作用

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

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

    ORM编程思想(Object relational mapping)

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

    一个数据表对应一个Java类

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

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

    JavaBean

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

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

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

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

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

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

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

    反射 + 元数据

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

    ① 列的个数:getColumnCount( )

    ② 列的别名:getColumnLabel( )

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

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

    针对某一具体的表的查询,且其查询结果集中只有一条记录的通用查询方法
    @Test
        
        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后面的条件查询仍然用列名,不能用别名

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

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

    @Test
        
        public  T testCommonRetrieve1(Class 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);
        }
    
    不同表的通过查询方法

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

    public  List CommonRetrieve(Class 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 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 list = CommonRetrieve(Customers.class, sql, 1);
            // 使用Lambda表达式表示
            list.forEach(System.out::println);
        }
    
五、查询操作使用的思想及技术
    PreparedStatement对查询进行操作:

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

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

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

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

    面向接口的编程思想

    ① 只需要面向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语句

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

    可以操作Blob数据

    实现更高效的批量操作

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/716149.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号