[Java]JDBC学习笔记(尚硅谷康师傅JDBC) (二)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: [Java]JDBC学习笔记(尚硅谷康师傅JDBC)(二)

🌊 PreparedStatement 实现数据的修改

// 修改customers表中的一条数据
    @Test
    public void testUpdate() {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            // 获取数据库的连接
            connection = JDBCUtils.getConnection();
            // 预编译sql语句,返回PreparedStatement实例对象
            String sql = "update customers set name=? where id=?";
            ps = connection.prepareStatement(sql);
            // 填充占位符
            ps.setObject(1, "莫扎特");
            ps.setObject(2, 18);
            // 执行sql
            Boolean res = ps.execute();
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            // 资源关闭
            JDBCUtils.closeResource(connection, ps);
        }
    }

🌊 PreparedStatement 实现通用的增删改操作

// 通用的增删改操作
    public void update(String sql, Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            // 获取数据库的连接
            connection = JDBCUtils.getConnection();
            // 预编译sql,获取PreparedStatement实例对象
            ps = connection.prepareStatement(sql);
            // 填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1, args[i]); // 由于不知道填充占位的数据是什么类型所以使用setObject
            }
            // 执行sql
            ps.execute();
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(connection, ps);
        }
    }
@Test
    public void test() {
        String sql = "delete from customers where id=?";
        update(sql, 3);
    }

🌊 PreparedStatement 实现数据的查询

JDBCUtils中新增关闭资源的方法:

/**
     * 关闭资源操作
     * @param connection
     * @param ps
     * @param rs
     */
    public static void closeResource(Connection connection, Statement ps, ResultSet rs) {
        // 如果存在Statement或Statement的子类对象,进行关闭操作
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        // 进行数据库的关闭
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        // 进行结果集的关闭
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
@Test
    public void testQuery1() {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            // 获取数据库的连接
            connection = JDBCUtils.getConnection();
            // 预编译sql,获取PreparedStatement实例对象
            String sql = "select id, name, email from customers where id=?";
            ps = connection.prepareStatement(sql);
            // 填充占位符
            ps.setObject(1, 1);
            // 执行sql获取结果集
            resultSet = ps.executeQuery();
            // 处理结果集
            while (resultSet.next()) { // 判断结果集的下一条是否有数据,有数据返回true并指针下移,如果返回false指针不会下移
                // 获取当前这条数据的各个字段值
                int id = resultSet.getInt(1); // 获取第一个字段的值(从1开始)
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                System.out.println(id + ": " + name + " " + email);
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(connection, ps, resultSet);
        }
    }

🌊 实现通用的查询操作

💦 ORM编程思想

  • 一个数据表对应一个Java类;
  • 表中的一条记录对应Java类的一个对象;
  • 表中的一个字段对应Java类的一个属性。

💦 Java与SQL对应数据类型

💦 针对customers表的通用查询操作

customers表对应的Java类

package preparedstatement;
import java.sql.Date;
public class Customer {
    private int id;
    private String name;
    private String email;
    private Date birth;
    @Override
    public String toString() {
        return "Customer{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", birth=" + birth + '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public Date getBirth() {
        return birth;
    }
    public void setBirth(Date birth) {
        this.birth = birth;
    }
    public Customer() {
    }
    public Customer(int id, String name, String email, Date birth) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = birth;
    }
}

针对customers表的通用查询操作的方法

public ArrayList<Customer> queryForCustomers(String sql, Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<Customer> customers = new ArrayList<>(); // 返回的数据对象
        try {
            // 获取数据库连接
            connection = JDBCUtils.getConnection();
            // 预编译sql获取PreparedStatement实例对象
            ps = connection.prepareStatement(sql);
            // 填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            // 执行sql获取结果集
            rs = ps.executeQuery();
            // 获取结果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            // 获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            // 处理结果集
            while (rs.next()) { // 如果还有下一条数据
                // 创建表行数据对应的对象
                Customer customer = new Customer();
                // 获取当前这条数据的每个字段
                for (int i = 0; i < columnCount; i++) {
                    Object columnValue = rs.getObject(i + 1);
                    // 获取当前列的列名
                    String columnName = rsmd.getColumnName(i + 1);
                    // 给Customer对象指定的columnName属性赋值 通过反射
                    Field field = Customer.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(customer, columnValue); // 把customer对象的属性赋值为columnValue
                }
                customers.add(customer);
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return customers;
    }
@Test
    public void testQueryForCustomers() {
        String sql = "select id, name, email from customers where id=?";
        ArrayList<Customer> customers = queryForCustomers(sql, 13);
        for (int i = 0; i < customers.size(); i++) {
            System.out.println(customers.get(i));
        }
    }

💦 针对order表的通用查询操作

order表对应的Java类

package preparedstatement;
import java.sql.Date;
public class Order {
    private int orderId;
    private String orderName;
    private Date orderDate;
    public int getOrderId() {
        return orderId;
    }
    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }
    public String getOrderName() {
        return orderName;
    }
    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }
    public Date getOrderDate() {
        return orderDate;
    }
    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }
    public Order() {
    }
  @Override
    public String toString() {
        return "Order{" + "orderId=" + orderId + ", orderName='" + orderName + '\'' + ", orderDate=" + orderDate + '}';
    }
}

针对order表的通用查询操作的方法

// 针对与表的字段名和类的属性名不一致的情况,
  // 声明sql时使用类的属性名作为字段的别名,
  // 使用getColumnLabel获取列的别名
  // 如果没有取别名getColumnLabel获取的是列名
    public ArrayList<Order> queryForOrder(String sql, Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<Order> orders = new ArrayList<>();
        try {
            connection = JDBCUtils.getConnection();
            ps = connection.prepareStatement(sql);
            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();
            while (rs.next()) {
                Order order = new Order();
                for (int i = 0; i < columnCount; i++) {
                    // 获取每个列的列值
                    Object columnValue = rs.getObject(i + 1);
                    // 获取列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    // 获取列的别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 通过反射将对象指定列名columnName的属性赋值为columnValue
                    Field field = Order.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(order, columnValue); // 赋值
                }
                orders.add(order);
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return orders;
    }
@Test
    public void testQueryForOrder() {
        String sql = "select order_id orderId, order_name orderName, order_date orderDate from `order` where order_id=?";
        ArrayList<Order> orders = queryForOrder(sql, 1);
        for (int i = 0; i < orders.size(); i++) {
            System.out.println(orders.get(i));
        }
    }

💦 查询过程图解

💦 针对不同表的通用查询操作

/**
     * 泛型方法
     * @param tClass 指定返回的数据对应的Class
     * @param sql
     * @param args 填充sql语句的参数
     * @return 返回查询结果
     * @param <T> 指定返回数据对应的类
     */
    public <T> ArrayList<T> query(Class<T> tClass, String sql, Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<T> ts = new ArrayList<>(); // 用于存储查询结果数据
        try {
            connection = JDBCUtils.getConnection(); // 获取连接对象
            ps = connection.prepareStatement(sql); // 预编译sql获取prepareStatement对象
            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();
            while (rs.next()) {
                T t = tClass.newInstance(); // 实例化对应的对象
                for (int i = 0; i < columnCount; i++) {
                    // 获取每个列的列值
                    Object columnValue = rs.getObject(i + 1);
                    // 获取列的别名,没有别名获取列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 通过反射将对象指定列名columnName的属性赋值为columnValue
                    Field field = tClass.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue); // 赋值
                }
                ts.add(t);
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return ts;
    }
    @Test
    public void test() {
        String sql = "select id, name, email from customers where id < ?";
        ArrayList<Customer> customers = query(Customer.class, sql, 3);
        // System.out.println(customers);
        // for (Customer customer: customers) {
        //     System.out.println(customer);
        // }
        customers.forEach(System.out::println);
    }

🌊 PreparedStatement 解决SQL注入问题

@Test
    public void testLogin() {
        // SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
        // String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";
        String sql = "SELECT user,password FROM user_table WHERE user = ? AND password = ?";
        // ArrayList<User> users = query(User.class, sql, "AA", "123456"); // 登录成功
        ArrayList<User> users = query(User.class, sql, "1' or ", "=1 or '1' = '1"); // 用户名不存在或密码错误
        if(users.size() > 0){
            System.out.println("登录成功");
        }else{
            System.out.println("用户名不存在或密码错误");
        }
    }
    /**
     * 泛型方法
     * @param tClass 指定返回的数据对应的Class
     * @param sql
     * @param args 填充sql语句的参数
     * @return 返回查询结果
     * @param <T> 指定返回数据对应的类
     */
    public <T> ArrayList<T> query(Class<T> tClass, String sql, Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<T> ts = new ArrayList<>(); // 用于存储查询结果数据
        try {
            connection = JDBCUtils.getConnection(); // 获取连接对象
            ps = connection.prepareStatement(sql); // 预编译sql获取prepareStatement对象
            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();
            while (rs.next()) {
                T t = tClass.newInstance(); // 实例化对应的对象
                for (int i = 0; i < columnCount; i++) {
                    // 获取每个列的列值
                    Object columnValue = rs.getObject(i + 1);
                    // 获取列的别名,没有别名获取列名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 通过反射将对象指定列名columnName的属性赋值为columnValue
                    Field field = tClass.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue); // 赋值
                }
                ts.add(t);
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return ts;
    }

由于PreparedStatement会先对sql语句进行预编译,上述登录的sql语句的条件子句为且关系,PreparedStatement对其预编译之后会认定其就为且关系,不会因为填充的数据而改变其逻辑关系,所以不会引发SQL注入问题。对于Statement,会根据拼接字符串的不同而发生条件子句逻辑关系的改变,所以会导致SQL注入。

  • PreparedStatement能够解决Statement的问题:
  1. Statement的拼串
  2. Statement的SQL注入问题
  3. PreparedStatement可以操作BLob类型的数据,而Statement不行
  4. PreparedStatement可以实现更高效的批量操作,Statement每次执行sql都需要进行sql校验,而PreparedStatement在预编译时进行sql校验,后续多次执行sql可以直接填充数据执行,不用再次进行校验。

🥽 PreparedStatement操作Blob类型的数据

🌊 MySQL BLOB类型

MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的):

实际使用中根据需要存入的数据大小定义不同的BLOB类型。需要注意的是:如果存储的文件过大,数据库的性能会下降,所以实际情况中,一般将文件存储在专门的文件服务器,在数据库中存储的为文件对应的地址。

🌊 插入Blob类型的数据

@Test
    public void testInster() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into customers(name, email, birth, photo) values (?, ?, ?, ?)";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setObject(1, "ZS");
        ps.setObject(2, "ZS@qq.com");
        ps.setObject(3, "2001-11-11");
        // 使用文件输入流获取文件
        FileInputStream fis = new FileInputStream(new File("src\\preparedstatement\\1.jpg"));
        ps.setObject(4, fis);
        ps.execute();
        JDBCUtils.closeResource(connection, ps);
    }

如果在数据库表中指定了字段相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数:max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

Mysql8.0的my.ini文件在如下路径:C:\ProgramData\MySQL\MySQL Server 8.0

🌊 读取Blob类型的数据

@Test
    public void testQuery() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql = "select id, name, email, birth, photo from customers where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setObject(1, 21);
        // 执行获取结果集
        ResultSet rs = ps.executeQuery();
        InputStream is = null;
        FileOutputStream fos = null;
        if (rs.next()) {
            // 方式1:
            //int id = rs.getInt(1);
            //String name = rs.getString(2);
            //String email = rs.getString(3);
            //Date birth = rs.getDate(3);
            // 方式2(建议):
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String email = rs.getString("email");
            Date birth = rs.getDate("birth");
            System.out.println(id);
            System.out.println(name);
            System.out.println(email);
            System.out.println(birth);
            // 将blob类型的数据保存本地
            Blob photo = rs.getBlob("photo");
            is = photo.getBinaryStream(); // 读取blob数据到内存
            fos = new FileOutputStream("ZS.jpg");// 输出流
            // 写入文件
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        }
        fos.close();
        is.close();
        JDBCUtils.closeResource(connection, ps, rs);
    }

🥽 PreparedStatement批量插入数据

goods的建表语句:

CREATE TABLE goods(
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(25)
);

向goods表中插入20000条数据

🌊 使用PreparedStatement

@Test
    public void testInsert1() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into goods(name) values(?);";
        PreparedStatement ps = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for (int i=0; i<20000; i++) {
            ps.setObject(1, "name_" + i);
            ps.execute();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        JDBCUtils.closeResource(connection, ps);
    }

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
3月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
220 5
|
12天前
|
小程序 Java 知识图谱
Java 学习笔记 —— BMI & BMR 计算器
这是一个使用 Java 编写的 BMI 与 BMR 计算器小程序,可输入年龄、性别、身高和体重,计算身体质量指数(BMI)和基础代谢率(BMR),并输出健康评估结果。通过该项目,掌握了 Java 的输入处理、数据验证、条件判断、数学运算及格式化输出等基础知识,是 Java 初学者的理想练习项目。
|
12天前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
6月前
|
存储 Java
# 【Java全栈学习笔记-U1-day02】变量+数据类型+运算符
本篇笔记主要围绕Java全栈学习的第二天内容展开,涵盖了变量、数据类型、运算符以及Scanner类的应用。首先介绍了变量的概念与命名规范,以及如何定义和使用变量;接着详细讲解了Java中的基本数据类型,包括整型、浮点型、字符型、布尔型等,并通过实例演示了数据类型的运用。随后,深入探讨了各类运算符(赋值、算术、关系、逻辑)及其优先级,帮助理解表达式的构成。最后,介绍了如何利用Scanner类实现用户输入功能,并通过多个综合示例(如计算圆面积、购物打折、变量交换及银行利息计算)巩固所学知识。完成相关作业将进一步加深对这些基础概念的理解与实践能力。
98 13
|
6月前
|
SQL Java 中间件
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
在BeetISQL 2.13.8版本中,客户使用batch insert向yashandb表插入数据并尝试获取自动生成的sequence id时,出现类型转换异常。原因是beetlsql在prepareStatement时未指定返回列,导致yashan JDBC驱动返回rowid(字符串),与Java Bean中的数字类型tid不匹配。此问题影响业务流程,使无法正确获取sequence id。解决方法包括:1) 在batchInsert时不返回自动生成的sequence id;2) 升级至BeetISQL 3,其已修正该问题。
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
|
6月前
|
SQL druid Oracle
【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常
客户Java日志中出现异常,影响Druid的merge SQL功能(将SQL字面量替换为绑定变量以统计性能),但不影响正常业务流程。原因是Druid在merge SQL时传入null作为dbType,导致无法解析递归查询中的`start`关键字。
|
6月前
|
开发框架 Java 开发工具
【Java全栈学习笔记-U1-day01】Java介绍
本笔记整理了Java学习的基础内容,涵盖程序理解、Java语言特性、JDK安装与配置、Java程序开发工具及编写步骤。重点介绍了Java程序的基本结构、编译和运行过程,以及输出语句的使用。通过实例演示了IDEA创建Java程序的方法,并强调了编码规范和注意事项。适合初学者复习和交流学习。 主要内容: 1. 理解程序:计算机组成、程序定义。 2. 简介:Java语言特点、技术平台、JDK作用。 3. 编写Java程序:编写、编译、运行步骤,基本结构。 4. 输出语句 5. DEA使用:新建工程、保存位置、文件介绍、新建类。 6. 扩展:注释、代码规范、大小写敏感、缩进等。
|
11月前
|
Java 关系型数据库 MySQL
mysql5.7 jdbc驱动
遵循上述步骤,即可在Java项目中高效地集成MySQL 5.7 JDBC驱动,实现数据库的访问与管理。
2168 1
|
SQL druid Java
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
147 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
|
SQL Java 关系型数据库
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
540 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)

热门文章

最新文章