阿特我自己
[email protected]
Hello WvT
13.3 JDBC 的用法
13.3 JDBC 的用法

1. JDBC 编程步骤

1. 加载数据库驱动

通常, 使用反射来加载数据库驱动

例如 Class.forName(driverClass)

MySQL 的 DriverClass 为 “com.mysql.cj.jdbc.Driver”
Oracle 的 DriverClass 为 “oracle.jdbc.driver.OracleDriver”

最新的 JDBC 驱动已经可以通过 SPI 自动注册驱动类了, 可以省略该步骤

2. 通过 DriverManager 获取数据库连接

DriverManager.getConnetction(String url, String user, String password);

数据库 URL 通常遵循以下写法

jdbc:subprotocol:other stuff

MySQL 的 URL 为 “jdbc:mysql://hostname:port/databassename”
Oracle 的 URL 为 “jdbc:oracle:thin:@hostname:port:databasename”

3. 通过 Connection 对象创建 Statement 对象

createStatement(): 创建基本的 Statement 对象
prepareStatement(String sql): 根据传入的 SQL 语句创建预编译的 Statement 对象
prepareCall(String sql): 根据传入的 SQL 语句创建 CallableStatement 对象

4. 使用 Statement 对象执行 SQL 语句

execute(): 可以执行任何 SQL 语句
executeUpdate(): 主要用于执行 DDL 和 DML 语句, 执行 DDL 语句时返回 0, 执行 DML 语句时返回受影响的记录数量
executeQuery(): 执行查询语句, 返回代表查询结果的 ResultSet 对象

executeLargeUpdate(): Java8 增加的方法, 返回的结果为 long 类型, 用于解决受影响记录数量大于 int 类型上限的问题, 目前 MySQL 驱动并不支持该方法

5. 操作结果集

通过 ResultSet 的 next(), previous(), first(), last(), beforeFirst(), afterLast(), absolute() 等方法移动记录指针
通过 getXxx() 方法获取值

6. 回收数据库资源

关闭 ResultSet, Statement, Connection 等资源

2. 执行 SQL 语句的方式

Statemeent

普通的 Statement 几乎可以执行任何 SQL 语句

通过 Connection::createStatement() 创建一个 Statement

接下来使用 Statement::execute(sql) 方法执行一个 SQL 语句, 返回 Boolean 值, 表明执行该 SQL 语句是否返回了 ResultSet 对象

如果执行的是查询语句, 则可通过 getResultSet() 方法获取查询结果

如果执行的是 DML 语句, 则可通过 getUpdateCount() 方法来获取 DML 语句影响的记录行数

PreparedStatement

PreparedStatement 可以使用带占位符 (?) 的 SQL 语句预编译, 可以提升性能并防止 SQL 注入

通过 Connection::preparedStatement(sql) 创建一个 PreparedStatement

通过 PreparedStatement 对象的 setXxx(int index, Xxx value) 方法可以将指定占位符替换为实际值, 值得注意的是 index 是从 1 开始的

接下来使用 execute(), executeUpdate(), executeQuery() 方法即可执行该语句

CallableStatement

CallableStatement 用于调用一个存储过程

在 SQL 中调用存储过程总是以这种格式: “call {过程名(p1, p2, p3…)}”

因此, 通过 Connection::prepareCall(“call{过程名(?, ?, ?…)}”) 即可创建一个预编译的 CallableStatement

然后使用 setXxx() 方法替换占位符

为使 Java 程序能够读取存储过程的传出参数, 需要使用 registerOutParameter(index, Types.Xxx) 方法来注册该参数, index 代表参数的位置, 从 1 开始, Type 是该参数的类型

接下来使用 execute() 方法即可调用该存储过程

在调用成功后, 使用 getXxx(index) 方法即可获取执行传出参数的值

3. 管理结果集

JDBC 使用 ResultSet 来封装执行查询得到的查询结果, 通过移动记录指针来取出内容, 还允许通过 ResultSet 来更新记录

ResultSetMetaData 用于获取 ResultSet 的相关信息

ResultSet 的常用方法

  • void close() : 释放对象
  • boolean absolute(int row) : 将记录指针移动到第 row 行(从 1 开始), 如果 row 是负数, 则移动到倒数第 row 行, 如果移动后指向一条有效记录则返回 true
  • void beforeFirst() : 将记录指针定位到首行之前, 这是 ResultSet 的初始状态
  • boolean first() previous() next() last() : 将记录指针移动到 首行\上一行\下一行\最后一行 如果移动后的记录指针指向一条有效剂量, 则返回 true
  • void afterLast() : 将记录指针定位到最后一行之后

当把记录指针移动到指定行后, 通过如下方法获取数据

  • Xxx getXxx(int columnIndex) : 根据列索引获取当前行 指定列 指定类型的值
  • Xxx getXxx(String columnLabel) : 根据列名获取值
  • int findColumn(String columnLabel) : 根据列名获取列索引

Java 7 提供了如下两个泛型方法, 用于获取任意类型的值

  • <T> T getObject(int columnIndex, Class<T> type)
  • <T> T getObject(String columnLabel, Class<T> type)

1. 可滚动 可更新的结果集

以默认方式打开的 ResultSet 是不可更新的, 在 JDK 1.4 以前还是不可滚动(只可向前移动)的

需要在创建 Statement 时传入额外的参数

resultSetType: 控制 ResultSet 的类型

ResultSet.TYPE_FORWARD_ONLY: 控制记录指针只能向前移动, JDK 1.4 以前默认

ResultSet.TYPE_SCROLL_INSENSITIVE: 控制记录指针可以自由移动, 但底层数据的改变不会影响 ResultSet 的内容

ResultSet.TYPE_SCROLL_SENSITIVE: 控制记录指针可以自由移动, 底层数据的改变会影响 ResultSet 的内容

后两个常量需要数据库支持, 对于某些数据库来说, 这两个常量可能没有区别

resultSetConcurrency: 控制 ResultSet 的并发类型

ResultSet.CONCUR_READ_ONLY: 指示 ResultSet 是只读的并发模式 (默认)

ResultSet.CONCUR_UPDATEABLE: 指示 ResultSet 是可更新的并发模式

例如: conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATEABLE);

注意: 可更新的结果集必须满足如下两个条件

  • 所有数据都应该来自一个表
  • 选出的数据集必须包含主键列

更新记录

程序可以调用 ResultSet 对象的 updateXxx(int columnIndex, Xxx value) 方法修改记录

最后调用 updateRow() 方法提交更改

Java 8 为 ResultSet 添加了如下两个默认方法, 其中 SQLType 用于指定该数据列的类型

  • updateObject(String columnLabel, Object x, SQLType targetSqlType)
  • updateObject(int columnIndex, Object x, SQLType targetSqlType)

MySQL 驱动暂不支持该方法

2. 处理 Blob 类型的数据

Blob(Binary Long Object) 代表一个二进制长对象, 通常用于存储特殊文件, 由于其特殊性, 无法通过普通 SQL 语句完成

为了处理 Blob 类型的数据, 需要使用 PrepareStatement

使用其 setBinaryStream(int parameterIndex, InputSteam x)

可以为指定占位符传入二进制输入流, 进而可以实现将 Blob 数据保存到数据库的功能

当从 ResultSet 中取出数据时, 可以调用其 getBlob(int columIndex): Blob 方法, 该方法返回一个 Blob 对象

通过调用该对象的 getBinaryStream(): InputStream 方法, 可以获取该 Blob 数据的输入流, 也可以直接使用 getBytes() 方法取出二进制 Byte 数组

3. 使用 ResultSetMetaData 分析结果集

程序可能不清楚结果集里包含哪些数据列, 以及每个数据的数据类型, 即可通过 ResultSetMetaData 获取 ResultSet 的相关信息

通过 ResultSet 的 getMetaData() 方法可以获取对应的 ResultSetMetaData

常用的方法有

  • int getColumnCount(): 返回该 ResultSet 的列数量
  • String getColumnName(int column): 返回指定索引的列名
  • int getColumnType(int column): 返回指定索引的列类型

使用 ResultSetMetaData 需要一定的系统开销

4. 使用 RowSet 1.1 包装结果集

RowSet 接口继承了 ResultSet 接口, 包含 JdbcRowSet, CachedRowSet, FilteredRowSet, JoinRowSetWebRowSet 子接口

JdbcRowSet 需要保持与数据库的连接, 其余都是离线的 RowSet

RowSet 默认是可滚动 可更新 可序列化的结果集, 可以作为 JavaBean 使用

离线 RowSet 在创建时把数据从数据库读到了内存, 能充分利用计算机的内存, 提升性能

1. RowSetFactory 与 RowSet

java 7 新增的 RowSetProvider 类用于创建 RowSetFactory 接口的实例, 使用 newFactory() 静态方法

RowSetFactory 接口有如下方法用于创建 RowSet 实例

  • CachedRowSet createCachedRowSet(): 创建一个默认的 CachedRowSet
  • FilteredRowSet createFilteredRowSet(): 创建一个默认的 FIlteredRowSet
  • JdbcRowSet createJdbcRowSet(): 创建一个默认的 JdbcRowSet
  • JoinRowSet createJoinRowSet(): 创建一个默认的 JoinRowSet
  • WebRowSet createWebRowSet(): 创建一个默认的 WebRowSet

接下来需要为 RowSet 设置信息

  • setUrl(String url): 设置要访问的数据库 URL
  • setUsername(String name): 设置访问数据库使用的用户名
  • setPassword(String password): 设置访问数据库使用的密码
  • setCommand(String sql): 设置使用该 sql 语句的查询结果来装填该 RowSet

最后, 通过 execute() 执行查询

接下来即可使用 ResultSet 的方法对该 ResultRow 进行滚动修改了

2. 离线 RowSet

通过 Statement 查询获得一个 ResultSet

接下来通过 RowSetFactory 创建一个 CachedRowSet 的实例

使用 CachedRowSetpopulate() 方法可以包装一个 ResultSet

此时 CachedRowSet 会将 ResultSet 的数据全部读入内存以离线使用

3. 离线 RowSet 的查询分页

如果 SQL 查询返回的记录过大, CachedRowSet 将会占用大量的内存

为解决该问题, CachedRowSet 提供了分页功能, 所谓分页就是一次只装载 ResultSet 的某些记录

CachedRowSet 提供了如下方法来控制分页

  • populate(ResultSet rs, int startRow) : 使用给定的 ResultRow 状态 RowSet, 从 ResultSet 的第 startRow 条记录开始装填
  • setPageSize(int pageSize): 设置 CachedRowSet 的页大小, 即每次返回多少条记录
  • previousPage(): 在底层 ResultSet 可用的情况下, 让 CachedRowSet 读取上一页记录
  • nextPage(): 在底层 ResultSet 可用的情况下, 让 CachedRowSet 读取下一页记录

5. 事务处理

1. MySQL 事务支持

事务是由一步或几步数据库操作序列组成的逻辑执行单元, 这些单元要么全部执行, 要么全部不执行

MySQL 默认关闭事务, 即打开了自动提交, 为了开启 MySQL 事务支持, 可以显示调用如下命令

SET AUTOCOMMIT = 0

该命令仅在当前 Session 有效

如果想临时开启事务, 可以使用 start transactionbegin 命令

在进行一系列操作后, 使用 commit 命令提交更改, 或使用 rollback 指令回滚到之前的状态

使用 savepoing [name] 指令可以设置事务的保存点

一旦设置了保存点后, 即可通过 rollback name 回滚到保存点

2. JDBC 事务支持

JDBC 的事务支持由 Connection 对象提供, 有如下方法, Connection 默认关闭事务

  • setAutoCommit(boolean): 设置是否自动提交
  • commit(): 提交事务
  • SavePoint setSavePoint()
    SavePoint setSavePoint(String name): 设置事务保存点
  • rollback()
    rollback(Savepoint savepoint): 回滚事务

3. 使用批量更新

批量更新可以将多条 SQL 语句作为一批操作同时被收集, 并同时提交

批量更新必须得到底层数据库支持, 可以调用 DatabaseMetaData 的 supportsBatchUpdates() 方法来查看

使用批量更新也需要创建 Statement 对象, 使用该对象的 addBatch(sql) 方法将多条 SQL 语句收集起来

接着, 使用 executeBatch() 或 executeLargeBatch() 方法提交批量更新

该方法将返回一个 long/int[] 数组, 分别对应每条 DDL, DML 语句的返回值

如果在批量更新中添加了查询语句, 程序将直接出现错误

6. 分析数据库信息

程序有时需要动态地获取数据库的相关信息, 如数据表信息, 列信息, 底层数据库的特殊功能等

1. 使用 DatabaseMetaData 分析数据库信息

通过 Connection 提供的 getMetaData() 方法获取数据库对应的 DatabaseMetaData 对象

该对象提供了许多方法用于查看数据库信息

例如 supportsXxx() 系列方法用于查询数据库是否支持该功能
getTables() 用于查询数据库的表 getColumns() 用于查询列 等等

很多方法都需要传入一个 xxxPattern 模式字符串, 它是 SQL 里的模式字符串, 用 % 表示任意多个字符, 用 _ 表示单个字符
通常情况下, 如果设置为 null, 则表明该参数不作为过滤条件

2. 使用系统表分析数据库信息

如果已经确定应用程序所使用的数据库系统, 则可以通过系统表来分析信息

系统表又称为数据字典, 通常由数据库系统负责维护, 通常是只读的

几乎所有数据库都会提供系统表供用户查询

MySQL 使用 information_schema 数据库来保存系统表, 常用系统表如下

  • tables: 存放数据库里所有数据表的信息
  • schemata: 存放数据库里所有数据库 (与 MySQL 的 Schema 对应) 的信息
  • views: 存放数据库里所有视图的信息
  • columns: 存放数据库里所有列的信息
  • triggers: 存数据库里所有触发器的信息
  • routines: 存放数据库里所有存储过程和函数的信息
  • key_column_usage: 存放数据库里所有具有约束的键信息
  • table_constraints: 存放数据库里全部约束的表信息
  • statistics: 存放数据库里全部索引的信息

赞赏

发表评论

textsms
account_circle
email

Hello WvT

13.3 JDBC 的用法
1. JDBC 编程步骤 1. 加载数据库驱动 通常, 使用反射来加载数据库驱动 例如 Class.forName(driverClass) MySQL 的 DriverClass 为 "com.mysql.cj.jdbc.Driver" Oracle 的 Driver…
扫描二维码继续阅读
2019-10-15


没有激活的小工具