JDBC学习总结(一)获取数据库连接ConnectionSQL注入PreparedStatement实现增删改查ResultSetResultSetMetaDataORM思想

JDBC学习总结(一)获取数据库连接/Connection/SQL注入/PreparedStatement实现增删改查/ResultSet/ResultSetMetaData/ORM思想

一、JDBC概述

(一)数据的持久化

  • 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

在这里插入图片描述

(二)Java中的数据存储技术

  • 在Java中,数据库存取技术可分为如下几类:
    • JDBC直接访问数据库
    • JDO (Java Data Object )技术
    • 第三方O/R工具,如Hibernate, Mybatis 等
  • JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好地封装了JDBC。

(三)JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql、javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程

如果没有JDBC,那么Java程序访问数据库时是这样的:
在这里插入图片描述

那么,对于不同的数据库就要有不同的操作实现,太麻烦,可移植性低。


有了JDBC,Java程序访问数据库时是这样的:
在这里插入图片描述


而真实的连接其实是这样:

在这里插入图片描述
综述:SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范(接口), 称之为JDBC。JDBC是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。这套接口由数据库厂商去实现,实现了以后也就组成了上面图中各个数据库厂商提供的JDBC驱动,这样,我们Java开发人员只需要学习JDBC接口,并通过JDBC加载具体的驱动,就可以操作对应的数据库。

(四)JDBC驱动

这里的驱动的概念和平时听到的那种驱动的概念是类似的,比如平时购买的声卡,网卡直接插到计算机上面是不能用的,必须要安装相应的驱动程序之后才能够使用声卡和网卡。同样道理,我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道。JDBC驱动其实就是各个数据库厂商对于JDBC接口的一套实现。 不同的数据库厂商提供的JDBC驱动不一样。

(五)JDBC体系结构

  • JDBC接口(API)包括两个层次:
    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。

不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程

二、JDBC程序编写

(一)步骤

在这里插入图片描述

补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。

(二)获取数据库连接

这一步就对应上图步骤的:加载并注册驱动程序创建Connection对象

而需要获取数据库连接,我们又需要四个要素:Driver接口实现类URL、用于连接数据库的用户名和密码

1.要素一:Driver接口实现类

(1)Driver接口介绍
  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
  • 在程序中可以直接去访问实现了 Driver 接口的类(不推荐),也可以用驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现(更推荐)。
  • Driver接口中有一个抽象方法用于创建连接:Connection connect(String url, Properties info) 尝试使数据库连接到给定的URL。
(2)下载驱动

首先,我们建好Java项目以后,必须下载JDBC驱动

  • Oracle的驱动:oracle.jdbc.driver.OracleDriver
  • MySQL的驱动: com.mysql.jdbc.Driver
    MySQL驱动下载地址,本博客用的是5.1.7版本

将上述下载好的jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。
在这里插入图片描述
右键这个lib文件夹,点击Add as Library。
在这里插入图片描述
这样,我们就可以在代码中使用java.sql.Driver接口的实现类:com.mysql.jdbc.Driver 了,(com.mysql.jdbc.Driver就在我们导入的jar包里)

2.要素二:URL

  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。

  • JDBC URL的标准由三部分组成,各部分间用冒号分隔。
    格式协议:子协议:子名称

    • 协议:JDBC URL中的协议总是jdbc
    • 子协议:子协议用于标识一个数据库驱动程序,如mysql
    • 子名称:一种标识数据库的方法。不同的子协议,子名称可能不同。用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
  • 举例
    在这里插入图片描述
    localhost——主机名
    3306——端口号(MySQL默认的端口号是3306)
    test——要连接的数据库名

  • 几种常用数据库的 JDBC URL

    • MySQL的连接URL编写方式:

      • 格式jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值

      • 举例

        • jdbc:mysql://localhost:3306/test
        • jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8 (如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
        • jdbc:mysql://localhost:3306/test?user=root&password=123456
    • Oracle 9i的连接URL编写方式:

      • 格式jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
      • 举例jdbc:oracle:thin:@localhost:1521:test
    • SQLServer的连接URL编写方式:

      • 格式jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
      • 举例jdbc:sqlserver://localhost:1433:DatabaseName=test

3. 要素三、四:用于连接数据库的用户名和密码

  • userpassword可以用“属性名=属性值”方式告诉数据库
  • 可以调用 DriverManager 类的 getConnection方法传入用户名和密码建立到数据库的连接

4.数据库的五种连接方式

五种方法逐级递进

(1)方式一
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;

public class TestConnection1 {
    public static void main(String[] args) throws SQLException {
        //1、获取Driver接口的实现类对象(来自于导入的JDBC驱动)
        Driver driver=new com.mysql.jdbc.Driver();
        
        //2、提供要连接的数据库的URL
        String url = "jdbc:mysql://localhost:3306/test";
        
        //3、将用户名和密码封装在Properties属性集合中
        Properties info = new Properties();
        info.setProperty("user", "root");//用户名为root
        info.setProperty("password", "123456");//密码为123456
        
        //4、获取连接
        //Connection connect(String url, Properties info) 尝试使数据库连接到给定的URL。  
        Connection conn = driver.connect(url, info);
        
        //检验连接是否成功
        System.out.println(conn);
    }
}

在这里插入图片描述

(2)方式二:使用反射

方式一的升级版,程序中不直接出现第三方的API(com.mysql.jdbc.Driver),使得程序具有更好的可移植性

import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;

public class TestConnection2 {
    public static void main(String[] args) throws Exception{
        //1、获取Driver接口的实现类对象:使用反射
        Class clazz = Class.forName("com.mysql.jdbc.Driver");//只需要一个字符串,程序的灵活性更高。
        Driver driver =(Driver)clazz.newInstance();//通过反射使用Driver实现类的空参构造器创建实例
        
        // 2、提供要连接的数据库
        String url = "jdbc:mysql://localhost:3306/test";
       
        // 3、提供连接需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "123456");
        
        // 4、获取连接
        Connection conn = driver.connect(url, info);
        
        //检验连接是否成功
        System.out.println(conn);
    }
}

在这里插入图片描述

(3)方式三:使用DriverManager替换Driver

java.sql.DriverManager(驱动程序管理器类)中有如下常用静态方法:

  • static Connection getConnection(String url) 尝试建立与给定数据库URL的连接。
  • static Connection getConnection(String url, Properties info) 尝试建立与给定数据库URL的连接。
  • static Connection getConnection(String url, String user, String password) 尝试建立与给定数据库URL的连接。 (用得最多
  • static void registerDriver(Driver driver) 注册给定的驱动程序 。
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;

public class TestConnection3 {
    public static void main(String[] args) throws Exception {
        ///1、获取Driver实现类的对象
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();

        ///2、提供另外三个连接的基本信息:
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "123456";

        //3、注册驱动
        DriverManager.registerDriver(driver);

        ///4、获取连接
        Connection conn = DriverManager.getConnection(url, user, password);

        //检验连接是否成功
        System.out.println(conn);
    }
}

在这里插入图片描述

(4)方式四:在方式三的基础上,省略注册驱动

我们通过查看com.mysql.jdbc.Driver的源码发现:
在这里插入图片描述
源码中有一个static静态块,里面已经帮我们创建好了Driver实现类的实例,并且也将其注册了,因此我们可以在方式三的基础上,省略一部分代码:

Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。

import java.sql.Connection;
import java.sql.DriverManager;

public class TestConnection4 {
    public static void main(String[] args) throws Exception {
        //1、加载驱动
        Class.forName("com.mysql.jdbc.Driver");

        //2、提供三个连接的基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "123456";

        //3、获取连接
        Connection conn = DriverManager.getConnection(url, user, password);

        //检验连接是否成功
        System.out.println(conn);
    }
}

在这里插入图片描述

(5)方式五:(最终版)将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接

首先,在项目的src目录下新建一个File:【jdbc.properties】作为配置文件:

user=root
password=123456
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
driverClass=com.mysql.jdbc.Driver

其中useUnicode=true&characterEncoding=utf8避免出现乱码

Java代码:

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class TestConnection5 {
    public static void main(String[] args) throws Exception {
        //1、用类加载器读取配置文件中的4个基本信息
        InputStream is = TestConnection5.class.getClassLoader().getResourceAsStream("jdbc.properties");

        //创建属性集合类,用于载入配置文件的属性数据
        Properties pros = new Properties();
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

        //2、加载驱动
        Class.forName(driverClass);

        //3、获取连接
        Connection conn = DriverManager.getConnection(url, user, password);

        //检验是否连接成功
        System.out.println(conn);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-va3TEX9G-1678084491764)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303204736176.png)]

使用配置文件的好处:

①实现了代码和数据的分离,实现了解耦。如果需要修改配置信息,直接在配置文件中修改,不需要修改Java代码
②如果修改了配置信息,省去重新编译的过程,也避免了程序重新打包。

6)小结

本小节包含了两个步骤:

①加载并注册驱动
  • 加载驱动

    :加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名

    • Class.forName(“com.mysql.jdbc.Driver”);
  • 注册驱动

    :DriverManager 类是驱动程序管理器类,负责管理驱动程序

    • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动
    • 通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,因为 Driver 接口的驱动程序类中包含一个静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。
②创建Connection对象

使用DriverManager的getConnection方法来创建Connection对象,获取与数据库的连接。

5.Connection接口

java.sql.Connection

Connection connection = DriverManager.getConnection(url, username, password);//获取数据库连接

JDBC程序中的Connection类的对象connection,它用于代表数据库的链接,connection是数据库编程中十分重要的一个对象,客户端与数据库所有交互都是通过Connection类的对象connection完成的。

常用方法:
  • Statement createStatement():创建向数据库发送sql的statement对象。
  • PreparedStatement prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
  • CallableStatement prepareCall(sql):创建执行存储过程的callableStatement对象。
  • void setAutoCommit(boolean autoCommit):设置事务是否自动提交。
  • void commit() :在链接上提交事务。
  • void rollback() :在此链接上回滚事务。

(三)使用PreparedStatement实现CRUD操作

1.操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
  • 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程
      在这里插入图片描述

2.Statement接口介绍

java.sql.Statement是一个接口
通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

Statement statement = connection.createStatement();//获取用于向数据库发送sql语句的statement对象

JDBC程序中的Statement类的对象用于向数据库发送SQL语句

常用方法:
  • ResultSet executeQuery(String sql) :用于向数据发送查询语句SELECT
  • int executeUpdate(String sql):用于向数据库发送INSERT、UPDATE或DELETE语句
  • boolean execute(String sql):用于向数据库发送任意sql语句

3.ResultSet接口介绍

java.sql.ResultSet也是一个接口,通过Statement及其子接口中的executeQuery(sql)方法可以返回ResultSet的对象。ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现

ResultSet resultSet = statement.executeQuery(sql);//向数据库发送sql,并获取代表结果集的resultset
  • Resultset封装执行结果时,采用的类似于表格的方式。
  • ResultSet 类的对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行的前面,可以调用resultSet.next() 方法移动到下一行。next()方法会首先检测是否存在下一行。若存在,该方法返回 true,且指针下移。若不存在,指针不会下移。相当于Iterator对象的 hasNext()next() 方法的结合体。
  • ResultSet既然用于封装执行结果的,所以它也提供了用于获取数据的get方法:
    • 获取任意类型的数据
      • getObject(int index)获取第index列的数据,从1开始
      • getObject(string columnName)获取列名为columnName的数据
    • 获取指定类型的数据
      • getXxx(int index)获取第index列的数据,从1开始
      • getXxx(String columnName)获取列名为columnName的数据
      • 例如: getInt(1), getInt(“id”),getString(2),getString(“name”)
    • 其他
      • ResultSetMetaData getMetaData()获取关于 ResultSet 对象中列的类型和属性信息的对象

注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
在这里插入图片描述

4.ResultSetMetaData接口介绍

java.sql.ResultSetMetaData可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

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

5.使用Statement操作数据表的弊端

那么为什么不用Statement来实现CRUD操作呢?

  • 弊端一:存在拼串操作,繁琐
  • 弊端二:存在SQL注入问题
(1)什么是SQL注入

SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。

(2)案例演示

已知有这样一张user_table表:
在这里插入图片描述

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;

public class StatementTest {
    public static void main(String[] args){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            1、用类加载器读取配置文件中的4个基本信息
            InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //创建属性集合类,用于载入配置文件的属性数据
            Properties properties = new Properties();
            properties.load(is);
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");
            //2、加载驱动
            Class.forName(driverClass);
            //3、获取连接
            connection = DriverManager.getConnection(url, user, password);
            //4、获取statement对象
            statement = connection.createStatement();
            //5、输入用户名密码,与数据表进行校验
            Scanner scanner = new Scanner(System.in);
            System.out.print("请输入用户名:");
            String username = scanner.nextLine();
            System.out.print("请输入密码:");
            String pwd = scanner.nextLine();
            //6、编写SQL语句(需要拼串,繁琐)
            String sql="select * from user_table where user='"+username+"'and password='"+pwd+"'";
            System.out.println(sql);
            //7、statement执行SQL语句,获得结果集
            resultSet = statement.executeQuery(sql);
            //8、输出结果集的数据
            while(resultSet.next()){
                System.out.println(resultSet.getString(1));
                System.out.println(resultSet.getString(2));
                System.out.println(resultSet.getInt(3));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {//9、释放资源
            if(resultSet!=null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(statement!=null){
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述
输入的用户名和密码添加了特殊的字符,改变了SQL语句,影响了程序运行轨迹:

SELECT * FROM user_table WHERE USER='a' OR 1 = ' AND password = ' OR '1' = '1'

因为1=1恒成立,所以它会将所有的用户名和密码等信息全部查询出来,十分不安全。

对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
在这里插入图片描述

6.PreaparedStatement接口介绍

可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

PreparedStatement ps=connection.preparedStatement(sql);
  • PreperedStatement是Statement的子接口,它表示一条预编译过的 SQL 语句
  • PreperedStatement对象所代表的 SQL 语句中的参数,允许使用问号(?)等占位符的形式进行对输入数据的替换,简化SQL语句的编写。调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数,setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个参数是设置 SQL 语句中的参数的值
常用方法:

注意:和Statement不同,PreparedStatement的以下方法没有参数

  • ResultSet executeQuery() :用于向数据发送查询语句SELECT
  • int executeUpdate():用于向数据库发送INSERT、UPDATE或DELETE语句(返回值是受影响的行数,通常可以根据这个返回值判断语句是否执行)
  • boolean execute():用于向数据库发送任意sql语句

7.PreaparedStatement和Statement的区别

  • PreaparedStatement相较于Statement,提升了代码的可读性和可维护性
  • PreparedStatement 能最大可能提高性能
    • Statement的对象会使数据库 频繁编译 SQL,可能造成数据库缓冲区溢出。(每次编译都要进行语法检查,语义检查,翻译成二进制命令,缓存)
    • PreparedStatement的对象可对SQL进行预编译,从而提高数据库的执行效率。数据库服务器会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被数据库服务器的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
  • Statement 容易造成SQL语句拼接错误,不能防止SQL注入攻击; PreparedStatement 可以防止 SQL 注入
  • PreparedStatement 可以操作Blob类型的数据,而Statement做不到。
  • PreparedStatement 可以实现更高效的批量操作,例如想要向数据表中插入多条记录,那么预编译好插入语句以后,执行代码被缓存下来,之后每次插入不需要再编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

8.Java与SQL对应数据类型转换表

Java类型SQL类型
booleanBIT
byteTINYINT
shortSMALLINT
intINTEGER
longBIGINT
StringCHAR,VARCHAR,LONGVARCHAR
byte arrayBINARY , VAR BINARY
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP

9.使用PreparedStatement实现增、删、改操作

固定步骤:
① 加载驱动
② 获取与数据库的连接
③ 获取用于向数据库发送sql语句的PreparedStatement对象并填充占位符
④ 向数据库发sql
⑤ 关闭连接,释放资源

(1)使用PreparedStatement实现增加一条数据

已知存在以下customers表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7z5zGS6U-1678084491765)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303205939636.png)]
表的结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tux6AKnP-1678084491766)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303205956571.png)]
varchar对应String,date对应 java.sql.date

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class TestPreparedStatement1 {
    public static void main(String[] args){
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1、用类加载器读取配置文件中的4个基本信息
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            //创建属性集合类,用于载入配置文件的属性数据
            Properties properties = new Properties();
            properties.load(is);
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");
            //2、加载驱动
            Class.forName(driverClass);
            //3、获取连接
            connection = DriverManager.getConnection(url, user, password);
            //4、预编译sql语句,返回PreparedStatement的实例
            String sql="insert into customers(name,email,birth) values(?,?,?)";
            ps = connection.prepareStatement(sql);
            //5、填充占位符
            ps.setString(1,"杨幂");//第1个占位符的值为"杨幂"
            ps.setString(2,"yangmi@qq.com");//第2个占位符的值为"yangmi@qq.com"
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse("1986-09-12");
            //void setDate(int parameterIndex, Date x)
            //第二个参数要传入的Date值是java.sql.Date类型,它有一个构造:
            // Date(long m)使用给定的毫秒时间值构造一个java.sql.Date对象。
            ps.setDate(3,new java.sql.Date(date.getTime()));//第3个占位符的值为"1986-09-12"
            //ps.setString(3,"1986-09-12");这样也行,只要格式是xxxx-xx-xx,默认会隐式转换,将字符串转换成Date类型
            //6、执行操作,向数据库发送sql语句
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {//7、释放资源
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(ps!=null){
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvcvECwv-1678084491766)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210020336.png)]

(2)封装数据库连接和释放资源的操作

我们发现,每次编写JDBC的代码,数据库连接和释放资源的操作都是一样的,那么我们就可以新建一个自定义工具类JDBCUtils,负责创建连接和释放数据库资源,以后调用该工具类的方法就行了。

JDBCUtils:

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;


public class JDBCUtils {
    //获取连接
    public static Connection getConnection() throws Exception {
        //1、用类加载器读取配置文件中的4个基本信息
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        //创建属性集合类,用于载入配置文件的属性数据
        Properties properties = new Properties();
        properties.load(is);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");
        //2、加载驱动
        Class.forName(driverClass);
        //3、获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }

    //释放资源
    public static void closeResource(Connection connection, Statement statement){
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    //释放资源(重载)
    public static void closeResource(Connection connection, Statement statement, ResultSet resultSet){
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement!=null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
(3)使用PreparedStatement实现修改一条数据
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class TestPreparedStatement2 {
    public static void main(String[] args){
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            connection = JDBCUtils.getConnection();
            //2.预编译sql语句,返回PreparedStatement的实例
            String sql="update customers set name=? where id=?";
            ps = connection.prepareStatement(sql);
            //3.填充占位符
            ps.setString(1,"古力娜扎");
            ps.setInt(2,8);
            //4.执行
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
            JDBCUtils.closeResource(connection,ps);
        }
    }
}
(4)使用PreparedStatement实现删除一条数据
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class TestPreparedStatement3 {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql="delete from customers where id=?";
            ps = connection.prepareStatement(sql);
            ps.setInt(1,12);
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps);
        }
    }
}
(5)通用的增删改操作

我们发现,增删改三种操作就要写三种代码,那么能不能写统一的逻辑实现,让增删改都能用呢?
案例:假设已存在以下user_table表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4JIW1or-1678084491767)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210102486.png)]

import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class TestPreparedStatement4 {
    public static void main(String[] args) {
        String sql="insert into user_table values(?,?,?)";
        update(sql,"小白","666666",1200);
        sql="update user_table set password=? where user=?";
        update(sql,"abcdef","小黑");
        sql="delete from user_table where user=?";
        update(sql,"小刚");
    }
    
    //通用的增删改操作
    public static void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度必须相同!
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = JDBCUtils.getConnection();
            ps = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);//不知道数据的类型,不妨写setObject
            }
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Cm7UtPD-1678084491767)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210118033.png)]

10.使用PreparedStatement实现查询操作

固定步骤:
① 加载驱动
② 获取与数据库的连接
③ 获取用于向数据库发送sql语句的PreparedStatement对象并填充占位符
④ 向数据库发sql,并获取代表结果集的ResultSet对象
⑤ 取出结果集的数据
⑥ 关闭连接,释放资源

(1)针对customers表的普通查询操作

已知customers表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MCb7zQtY-1678084491768)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210137585.png)]

ORM思想(Object Relational Mapping)

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

首先,创建一个对应的Customer类,用于封装结果

import java.sql.Date;

public class Customer {
    private int id;
    private String name;
    private String email;
    private Date birth;
    public Customer() {
        super();
    }
    public Customer(int id, String name, String email, Date birth) {
        super();
        this.id = id;
        this.name = name;
        this.email = email;
        this.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;
    }
    @Override
    public String toString() {
        return "Customer [id=" + id + ", name=" + name + ", email=" + email + ", birth=" + birth + "]";
    }
}

针对customers表的普通查询操作:

import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class QueryForCustomers1 {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql="select id,name,email,birth from customers where id=?";
            ps = connection.prepareStatement(sql);
            ps.setInt(1,1);
            //执行,并返回结果集
            rs = ps.executeQuery();

            //处理结果集
            if(rs.next()){//next():判断结果集的下一条是否有数据,如果有数据返回true,并指针下移;如果返回false,指针不会下移。
                //获取当前这行查询结果的各个字段值
                int id = rs.getInt(1);
                String name = rs.getString(2);
                String email = rs.getString(3);
                Date birth = rs.getDate(4);
                //将数据封装为一个对象(推荐),也可以用集合、Object数组、或者直接打印
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqxmDJ5f-1678084491768)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210217128.png)]

(2)针对customers表的通用查询操作
import com.fox.bean.Customer;
import com.fox.util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class QueryForCustomers2 {
    public static void main(String[] args) {
        String sql="select id,name,birth,email from customers where name=?";
        Customer customer = query(sql, "杨幂");
        System.out.println(customer);
        sql="select id,name from customers where id=?";
        Customer customer1 = query(sql, 4);
        System.out.println(customer1);
    }
    public static Customer query(String sql,Object ...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        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 可用于获取关于ResultSet结果集中列的类型和属性信息
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();
            //处理结果集
            if(rs.next()){//if 适用于查询结果只有一行的情况
                Customer cust = new Customer();
                //处理结果集一行数据中的每一个列
                for(int i = 0;i <columnCount;i++){
                    //获取列数据
                    Object columnData = rs.getObject(i + 1);

                    //获取每个列的列名
					String columnName = rsmd.getColumnName(i + 1);

                    //给cust对象指定的columnName属性,赋值为columnData:通过反射
                    Field field = Customer.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(cust, columnData);
                }
                return cust;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(connection, ps, rs);
        }
        return null;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aB1drHlE-1678084491769)(C:\Users\23886\AppData\Roaming\Typora\typora-user-images\image-20230303210242422.png)]

(3)针对order表的通用查询操作

已知order表:
在这里插入图片描述
首先,根据ORM思想,创建一个对应的Order类,用于封装结果:

import java.sql.Date;

public class Order {
    private int orderId;
    private String orderName;
    private Date orderDate;

    public Order() {
    }

    public Order(int orderId, String orderName, Date orderDate) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderDate = 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;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderName='" + orderName + '\'' +
                ", orderDate=" + orderDate +
                '}';
    }
}

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

import com.fox.bean.Order;
import com.fox.util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class QueryForOrder {
    //测试
    public static void main(String[] args) {
        //order表必须要加上``,不然数据库会以为是order排序查询
        //因为数据库的字段名和Java类Order的属性名不一致,所以要起别名
        String sql="select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id=?";
        Order order = query(sql, 4);
        System.out.println(order);
    }
    //针对于order表的通用的查询操作
    public static Order query(String sql,Object ...args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        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();
            if(rs.next()){
                Order order = new Order();
                for (int i = 0; i < columnCount; i++) {
                    Object columnData = rs.getObject(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //String getColumnLabel(int column):获取指定列的别名,getColumnName获取表的列名
                    Field field = Order.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(order,columnData);
                }
                return order;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }
}

在这里插入图片描述

在这里插入图片描述
sql是需要结合列名和表的属性名来写。注意起别名。
哪怕sql语句中没有起别名,也建议使用getColumnLabel()(没有起别名同样能获取到字段名),不建议使用getColumnName()

(4)针对不同表的通用查询操作

我们学会了针对某张表的通用查询操作,那么是否能实现针对不同表的通用查询操作,让每一张表的查询操作都只需要一个逻辑实现呢?

①查询结果只有一条记录
import com.fox.bean.Customer;
import com.fox.bean.Order;
import com.fox.util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class QueryForAll {
    public static void main(String[] args) {
        String sql="select id,name,birth,email from customers where name=?";
        Customer customer = query(Customer.class, sql, "周杰伦");
        System.out.println(customer);
		//如果没有占位符,那么可变参数可以不写
        sql="select order_id orderId,order_name orderName,order_date orderDate from `order`";
        Order order = query(Order.class, sql);
        System.out.println(order);
    }


    //针对于不同的表的通用的查询操作,适用于 只返回表中的一条记录
    public static <T> T query(Class<T> clazz,String sql,Object ...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        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();
            if(rs.next()){
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object columnData = rs.getObject(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);//getColumnLabel获取表的列的别名
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnData);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }
}

在这里插入图片描述

②查询结果有多条记录
import com.fox.bean.Customer;
import com.fox.bean.Order;
import com.fox.util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class QueryForAll {
    public static void main(String[] args) {
        String sql="select id,name,birth,email from customers where id<?";
        List<Customer> list1 = query(Customer.class, sql, 10);
        for (Customer customer : list1) {
            System.out.println(customer);
        }

        sql="select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id<?";
        List<Order> list2 = query(Order.class, sql, 3);
        for (Order order : list2) {
            System.out.println(order);
        }
    }

    //针对于不同的表的通用的查询操作,适用于 返回表中的多条记录,用集合来收
    public static <T> List<T> query(Class<T> clazz, String sql, Object ...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        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();
            ArrayList<T> list = new ArrayList<>();
            while(rs.next()){
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object columnData = rs.getObject(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnData);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }
}

在这里插入图片描述

(四)释放资源

JDBC程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象, 这些对象通常是ResultSet、Statement和Connection类的对象。

注意

  • 特别是Connection类的对象,它是非常稀有的资源,用完后必须马上释放。如果Connection类的对象不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
    nt i = 0; i < args.length; i++) {
    ps.setObject(i+1,args[i]);
    }
    rs = ps.executeQuery();
    ResultSetMetaData rsmd = rs.getMetaData();
    int columnCount = rsmd.getColumnCount();
    ArrayList list = new ArrayList<>();
    while(rs.next()){
    T t = clazz.newInstance();
    for (int i = 0; i < columnCount; i++) {
    Object columnData = rs.getObject(i + 1);
    String columnLabel = rsmd.getColumnLabel(i + 1);
    Field field = clazz.getDeclaredField(columnLabel);
    field.setAccessible(true);
    field.set(t,columnData);
    }
    list.add(t);
    }
    return list;
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    JDBCUtils.closeResource(connection,ps,rs);
    }
    return null;
    }
    }

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210517213102801.png)

## (四)释放资源

JDBC程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象, 这些对象通常是**ResultSet、Statement和Connection**类的对象。

**注意**:

- 特别是Connection类的对象,它是非常稀有的资源,用完后必须马上释放。如果Connection类的对象不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
- 可以将资源释放代码写入finally中,保证即使其他代码出现异常,资源也一定能被关闭。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值