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. 要素三、四:用于连接数据库的用户名和密码
- user、password可以用“属性名=属性值”方式告诉数据库
- 可以调用 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)
:用于向数据发送查询语句SELECTint 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()
:用于向数据发送查询语句SELECTint 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类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
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中,保证即使其他代码出现异常,资源也一定能被关闭。