JDBC的基本使用
1、JDBC和JDBC驱动的基本概念
JDBC(Java DataBase Connectivity),指 Java 数据库连接,是一种标准Java应用编程接口(JAVA API),是 Java 语言用来连接和操作数据库的。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问。而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
实际上,JDBC 就是官方定义的一套操作所有关系型数据库的规则,也就是接口。而 JDBC 驱动就是实现了这些接口的实现类,各个数据库厂商去实现这些接口,也就是 JDBC 驱动,提供数据库驱动 jar 包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动 jar 包中的实现类。
JDBC 是一套接口规范,它在Java的标准库java.sql
里放着,不过这里面大部分都是接口。接口并不能直接实例化,而是必须实例化对应的实现类,然后通过接口引用这个实例。JDBC接口的实现类就是 JDBC 驱动。JDBC接口并不知道我们要使用哪个数据库,所以,用哪个数据库,我们就去使用哪个数据库的“实现类”,我们把某个数据库实现了JDBC接口的jar包称为 JDBC 驱动。
例如,我们在Java代码中要访问MySQL,那么必须编写代码操作JDBC接口。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,例如,MySQL的JDBC驱动由Oracle提供。因此,访问某个具体的数据库,我们只需要引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库,因为他们都提供了JDBC驱动:
如果从代码上来看,Java标准库自带的JDBC接口其实就是定义了一组接口,而某个具体的JDBC驱动其实就是实现了这些接口的类:
实际上,一个MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器:
1.1、使用JDBC的好处
JDBC API 是一个 Java API,它可以访问任何类型的表格数据,特别是可以访问存储在关系数据库里的数据。JDBC 可以用 Java 语言在各种平台上实现,比如 Windows 系统, Mac OS 系统,和各种版本的 UNIX 系统。
并且:
-
各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;
-
Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;
-
可随时替换底层数据库,访问数据库的Java代码基本不变。
2、下载导入 JDBC 驱动
2.1、下载 JDBC 驱动
可参考:https://www.cnblogs.com/NyanKoSenSei/p/11510438.html
2.2、将 JDBC 驱动导入项目
将 JDBC 驱动导入项目就跟将普通的 jar 包导入项目一样。下载完 JDBC 驱动后,将其解压,可以看到里面有两个 jar 包。
在 java 项目中新建一个 lib 文件夹,将 JDBC 解压后里面的 xxx.bin.jar 包复制到项目的 lib 文件夹中,右键要使用的jar包,选择Build Path-->Add to Build Path,将其添加为项目依赖即可。
可参考:https://jingyan.baidu.com/article/bad08e1e23982609c851219e.html
3、使用JDBC操作数据库
构建一个 JDBC 应用程序包括以下六个步骤-
-
导入数据包:需要你导入含有需要进行数据库编程的 JDBC 类的包。大多数情况下,使用 import java.sql. 就足够了。
-
注册 JDBC 驱动器:需要你初始化一个驱动器,以便于你打开一个与数据库的通信通道。
-
打开连接:需要使用 DriverManager.getConnection() 方法创建一个 Connection 对象,它代表与数据库的物理连接。
-
执行查询:需要使用类型声明的对象建立并提交一个 SQL 语句到数据库。
-
提取结果数据:要求使用适当的 ResultSet.getXXX() 方法从结果集中检索数据。
- 清理环境:依靠 JVM 的垃圾收集来关闭所有需要明确关闭的数据库资源。
3.1、操作数据库代码示例
在导入驱动 jar 包后,我们就可以使用 JDBC 驱动来操作数据库了。
代码示例:
package jdbcTest; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCTest { public static void main(String[] args) throws Exception { //1.注册驱动(mysql5之后的驱动jar包可以省略注册驱动的步骤) Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接对象 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test", "root", "123456"); //3.定义sql语句 String sql = "update students set name = 'hahaha' where id = 1"; //4.获取执行sql的对象 Statement stmt = conn.createStatement(); //5.执行sql int count = stmt.executeUpdate(sql); System.out.println(count); //6.释放资源 stmt.close(); conn.close(); } }
合理地 try{} catch(){} 的写法:
public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { //1.注册驱动(mysql5之后的驱动jar包可以省略注册驱动的步骤) Class.forName("com.mysql.jdbc.Driver"); //2.获取数据库连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test", "root", "123456"); //3.定义sql语句 String sql = "update students set name = 'hahaha' where id = 1"; //4.获取执行sql的对象 stmt = conn.createStatement(); //5.执行sql int count = stmt.executeUpdate(sql); System.out.println(count); } catch (Exception e) { e.printStackTrace(); }finally { //6.释放资源 if(stmt != null) { try { stmt.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
3.2、常用的 JDBC 组件介绍
JDBC 的 API 提供了以下接口和类:
DriverManager :这个类管理一系列数据库驱动程序。匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。
Driver : 这个接口处理与数据库服务器的通信。你将很少直接与驱动程序互动。相反,你使用 DriverManager 中的对象,它管理此类型的对象。它也抽象与驱动程序对象工作相关的详细信息。
Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,即,所有与数据库的通信仅通过这个连接对象进行。
Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。除了执行存储过程以外,一些派生的接口也接受参数。
ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据获得的数据。它作为一个迭代器,让您可以通过它的数据来移动。
SQLException : 这个类处理发生在数据库应用程序的任何错误。
3.3、先注册 JDBC 驱动程序(mysql5之后的驱动程序可省略)
在使用驱动程序之前,你必须在你的程序里面注册它。我们可以通过加载 Oracle 驱动程序的类文件到内存中来注册驱动程序,在程序里做一次注册即可。注册驱动实际上就是告诉程序应该使用哪个数据库驱动 jar 包。
注册一个驱动程序中最常用的方法是使用 Java 的 Class.forName() 方法来动态加载驱动程序的类文件到内存中,它会自动将其注册。
try { Class.forName("oracle.jdbc.driver.OracleDriver"); } catch(ClassNotFoundException ex) { System.out.println("Error: unable to load driver class!"); }
(注意:在mysql5之后的驱动jar包可以省略注册驱动的步骤)
3.4、然后使用JDBC连接数据库(Connection)
要通过 JDBC 操作数据库,我们需要先连接数据库。当你加载了驱动程序之后,你可以通过 DriverManager.getConnection() 方法建立一个连接。Connection代表一个JDBC连接,它相当于 Java 程序到数据库的连接(通常是TCP连接)。
打开一个Connection时,需要准备URL、用户名和密码,才能成功连接到数据库。
数据库连接的代码示例如下:
// JDBC连接的URL, 不同数据库有不同的格式: String JDBC_URL = "jdbc:mysql://localhost:3306/test"; //URL String JDBC_USER = "root"; //用户名 String JDBC_PASSWORD = "password"; //密码 // 获取连接 Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD); ... // 最后需关闭连接 conn.close();
核心代码是DriverManager
提供的静态方法getConnection()
。DriverManager
会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。
JDBC连接是一种昂贵的资源,使用后要及时释放。可以使用try (resource)
来自动释放JDBC连接:
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { ... }
在 JDBC 程序的末尾,它必须明确关闭所有的连接到数据库的连接,以结束每个数据库会话。但是,如果忘了,Java 垃圾收集器也会关闭连接,它会完全清除过期的对象。依托垃圾收集器,特别是在数据库编程,是非常差的编程习惯,我们应该养成用 close()方法关闭连接对象的习惯。
3.4.1、数据库连接的URL格式
URL是由数据库厂商指定的格式,例如,MySQL的URL是:
jdbc:mysql://服务器IP:端口号/数据库名称?key1=value1&key2=value2
假设数据库运行在本机localhost,端口使用标准的3306,数据库名称是learnjdbc。示例如下:
jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8 # 如果连接的是本地服务器,并且服务器默认端口是3306,那么可以简写为以下形式,即省略服务器地址和端口号: jdbc:mysql:///数据库名称
(后面的两个参数表示不使用SSL加密,使用UTF-8作为字符编码(注意MySQL的UTF-8是utf8),不写也行)
下表列出了常用的 JDBC 驱动程序名和数据库URL。
上表中 URL 格式所有加粗的部分都是静态的,你需要将剩余部分按照你的数据库实际情况进行设置。
3.4.2、如何创建连接对象
我们可以通过 DriverManager.getConnection() 方法来建立一个数据库连接,加载 DriverManager.getConnection() 参数有以下三种:
- getConnection(String url)
- getConnection(String url, Properties prop)
- getConnection(String url, String user, String password)
使用URL、用户名、密码:getConnection() 最常用的方式是需要你提供一个数据库 URL,用户名和密码:
String URL = "jdbc:oracle:thin:@amrood:1521:EMP"; String USER = "username"; String PASS = "password" Connection conn = DriverManager.getConnection(URL, USER, PASS);
只使用URL:在只使用url时,数据库的 URL ,包括用户名和密码,将表现为以下的格式:
jdbc:oracle:driver:username/password@database
示例:
String URL = "jdbc:oracle:thin:username/password@amrood:1521:EMP";
Connection conn = DriverManager.getConnection(URL);
使用数据库 URL 和 Properties 对象:Properties 对象保存了一组关键数值。它通过调用 getConnection() 方法,将驱动程序属性传递给驱动程序。
import java.util.*; String URL = "jdbc:oracle:thin:@amrood:1521:EMP"; Properties info = new Properties( ); info.put( "user", "username" ); info.put( "password", "password" ); Connection conn = DriverManager.getConnection(URL, info);
3.5、获取执行sql的对象并且执行sql
一旦我们获得了数据库的连接,我们就可以和数据库进行交互。JDBC 的 Statement,CallableStatement 和 PreparedStatement 接口定义的方法和属性,可以让你发送 SQL 命令或 PL/SQL 命令到数据库,并从你的数据库接收数据。在数据库中,它们还定义了帮助 Java 和 SQL 数据类型之间转换数据差异的方法。
下表提供了每个接口的用途概要,根据实际目的决定使用哪个接口。
查询数据库可以分为以下几步:
第一步,通过Connection
提供的createStatement()
方法创建一个Statement
对象,用于执行一个查询;
第二步,执行Statement
对象提供的executeQuery("SELECT * FROM students")
并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet
来引用这个结果集;
第三步,反复调用ResultSet
的next()
方法并读取每一行结果。
完整查询代码如下:
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) { while (rs.next()) { long id = rs.getLong(1); // 注意:索引从1开始 long grade = rs.getLong(2); String name = rs.getString(3); int gender = rs.getInt(4); } } } }
注意要点:
Statment
和ResultSet
都是需要关闭的资源,因此嵌套使用try (resource)
确保及时关闭;rs.next()
用于判断是否有下一行记录,如果有,将自动把当前行移动到下一行(一开始获得ResultSet
时当前行不是第一行);ResultSet
获取列时,索引从1
开始而不是0
;必须根据SELECT
的列的对应位置来调用getLong(1)
,getString(2)
这些方法,否则对应位置的数据类型不对,将报错。
3.5.1、使用 Statement 对象来操作数据库
在你准备使用 Statement 对象执行 SQL 语句之前,你需要使用 Connection 对象的 createStatement() 方法先创建一个 Statement 对象。
Connection conn = DriverManager.getConnection(URL, 用户名, 密码);
Statement stmt = conn.createStatement( );
当你创建了一个 Statement 对象之后,你可以用它的三个执行方法的任一方法来执行 SQL 语句。
-
boolean execute(String SQL) : 该方法可以执行任何的SQL语句。如果第一个结果是一个 ResultSet 对象,则返回的布尔值为 true ,否则返回 false 。当你需要使用真正的动态 SQL 时,可以使用这个方法来执行 SQL DDL 语句。
-
int executeUpdate(String SQL) : 常用该方法来执行DML语句(增删改),也可执行DDL语句(操作数据库和表结构)。它返回的是执行 SQL 语句影响的行的数目。
- ResultSet executeQuery(String SQL) : 常用该方法执行DQL语句(查询),它返回一个 ResultSet 对象。
String sql = "update students set name = 'hahaha' where id = 1"; Statement stmt = conn.createStatement(); int count = stmt.executeUpdate(sql);
在使用后我们应该关闭 Statement 对象。通过调用 close() 方法就可以关闭 Statement 对象。其实在我们关闭了 Connection 对象后,它也会自动关闭 Statement 对象。但我们应该始终明确关闭 Statement 对象,以确保真正的清除。
Statement stmt = null; try { stmt = conn.createStatement( ); . . . } catch (SQLException e) { . . . } finally { stmt.close(); }
3.5.2、使用PreparedStatement对象操作数据库
PreparedStatement 接口扩展了 Statement 接口,它让你用一个常用的 Statement 对象增加几个高级功能。这个 statement 对象可以提供灵活多变的动态参数。
创建 PreparedStatement 对象:
String SQL = "Update Employees SET age = ? WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(SQL);
JDBC 中所有的参数都被用 ? 符号表示,这是已知的参数标记。在执行 SQL 语句之前,你必须赋予每一个参数确切的数值。
setXXX() 方法将值绑定到参数,其中 XXX 表示你希望绑定到输入参数的 Java 数据类型。如果你忘了赋予值,你将收到一个 SQLException。每个参数标记映射它的序号位置。第一标记表示位置 1 ,下一个位置为 2 等等。这种方法不同于 Java 数组索引,它是从 0 开始的。
String sql = "SELECT * FROM user WHERE login=? AND pass=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setObject(1, name); ps.setObject(2, pass);
所有的 Statement对象 的方法都与数据库交互,(a) execute(),(b) executeQuery(),及 (c) executeUpdate() 也能被 PreparedStatement 对象引用。然而,这些方法被 SQL 语句修改后是可以输入参数的。
PreparedStatement 对象在使用后也需要关闭,只需简单调用 close() 方法就可以完成这项工作。如果你关闭了 Connection 对象,那么它也会关闭 PreparedStatement 对象。然而,你应该始终明确关闭 PreparedStatement 对象,以确保真正的清除。
4、JDBC事务
数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized
同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:
- Atomicity:原子性
- Consistency:一致性
- Isolation:隔离性
- Durability:持久性
数据库事务可以并发执行,而数据库系统从效率考虑,对事务定义了不同的隔离级别。SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
4.1、JDBC管理事务
要在JDBC中执行事务,本质上就是如何把多条SQL包裹在一个数据库事务中执行。
在 JDBC 中,我们可以使用 connection 对象来管理事务,connection 对象提供了3个方法来进行事务管理:
-
setAutoCommit():设置是否自动提交,方法中需要传入一个boolean类型的参数,true为自动提交,false为手动提交。进行事务管理时,应该在所有sql执行前设置为false,此时意味着开启了事务
-
commit():主动提交事务。应该在当所有sql执行完毕时调用该方法主动提交事务。
-
rollback():回滚事务。应该在捕获异常时调用giant方法来回滚事务。
伪代码:
Connection conn = openConnection(); try { // 关闭自动提交,即开启事务 conn.setAutoCommit(false); // 执行多条SQL语句: insert(); update(); delete(); // 提交事务: conn.commit(); } catch (SQLException e) { // 回滚事务: conn.rollback(); } finally { conn.setAutoCommit(true); //最后恢复至自动提交 conn.close(); }
在MySQL中,默认会自动提交事务,也就是任意一条SQL语句都会被当做是一个事务,并且自动提交,所以需要主动将自动提交关闭掉。使用 conn.setAutoCommit(false) 来关闭自动提交,即开启事务。执行完指定的若干条SQL语句后,我们可以调用 conn.commit() 来提交事务。
如果事务提交失败,此时我们可以调用conn.rollback()来
回滚事务。最后,在finally
中通过conn.setAutoCommit(true)
把Connection
对象的状态恢复到初始值。
实例代码如下:
public void jdbcTest2() { Connection conn = null; PreparedStatement ps1 = null; PreparedStatement ps2 = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test", "root", "123456"); //关闭自动提交,开启事务 conn.setAutoCommit(false); //多条sql String updatesql1 = "update userbalance set money = money - ? where username = ?"; ps1 = conn.prepareStatement(updatesql1); ps1.setBigDecimal(1, new BigDecimal(500)); ps1.setString(2, "zhangsan"); String updatesql2 = "update userbalance set money = money + ? where username = ?"; ps2 = conn.prepareStatement(updatesql2); ps2.setBigDecimal(1, new BigDecimal(500)); ps2.setString(2, "lisi"); //执行sql int count1 = ps1.executeUpdate(); //模拟异常 int a = 5 / 0; int count2 = ps2.executeUpdate(); //提交事务 conn.commit(); System.out.println("结果:" + count1 + count2); } catch (Exception e) { e.printStackTrace(); //在异常出现时需要回滚事务 try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { //最后需要恢复至自动提交 try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //释放资源。为了避免空指针异常,必须先判断是否为null if(ps1 != null) { try { ps1.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(ps2 != null) { try { ps2.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(conn != null) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
4.2、JDBC定义事务的隔离级别
如果要设定事务的隔离级别,可以使用如下代码:
// 设定隔离级别为READ COMMITTED: conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
如果没有调用上述方法,那么会使用数据库的默认隔离级别。MySQL的默认隔离级别是REPEATABLE READ
。