JDBC教程——检视阅读
参考
JDBC教程——W3Cschool
JDBC教程——一点教程,有高级部分
JDBC教程——易百
JDBC入门教程 – 终极指南
略读
三层架构详解,JDBC在数据访问层:
UI(表现层): 主要是指与用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数据。
BLL:(业务逻辑层): UI层和DAL层之间的桥梁。实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。
DAL:(数据访问层): 与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),UI反映给BLL,BLL反映给DAL,DAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户)
使用三层架构的目的:解耦 。
三层架构详解
驱动程序(Drivers)
JDBC驱动管理器(java.sql.DriverManager)是JDBC API中最重要的元素之一。 它是用来处理一系列JDBC驱动程序的基本服务。它含有很多机制和对象,能够将Java应用程序连接到所需的JDBC驱动程序。它负责管理这些不同类型的JDBC数据库驱动程序。总结一下驱动管理器的主要功能就是:获取当前可用的驱动列表;处理特定的的驱动程序和数据库之间的连接。
我们通过方法DriverManager.registerDriver()来注册驱动程序:
new org.hsqldb.jdbc.JDBCDriver();
DriverManager.registerDriver( new org.hsqldb.jdbc.JDBCDriver() );
我们也可以调用方法Class.forName() 加载驱动程序:
// Loading the HSQLDB JDBC driver
Class.forName( "org.hsqldb.jdbc.JDBCDriver" );
// connection to JDBC using mysql driver
Class.forName( "com.mysql.jdbc.Driver" );
这二者的主要区别是:前者方法registerDerver()需要保证驱动程序在编译时就是可用的;后者加载驱动程序类文件的方式,不需要驱动程序在编译时是可用的。JDBC 4版本后,实际上没有必要调用这些方法,应用程序不需要单独注册这些驱动,也不需要加载驱动类。我们也不推荐使用方法registerDriver()来手动加载驱动程序。
JDBC 指南
JDBC 简介
什么是 JDBC?
JDBC (Java Database Connectivity )指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库。
JDBC API 库包含下面提到的每个任务,都是与数据库相关的常用用法。
- 制作到数据库的连接。
- 创建 SQL 或 MySQL 语句。
- 执行 SQL 或 MySQL 查询数据库。
- 查看和修改所产生的记录。
从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以用 Java 编写不同类型的可执行文件.
JDBC 架构
JDBC 的 API 支持两层和三层处理模式进行数据库访问,但一般的 JDBC 架构由两层处理模式组成:
- JDBC API: 提供了应用程序对 JDBC 管理器的连接(crud数据库操作)。
- JDBC Driver API: 提供了 JDBC 管理器对驱动程序连接(驱动连接)。
JDBC API 使用驱动程序管理器和数据库特定的驱动程序来提供各种各样的 (heterogeneous)数据库的透明连接。
JDBC 驱动程序管理器可确保正确的驱动程序来访问每个数据源。该驱动程序管理器能够支持连接到多个各种各样的数据库的多个并发的驱动程序。
以下是结构图,其中显示了驱动程序管理器相对于在 JDBC 驱动程序和 Java 应用程序所处的位置。
常见的 JDBC 组件
JDBC 的 API 提供了以下接口和类:
DriverManager :这个类管理一系列数据库驱动程序。匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。
Driver : 这个接口处理与数据库服务器的通信**。你将很少直接与驱动程序互动。相反,你使用 DriverManager 中的对象,它管理此类型的对象。它也抽象与驱动程序对象工作相关的详细信息。
Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,即,所有与数据库的通信仅通过这个连接对象进行。
Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。除了执行存储过程以外,一些派生的接口也接受参数。
ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据获得的数据。它作为一个迭代器,让您可以通过它的数据来移动。
SQLException : 这个类处理发生在数据库应用程序的任何错误。
实例:
public class JDBCTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
Statement stmt = null;
try {
//STEP 3: Open a connection
conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//STEP 4: Execute a query
stmt = conn.createStatement();
String sql;
sql = "SELECT id, name, dept, phone FROM t_user";
rs = stmt.executeQuery(sql);
//STEP 5: Extract data from result set
//调用rs.next()时,执行this.thisRow = this.rowData.next();
//这时候返回值会把当前行数据指向游标的下一行数据
while (rs.next()) {
//Retrieve by column name 通过列名获取值
/* int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");*/
//Retrieve by columnIndex 通过列的下标获取值
int id = rs.getInt(1);
String name = rs.getString(2);
String dept = rs.getString(3);
String phone = rs.getString(4);
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs.close();
stmt.close();
conn.close();
}
System.out.println("over !");
}
}
输出:
id: 1name: 大青山dept: amy empirephone: 18956563228
id: 2name: 艾米哈珀dept: amy empirephone: 18956563228
id: 3name: 池寒枫dept: amy empirephone: 22056545
id: 4name: 霍恩斯dept: 森林矮人王国phone: 852-253521
over !
报错:
1、java.sql.SQLException: Column Index out of range, 0 < 1.
这是因为ResultSet结果集是获取列是从1开始的。
int id = rs.getInt(0);//java.sql.SQLException: Column Index out of range, 0 < 1.
//正确操作如下:
int id = rs.getInt(1);
JDBC 驱动类型
什么是 JDBC 驱动程序?
JDBC 驱动实现了 JDBC API 中定义的接口,该接口用于与数据库服务器进行交互。
例如,使用 JDBC 驱动程序可以让你打开数据库连接,并通过发送 SQL 或数据库命令,然后通过 Java 接收结果。
java.sql 包中附带的 JDK,包含了定义各种类与他们的行为和实际实现,这些类都在第三方驱动程序中完成。第三方供应商在他们的数据库驱动程序中都实现了 java.sql.Driver 接口。
JDBC 驱动程序类型
JDBC 驱动程序的实现,因为各种各样的操作系统和 Java 运行在硬件平台的不同而不同。Sun 公司将实现类型分为四类:类型1,2,3,4,其解释如下.
类型1:JDBC-ODBC 桥驱动程序:
在类型1驱动程序中,一个 JDBC 桥接器是用来访问安装在每个客户机上的 ODBC 驱动程序。为了使用 ODBC,需要在目标数据库上配置系统数据源名称(DSN)。
当 Java 刚出来时,这是一个很有用的驱动程序,因为大多数的数据库只支持 ODBC 访问,但现在此类型的驱动程序仅适用于实验用途或在没有其他选择的情况。
自带 JDK 1.2 中的 JDBC-ODBC 桥是这类驱动程序的一个很好的例子。
类型2:JDBC-Native API
在类型2驱动程序中,JDBC API 调用转换成原生的 C/C++ API 调用,这对于数据库来说具有唯一性。这些驱动程序通常由数据库供应商提供,并和 JDBC-ODBC 桥驱动程序同样的方式使用。该供应商的驱动程序必须安装在每台客户机上。
如果我们改变了当前数据库,我们必须改变原生 API ,因为它是具体到某一个数据库,并且他们大多已经失效了。即使这样用类型2驱动程序也能提高一些速度,因为他消除了 ODBC 的开销。
Oracle 调用接口(OCI)驱动程序是一个类型2驱动程序的示例。
类型3:JDBC-Net 纯 Java
在类型3驱动程序中,一般用三层方法来访问数据库。JDBC 客户端使用标准的网络套接字与中间件应用服务器进行通信。套接字的相关信息被中间件应用服务器转换为数据库管理系统所要求的的调用格式,并转发到数据库服务器。
这种驱动程序是非常灵活的,因为它不需要在客户端上安装代码,而且单个驱动程序能提供访问多个数据库。
你可以将应用服务器作为一个 JDBC “代理”,这意味着它会调用客户端应用程序。因此,你需要一些有关服务器配置方面的知识,这样可以高效地使用此驱动程序类型。
你的应用服务器可能使用类型1,2,或4驱动程序与数据库进行通信,了解它们的细微之处将会很有帮助。
类型4:100%纯 Java
在类型4驱动程序中,一个纯粹的基于 Java 的驱动程序通过 socket 连接与供应商的数据库进行通信。这是可用于数据库的最高性能的驱动程序,并且通常由供应商自身提供。
这种驱动器是非常灵活的,你不需要在客户端或服务端上安装特殊的软件。此外,这些驱动程序是可以动态下载的。
MySQL Connector/J 的驱动程序是一个类型4驱动程序。因为它们的网络协议的专有属性,数据库供应商通常提供类型4的驱动程序。
该使用哪种驱动程序?
如果你正在访问一个数据库,如 Oracle,Sybase 或 IBM,首选的驱动程序是类型4。
如果你的 Java 应用程序同时访问多个数据库类型,类型3是首选的驱动程序。
类型2驱动程序是在你的数据库没有提供类型3或类型4驱动程序时使用的。
类型1驱动程序不被认为是部署级的驱动程序,它存在的目的通常仅用于开发和测试。
JDBC 连接数据库
连接数据库
在你安装相应的驱动程序后,就可以用 JDBC 建立一个数据库连接。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.31</version>
</dependency>
编写建立一个 JDBC 连接的程序是相当简单的。下面是简单的四个步骤:
- 导入 JDBC 包:在你的 Java 代码中,用 import 语句添加你所需的类。
- 注册 JDBC 驱动程序:这一步会导致 JVM 加载所需的驱动程序到内存中执行,因此它可以实现你的 JDBC 请求。
- 数据库 URL 制定:这是用来创建格式正确的地址指向你想要连接的数据库。
- 创建连接对象:最后,代码调用 DriverManager 对象的 getConnection() 方法来建立实际的数据库连接。
导入包:
注册 JDBC 驱动程序
//方法1 - Class.forName()
//这种方法更优越一些,因为它允许你对驱动程序的注册信息进行配置,便于移植。
Class.forName("oracle.jdbc.driver.OracleDriver");//推荐
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
//方法2 - DriverManager.registerDriver()
//如果你使用的是不兼容 JVM 的非 JDK,比如微软提供的,你必须使用 registerDriver() 方法。
Driver myDriver = new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver( myDriver );
数据库 URL 制定
当你加载了驱动程序之后,你可以通过 DriverManager.getConnection() 方法建立一个连接。为方便参考,以下列出了三个加载 DriverManager.getConnection() 方法:
- getConnection(String url)
- getConnection(String url, Properties prop)
- getConnection(String url, String user, String password)
下表列出了常用的 JDBC 驱动程序名和数据库URL。
RDBMS JDBC 驱动程序名称 URL 格式
MySQL com.mysql.jdbc.Driver jdbc:mysql://hostname/ databaseName
ORACLE oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@hostname:port Number:databaseName
DB2 COM.ibm.db2.jdbc.net.DB2Driver jdbc:db2:hostname:port Number/databaseName
Sybase com.sybase.jdbc.SybDriver jdbc:sybase:Tds:hostname: port Number/databaseName
URL 格式所有加粗的部分都是静态的,你需要将剩余部分按照你的数据库实际情况进行设置。
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
关闭 JDBC 连接
//Java 垃圾收集器也会关闭连接,它会完全清除过期的对象。但最好用try finally来关闭连接。
conn.close();
JDBC Statement 对象
Statement 对象
一旦我们获得了数据库的连接,我们就可以和数据库进行交互。JDBC 的 Statement,CallableStatement 和 PreparedStatement 接口定义的方法和属性,可以让你发送 SQL 命令或 PL/SQL 命令到数据库,并从你的数据库接收数据。
下表提供了每个接口的用途概要,根据实际目的决定使用哪个接口。
StatementImpl stmt = new StatementImpl(this.getLoadBalanceSafeProxy(), this.database);
接口 推荐使用
Statement 可以正常访问数据库,适用于运行静态 SQL 语句。 Statement 接口不接受参数。
PreparedStatement 计划多次使用 SQL 语句, PreparedStatement 接口运行时接受输入的参数。
CallableStatement 适用于当你要访问数据库存储过程的时候, CallableStatement 接口运行时也接受输入的参数。
当你创建了一个 Statement 对象之后,你可以用它的三个执行方法的任一方法来执行 SQL 语句。
- boolean execute(String SQL) : 如果 ResultSet 对象可以被检索,则返回的布尔值为 true ,否则返回 false 。当你需要使用真正的动态 SQL 时,可以使用这个方法来执行 SQL DDL 语句。
- int executeUpdate(String SQL) : 返回执行 SQL 语句影响的行的数目。使用该方法来执行 SQL 语句,是希望得到一些受影响的行的数目,例如,INSERT,UPDATE 或 DELETE 语句。
- ResultSet executeQuery(String SQL) : 返回一个 ResultSet 对象。当你希望得到一个结果集时使用该方法,就像你使用一个 SELECT 语句。
关闭 Statement 对象
正如你关闭一个 Connection 对象来节约数据库资源,出于同样的原因你也应该关闭 Statement 对象。
简单的调用 close() 方法就可以完成这项工作。如果你关闭了 Connection 对象,那么它也会关闭 Statement 对象。然而,你应该始终明确关闭 Statement 对象,以确保真正的清除。
PreparedStatement 对象
PreparedStatement 接口扩展了 Statement 接口,它让你用一个常用的 Statement 对象增加几个高级功能。
JDBC 中所有的参数都被用 ? 符号表示,这是已知的参数标记。在执行 SQL 语句之前,你必须赋予每一个参数确切的数值。
setXXX() 方法将值绑定到参数,其中 XXX 表示你希望绑定到输入参数的 Java 数据类型。如果你忘了赋予值,你将收到一个 SQLException。
每个参数标记映射它的序号位置。第一标记表示位置 1 ,下一个位置为 2 .
prepareStatement对象防止sql注入的方式是把用户非法输入的单引号用反斜杠做了转义,从而达到了防止sql注入的目的 。
实例:
public class JDBCTest2 {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"13565422554");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update :"+execute);
//STEP 4: Execute a query
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通过列名获取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs.close();
//关闭 PreparedStatement 对象
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
输出:
execute update :1
id: 2name: 艾米哈珀dept: amy empirephone: 13565422554
over !
CallableStatement 对象
CallableStatement是创建被用来执行调用数据库存储过程的 CallableStatement 对象。
三种类型的参数有:IN,OUT 和 INOUT。PreparedStatement 对象只使用 IN 参数。CallableStatement 对象可以使用所有的三个参数。
这里是每个参数的定义:
参数 描述
IN 在 SQL 语句创建的时候该参数是未知的。你可以用 setXXX() 方法将值绑定到IN参数中。
OUT 该参数由 SQL 语句的返回值提供。你可以用 getXXX() 方法获取 OUT 参数的值。
INOUT 该参数同时提供输入输出的值。你可以用 setXXX() 方法将值绑定参数,并且用 getXXX() 方法获取值。
前提:
存储过程
DELIMITER $$
DROP PROCEDURE IF EXISTS `hello_mybatis`.`getNameById` $$
CREATE PROCEDURE `hello_mybatis`.`getNameById`
(IN USER_ID INT, OUT USER_NAME varchar(64))
BEGIN
SELECT NAME INTO USER_NAME
FROM t_user
WHERE ID = USER_ID;
END $$
DELIMITER ;
示例:
public class JDBCTest3 {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
CallableStatement cstmt= null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//注意,不管是入参出参都有有?占位符,否则会执行失败
String sql ="{call getNameById (?,?)}";
cstmt = conn.prepareCall(sql);
cstmt.setInt("USER_ID",2);
cstmt.registerOutParameter("USER_NAME",Types.VARCHAR);
//也可以根据列的索引,1,2这样进行设置参数
cstmt.execute();
String userName = cstmt.getString("USER_NAME");
System.out.println("CallableStatement userName: "+userName);
} catch (SQLException e) {
e.printStackTrace();
} finally {
cstmt.close();
conn.close();
}
System.out.println("over !");
}
}
输出:
CallableStatement userName: 艾米哈珀
over !
报错:
java.sql.SQLException: Can't find local placeholder mapping for parameter named "USER_NAME".
这是因为执行存储过程时,不管是入参出参都有有?占位符,否则会执行失败
//错误
String sql ="{call getNameById (?)}";
//正确
String sql ="{call getNameById (?,?)}";
CallableStatement参考
JDBC 结果集
SQL 语句从数据库查询中获取数据,并将数据返回到结果集中。SELECT 语句是一种标准的方法,它从一个数据库中选择行记录,并显示在一个结果集中。 java.sql.ResultSet 接口表示一个数据库查询的结果集。
一个 ResultSet 对象控制一个光标指向当前行的结果集。术语“结果集”是指包含在 ResultSet 对象中的行和列的数据。
ResultSet 接口的方法可细分为三类-
- 导航方法:用于移动光标。
- 获取方法:用于查看当前行被光标所指向的列中的数据。
- 更新方法:用于更新当前行的列中的数据。这些更新也会更新数据库中的数据。
光标的移动基于 ResultSet 的属性。用相应的语句生成 ResultSet 对象时,同时生成 ResultSet 的属性。
JDBC 提供了连接方法通过下列创建语句来生成你所需的 ResultSet 对象:
- createStatement(int RSType, int RSConcurrency);
- prepareStatement(String SQL, int RSType, int RSConcurrency);
- prepareCall(String sql, int RSType, int RSConcurrency);
第一个参数表示 ResultSet 对象的类型,第二个参数是两个 ResultSet 常量之一,该常量用于判断该结果集是只读的还是可修改的。
ResultSet 的类型
可能的 RSType 如下所示。如果你不指定 ResultSet 类型,将自动获得的值是 TYPE_FORWARD_ONLY。
类型 描述
ResultSet.TYPE_FORWARD_ONLY 光标只能在结果集中向前移动。
ResultSet.TYPE_SCROLL_INSENSITIVE 光标可以向前和向后移动。当结果集创建后,其他人对数据库的操作不会影响结果集的数据。
ResultSet.TYPE_SCROLL_SENSITIVE. 光标可以向前和向后移动。当结果集创建后,其他人对数据库的操作会影响结果集的数据。
ResultSet 的并发性
RSConcurrency 的值如下所示,如果你不指定并发类型,将自动获得的值是 CONCUR_READ_ONLY。
并发性 描述
ResultSet.CONCUR_READ_ONLY 创建一个只读结果集,这是默认的值。
ResultSet.CONCUR_UPDATABLE 创建一个可修改的结果集。
//默认初始化一个 Statement 对象来创建一个只能前进,而且只读的 ResultSet 对象。
conn.createStatement();
在 ResultSet 接口中包括如下几种方法涉及移动光标:
S.N. 方法 & 描述
1 public void beforeFirst() throws SQLException将光标移动到第一行之前。
2 public void afterLast() throws SQLException将光标移动到最后一行之后。
3 public boolean first() throws SQLException将光标移动到第一行。
4 public void last() throws SQLException将光标移动到最后一行。
5 public boolean absolute(int row) throws SQLException将光标移动到指定的第 row 行。
6 public boolean relative(int row) throws SQLException将光标移动到当前指向的位置往前或往后第 row 行的位置。
7 public boolean previous() throws SQLException将光标移动到上一行,如果超过结果集的范围则返回 false。
8 public boolean next() throws SQLException将光标移动到下一行,如果是结果集的最后一行则返回 false。
9 public int getRow() throws SQLException返回当前光标指向的行数的值。
10 public void moveToInsertRow() throws SQLException将光标移动到结果集中指定的行,可以在数据库中插入新的一行。当前光标位置将被记住。
11 public void moveToCurrentRow() throws SQLException如果光标处于插入行,则将光标返回到当前行,其他情况下,这个方法不执行任何操作。
查看结果集
ResultSet接口中含有几十种从当前行获取数据的方法。
每个可能的数据类型都有一个 get 方法,并且每个 get 方法有两个版本-
- 一个需要列名。
- 一个需要列的索引。
例如,如果你想查看的列包含一个 int 类型,你需要在 ResultSet 中调用 getInt()方法-
S.N. 方法 & 描述
1 public int getInt(String columnName) throws SQLException返回当前行中名为 columnName 的列的 int 值。
2 public int getInt(int columnIndex) throws SQLException返回当前行中指定列的索引的 int 值。列索引从 1 开始,意味着行中的第一列是 1 ,第二列是 2 ,以此类推。
同样的,在 ResultSet 接口中还有获取八个 Java 原始类型的 get 方法,以及常见的类型。
更新的结果集(一般不这样更新,了解)
ResultSet 接口包含了一系列的更新方法,该方法用于更新结果集中的数据。
用 updateString 方法可以有两个更新方法来更新任一数据类型-
- 一个需要列名。
- 一个需要列的索引。
例如,要更新一个结果集的当前行的 String 列,你可以使用任一如下所示的 updateString()方法-
S.N. 方法 & 描述
1 public void updateString(int columnIndex, String s) throws SQLException将指定列的字符串的值改为 s。
2 public void updateString(String columnName, String s) throws SQLException类似于前面的方法,不同之处在于指定的列是用名字来指定的,而不是它的索引。
八个原始数据类型都有其更新方法,比如 String,Object,URL,和在 java.sql 包中的 SQL 数据类型。
更新结果集中的行将改变当前行的列中的 ResultSet 对象,而不是基础数据库中的数据。要更新数据库中一行的数据,你需要调用以下的任一方法。
S.N. 方法 & 描述
1 public void updateRow()通过更新数据库中相对应的行来更新当前行。
2 public void deleteRow()从数据库中删除当前行。
3 public void refreshRow()在结果集中刷新数据,以反映数据库中最新的数据变化。
4 public void cancelRowUpdates()取消对当前行的任何修改。
5 public void insertRow()在数据库中插入一行。本方法只有在光标指向插入行的时候才能被调用。
当使用ResultSet的时候,当查询出来的数据集记录很多,有一千万条的时候,那rs所指的对象是否会占用很多内存,如果记录过多,那程序会不会把系统的内存用光呢 ?
不会,ResultSet表面看起来是一个记录集,其实这个对象中只是记录了结果集的相关信息,具体的记录并没有存放在对象中,具体的记录内容直到你通过next方法提取的时候,再通过相关的getXXXXX方法提取字段内容的时候才能从数据库中得到,这些并不会占用内存,具体消耗内存是由于你将记录集中的数据提取出来加入到你自己的集合中的时候才会发生,如果你没有使用集合记录所有的记录就不会发生消耗内存厉害的情况。
JDBC 数据类型
JDBC 驱动程序在将 Java 数据类型发送到数据库之前,会将其转换为相应的 JDBC 类型。对于大多数数据类型都采用了默认的映射关系。例如,一个 Java int 数据类型转换为 SQL INTEGER。通过默认的映射关系来提供驱动程序之间的一致性。
ResultSet 对象为任一数据类型提供相应的 getXXX()方法,该方法可以获取任一数据类型的列值。上述任一方法的使用需要列名或它的顺序位置。
SQL JDBC/Java setXXX getXXX
VARCHAR java.lang.String setString getString
CHAR java.lang.String setString getString
LONGVARCHAR java.lang.String setString getString
BIT boolean setBoolean getBoolean
NUMERIC java.math.BigDecimal setBigDecimal getBigDecimal
TINYINT byte setByte getByte
SMALLINT short setShort getShort
INTEGER int setInt getInt
BIGINT long setLong getLong
REAL float setFloat getFloat
FLOAT float setFloat getFloat
DOUBLE double setDouble getDouble
VARBINARY byte[ ] setBytes getBytes
BINARY byte[ ] setBytes getBytes
DATE java.sql.Date setDate getDate
TIME java.sql.Time setTime getTime
TIMESTAMP java.sql.Timestamp setTimestamp getTimestamp
CLOB java.sql.Clob setClob getClob
BLOB java.sql.Blob setBlob getBlob
ARRAY java.sql.Array setARRAY getARRAY
REF java.sql.Ref SetRef getRef
STRUCT java.sql.Struct SetStruct getStruct
处理 NULL 值
SQL 使用 NULL 值和 Java 使用 null 是不同的概念。那么,你可以使用三种策略来处理 Java 中的 SQL NULL 值-
- 避免使用返回原始数据类型的 getXXX()方法。
- 使用包装类的基本数据类型,并使用 ResultSet 对象的 wasNull()方法来测试收到 getXXX()方法返回的值是否为 null,如果是 null,该包装类变量则被设置为 null。
- 使用原始数据类型和 ResultSet 对象的 wasNull()方法来测试通过 getXXX()方法返回的值,如果是 null,则原始变量应设置为可接受的值来代表 NULL。
JDBC 事务
如果你的 JDBC 连接是处于自动提交模式下,该模式为默认模式,那么每句 SQL 语句都是在其完成时提交到数据库。
对简单的应用程序来说这种模式相当好,但有三个原因你可能想关闭自动提交模式,并管理你自己的事务-
- 为了提高性能
- 为了保持业务流程的完整性
- 使用分布式事务
JDBC 驱动程序默认使用的自动提交模式 ,需要conn.setAutoCommit(false);关闭自动提交模式 .
提交和回滚
conn.commit( );
conn.rollback( );
还原点
Connection 对象有两个新的方法来管理还原点-
- setSavepoint(String savepointName): 定义了一个新的还原点。它也返回一个 Savepoint 对象。
- releaseSavepoint(Savepoint savepointName): 删除一个还原点。请注意,它需要一个作为参数的 Savepoint 对象。这个对象通常是由 setSavepoint() 方法生成的一个还原点。
有一个 rollback (String savepointName) 方法,该方法可以回滚到指定的还原点。
示例:
public class JDBCTranctionTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
Savepoint savepoint = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//关闭自动提交模式
conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
//想回滚到还原点,那么之前执行的sql要提交事务,否则这个还原点就没有意义,因为一整个事务都没有提交
conn.commit();
//创建还原点
savepoint = conn.setSavepoint("updateSavepoint1");
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,2);
execute = pstmtUp.executeUpdate();
System.out.println("execute update2 :"+execute);
//报错
//System.out.println(1/0);
//STEP 4: Execute a query
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通过列名获取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
//提交事务
conn.commit();
//当我们设置不提交时,虽然查出来的是最新更改的值,但数据库其实并没有更改,因为我们没有手动提交
//查出来的是最新更改的值是事务隔离类型的原因
} catch (SQLException e) {
//回滚事务
//conn.rollback();
//回滚到还原点
e.printStackTrace();
conn.rollback(savepoint);
} finally {
//关闭 PreparedStatement 对象
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
注意:还原点使用特别注意。
//想回滚到还原点,那么之前执行的sql要提交事务,否则这个还原点就没有意义,因为一整个事务都没有提交
conn.commit();
//创建还原点
savepoint = conn.setSavepoint("updateSavepoint1");
//回滚到还原点
conn.rollback(savepoint);
JDBC 批处理
Statement不能执行带参数占位符?的sql语句。
批处理注意用于增删改,因为返回的不是对象。
批处理是指你将关联的 SQL 语句组合成一个批处理,并将他们当成一个调用提交给数据库。
当你一次发送多个 SQL 语句到数据库时,可以减少通信的资源消耗,从而提高了性能。
- JDBC 驱动程序不一定支持该功能。你可以使用 DatabaseMetaData.supportsBatchUpdates() 方法来确定目标数据库是否支持批处理更新。如果你的JDBC驱动程序支持此功能,则该方法返回值为 true。
- Statement,PreparedStatement 和 CallableStatement 的 addBatch() 方法用于添加单个语句到批处理。
- executeBatch() 方法用于启动执行所有组合在一起的语句。
- executeBatch() 方法返回一个整数数组,数组中的每个元素代表了各自的更新语句的更新数目。
- 正如你可以添加语句到批处理中,你也可以用 clearBatch() 方法删除它们。此方法删除所有用 addBatch() 方法添加的语句。但是,你不能有选择性地选择要删除的语句。
批处理和 Statement 对象
注意可以不手动控制事务。
使用 Statement 对象来使用批处理所需要的典型步骤如下所示-
- 使用 createStatement() 方法创建一个 Statement 对象。
- 使用 setAutoCommit() 方法将自动提交设为 false。
- 被创建的 Statement 对象可以使用 addBatch() 方法来添加你想要的所有SQL语句。
- 被创建的 Statement 对象可以用 executeBatch() 将所有的 SQL 语句执行。
- 最后,使用 commit() 方法提交所有的更改。
批处理和 PrepareStatement 对象
使用 prepareStatement 对象来使用批处理需要的典型步骤如下所示-
- 使用占位符创建 SQL 语句。
- 使用任一 prepareStatement() 方法创建 prepareStatement 对象。
- 使用 setAutoCommit() 方法将自动提交设为 false。
- 被创建的 Statement 对象可以使用 addBatch() 方法来添加你想要的所有 SQL 语句。
- 被创建的 Statement 对象可以用 executeBatch() 将所有的 SQL 语句执行。
- 最后,使用 commit() 方法提交所有的更改。
示例:
public class JDBCBatchTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main1(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
PreparedStatement pstmtUp = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//关闭自动提交模式
//conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
pstmtUp.addBatch();
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,1);
pstmtUp.addBatch();
pstmtUp.setString(1,"22027777");
pstmtUp.setInt(2,3);
pstmtUp.addBatch();
int[] ints = pstmtUp.executeBatch();
System.out.println(Arrays.toString(ints));
//提交事务
//conn.commit();
//当我们设置不提交时,虽然查出来的是最新更改的值,但数据库其实并没有更改,因为我们没有手动提交
//查出来的是最新更改的值是事务隔离类型的原因
} catch (Exception e) {
//回滚事务
//conn.rollback();
//回滚到还原点
e.printStackTrace();
} finally {
//关闭 PreparedStatement 对象
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
Connection conn = null;
Statement stmt = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//关闭自动提交模式
//conn.setAutoCommit(false);
//Statement只能执行不带参数的sql
stmt = conn.createStatement();
String sql;
sql = "UPDATE t_user SET phone = '123456' WHERE id = 1";
stmt.addBatch(sql);
sql = "UPDATE t_user SET phone = '123456' WHERE id = 2";
stmt.addBatch(sql);
sql = "UPDATE t_user SET phone = '123456' WHERE id = 3";
stmt.addBatch(sql);
//批量处理
int[] ints = stmt.executeBatch();
System.out.println(Arrays.toString(ints));
//提交事务
//conn.commit();
//当我们设置不提交时,虽然查出来的是最新更改的值,但数据库其实并没有更改,因为我们没有手动提交
//查出来的是最新更改的值是事务隔离类型的原因
} catch (Exception e) {
//回滚事务
//conn.rollback();
//回滚到还原点
e.printStackTrace();
} finally {
//关闭 PreparedStatement 对象
stmt.close();
conn.close();
}
System.out.println("over !");
}
}
JDBC 流数据
PreparedStatement 对象必须具备使用输入和输出流来提供参数数据的能力。这使你能够将整个文件存储到数据库列中,这样数据库就能存储大型数据,例如 CLOB 和 BLOB 数据类型。
用于流数据有下列几种方法-
- setAsciiStream(): 该方法是用来提供较大的 ASCII 值。
- setCharacterStream(): 该方法是用来提供较大的 UNICODE 值。
- setBinaryStream(): 该方法是用来提供较大的二进制值。
setXXXStream()方法需要一个额外的参数,该参数是除了参数占位符的文件大小。这个参数通知驱动程序通过使用流有多少数据被发送到数据库中。
一般不会用这个,都是直接把数据转为String类型直接存到text字段里吧?
JDBC 创建数据库实例
stmt = conn.createStatement();
String sql = "CREATE DATABASE hello_spring";
stmt.executeUpdate(sql);
JDBC 删除数据库实例
使用的用户必须拥有删除数据库的权限,这也侧面反映当我们给项目设置数据库访问用户时,要注意权限的赋予,像数据库操作、DDL这样的权限是不应该有的。
stmt = conn.createStatement();
String sql = "DROP DATABASE STUDENTS";
stmt.executeUpdate(sql);
JDBC 创建表实例
stmt = conn.createStatement();
String sql = "CREATE TABLE REGISTRATION " +
"(id INTEGER not NULL, " +
" first VARCHAR(255), " +
" last VARCHAR(255), " +
" age INTEGER, " +
" PRIMARY KEY ( id ))";
stmt.executeUpdate(sql);
System.out.println("Create
JDBC 删除表实例
stmt = conn.createStatement();
String sql = "DROP TABLE REGISTRATION ";
stmt.executeUpdate(sql);
JDBC 插入记录实例
下面这个示例在项目运用中其实应该使用PreparedStatement来插入数据,用占位符的形式来创建sql。
pstmtUp = conn.prepareStatement("INSERT INTO Registration " +
"VALUES (?, ?, ?, ?)");
stmt = conn.createStatement();
String sql = "INSERT INTO Registration " +
"VALUES (100, 'Zara', 'Ali', 18)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES (101, 'Mahnaz', 'Fatma', 25)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES (102, 'Zaid', 'Khan', 30)";
stmt.executeUpdate(sql);
sql = "INSERT INTO Registration " +
"VALUES(103, 'Sumit', 'Mittal', 28)";
stmt.executeUpdate(sql);
JDBC 查询记录实例
pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
pstmt.setInt(1,2);
pstmt.setString(2,"艾米哈珀");
rs = pstmt.executeQuery();
while (rs.next()) {
//Retrieve by column name 通过列名获取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
JDBC 更新记录实例
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
JDBC 删除记录实例
应该使用PreparedStatement来删除数据,用占位符的形式来创建sql。
stmt = conn.createStatement();
String sql = "DELETE FROM Registration " +
"WHERE id = 101";
stmt.executeUpdate(sql);
JDBC LIKE 子句实例
LIKE查询推荐使用:%放在占位符中或使用concat函数。
1:%放在占位符中
parameters.add("%"+familyMemberQueryBean.getFullName()+"%");
sql+=" and t.full_name like ?";
2:使用concat函数
parameters.add(familyMemberQueryBean.getFullName());
sql+=" and t.full_name like concat('%',?,'%')";
3:使用转义字符,百分号直接写在sql语句中
parameters.add(familyMemberQueryBean.getFullName());
sql+=" and t.full_name like "%"?"%"";
直接查询的SQL语句如下:SELECT id,full_name,email,phone,remark FROM family_member t WHERE 1 = 1
AND t.full_name LIKE "%"'李'"%" AND t.email LIKE "%"'qq'"%"
4:直接拼接SQL
sql+=" and t.full_name like '%"+familyMemberQueryBean.getFullName()+"%'";
参考:JDBC模糊查询的4种方式
示例:
public class JDBCTranctionTest {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/hello_mybatis";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//STEP 2: Register JDBC driver
Class.forName(JDBC_DRIVER);
ResultSet rs = null;
Connection conn = null;
PreparedStatement pstmt = null;
PreparedStatement pstmtUp = null;
Savepoint savepoint = null;
try {
//STEP 3: Open a connection
//conn = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);
//使用数据库 URL 和 Properties 对象
Properties info = new Properties();
info.put("user", USER);
info.put("password", PASSWORD);
conn = DriverManager.getConnection(DATABASE_URL, info);
//关闭自动提交模式
conn.setAutoCommit(false);
pstmtUp = conn.prepareStatement("UPDATE t_user SET phone = ? WHERE id = ?");
pstmtUp.setString(1,"22025555");
pstmtUp.setInt(2,2);
int execute = pstmtUp.executeUpdate();
System.out.println("execute update1 :"+execute);
//想回滚到还原点,那么之前执行的sql要提交事务,否则这个还原点就没有意义,因为一整个事务都没有提交
conn.commit();
//创建还原点
savepoint = conn.setSavepoint("updateSavepoint1");
pstmtUp.setString(1,"22026666");
pstmtUp.setInt(2,2);
execute = pstmtUp.executeUpdate();
System.out.println("execute update2 :"+execute);
//报错
//System.out.println(1/0);
//STEP 4: Execute a query
//pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name = ?");
//JDBC模糊查询的4种方式
//1、%放在占位符中
/* pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like ? ");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,"%" + userName + "%");
rs = pstmt.executeQuery();*/
//2、使用concat函数
/* pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like concat('%',?,'%')");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,userName);
rs = pstmt.executeQuery();*/
//3、使用转义字符,百分号直接写在sql语句中
/*pstmt = conn.prepareStatement("SELECT id, name, dept, phone FROM t_user WHERE id = ? and name like "%"?"%"");
pstmt.setInt(1,1);
String userName = "青";
pstmt.setString(2,userName);
rs = pstmt.executeQuery();*/
//4、直接拼接SQL
Statement stmt = conn.createStatement();
String sql ="SELECT id, name, dept, phone FROM t_user WHERE id = 1 and name like '%青%' ";
rs = stmt.executeQuery(sql);
while (rs.next()) {
//Retrieve by column name 通过列名获取值
int id = rs.getInt("id");
String name = rs.getString("name");
String dept = rs.getString("dept");
String phone = rs.getString("phone");
//Display values
System.out.println("id: " + id + "name: " + name + "dept: " + dept + "phone: " + phone);
}
//提交事务
conn.commit();
//当我们设置不提交时,虽然查出来的是最新更改的值,但数据库其实并没有更改,因为我们没有手动提交
//查出来的是最新更改的值是事务隔离类型的原因
} catch (Exception e) {
//回滚事务
//conn.rollback();
//回滚到还原点
e.printStackTrace();
conn.rollback(savepoint);
} finally {
//关闭 PreparedStatement 对象
pstmt.close();
pstmtUp.close();
conn.close();
}
System.out.println("over !");
}
}
JDBC 排序实例
sql语句加入 order by column desc 或 order by column asc;
stmt = conn.createStatement();
String sql = "SELECT id, first, last, age FROM Registration" +
" ORDER BY first ASC";
ResultSet rs = stmt.executeQuery(sql);
疑问:
Q:ResultSet 这个结果集是个怎么样的结构,为什么要这样设计,内部是什么容器?
ResultSet rs = stmt.executeQuery(sql);
Q:PreparedStatement计划多次使用 SQL 语句的时候使用是什么意思?每条sql都会被多次使用啊?
Q:conn.createStatement();默认初始化一个 Statement 对象来创建一个只能前进,而且只读的 ResultSet 对象。那为什么可以使用这些前后移动的方法呢?
rs.previous();
rs.absolute(1);
Q:JDBC 流数据还有用到么?什么场景里会用?一般不会用这个,都是直接把数据转为String类型直接存到text字段里吧?