ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java JDBC [打印本页]

作者: 河曲智叟    时间: 2023-12-4 23:47
标题: Java JDBC
JDBC 基本用法

常用接口和类简介

DriverManager 类

用于管理 JDBC 驱动的服务类。程序中使用该类的主要功能是获取 Connection 对象
Connection

代表数据库连接对象, 每个Connection 代表一个物理连接会话
该接口常用的方法如下
控制事务的方法
Statement

用于执行 SQL 语句的工具接口。可用于执行 DDL,DCL,DML 语句,也可用于执行SQL 查询。
常用方法如下
PreparedStatement

预编译的 Statement 对象。PreparedStatement 是 Statement 的子接口,它允许数据库预编译SQL 语句,以后每次只改变SQL 命令的参数,避免数据库每次都需要编译 SQL 语句,因此性能更好。
相对于 Statement 而言,使用 PreparedStatement 执行SQL 语句时,无需再传入 SQL 语句,只要为预编译的SQL 语句传入参数值即可。
所以它比 Statement 多了如下方法
ResultSet

结果集对象。该对象包含访问查询结果的方法,ResultSet 可以通过列索引或列名获取列数据。
它包含了如下常用方法来移动记录指针
当把记录指针移动到指定行之后,ResultSet 可通过getXxx(int columnIndex) 或 getXxx(String columnLable) 来获取当前行、指定列的值
JDBC 编程步骤

  1. <dependency>
  2.     <groupId>mysql</groupId>
  3.     <artifactId>mysql-connector-java</artifactId>
  4.     <version>8.0.28</version>
  5.     <scope>runtime</scope>
  6. </dependency>
复制代码
当使用DriverManger 获取数据库连接时,通常需要传入三个参数:数据库URL、用户名、密码
URL 通常遵循如下写法
  1. jdbc:subprotocol:other stuff
复制代码
上述写法中 jdbc 是固定的,而subprotocol 指定连接到特定数据库的驱动,后面other stuff则不是固定的,不同数据库的URL写法可能存在较大差异
Mysql的URL写法
  1. jdbc:mysql://hostname:port/databasename
复制代码
Oracle 数据库的URL写法
  1. jdbc:oracle:thin:@hostname:port:databasename
复制代码
代码示例
  1. public class Main {
  2.     public static void main(String[] args) throws Exception {
  3.         try (
  4.                 // 使用DriverManager 获取数据库连接
  5.                 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  6.                 // 使用 Connection 来创建一个 Statement对象
  7.                 Statement statement = conn.createStatement();
  8.                 // 执行SQL 查询语句 拿到结果集
  9.                 ResultSet rs = statement.executeQuery("select * from students");
  10.         ) {
  11.             //  遍历结果集 输出打印结果
  12.             while (rs.next()) {
  13.                 System.out.print(rs.getInt(1) + "\t");
  14.                 System.out.print(rs.getString(2) + "\t");
  15.                 System.out.print(rs.getInt(3) + "\t");
  16.                 System.out.println(rs.getString(4) + "\t");
  17.             }
  18.         }
  19.     }
  20. }
复制代码
输出
  1. 1        小红        23        女       
  2. 2        小兰        22        女       
  3. 3        小鹏        20        男       
  4. 4        小绿        21        男       
  5. 5        小花        22        女       
  6. 6        小强        24        男       
  7. 7        小五        23        男       
复制代码
执行SQL 语句的方式

使用 executeUpdate 方法执行 DDL 和 DML 语句

简单封装一个DBUtil 类
  1. public class DBUtil {
  2.     private String url;
  3.     private String username;
  4.     private String password;
  5.     public DBUtil(String url, String username, String password) {
  6.         this.url = url;
  7.         this.username = username;
  8.         this.password = password;
  9.     }
  10.     // 封装一个执行DDL和DML的方法
  11.     public int execute(String sql) throws SQLException {
  12.         try (
  13.                 Connection conn = DriverManager.getConnection(this.url, this.username, this.password);
  14.                 Statement stmt = conn.createStatement();
  15.         ) {
  16.             // 返回修改行数,如果是DDL则返回0
  17.             return stmt.executeUpdate(sql);
  18.         }
  19.     }
  20.     public void printData(String sql) throws SQLException {
  21.         try (
  22.                 Connection conn = DriverManager.getConnection(this.url, this.username, this.password);
  23.                 Statement stmt = conn.createStatement();
  24.         ) {
  25.             ResultSet rs = stmt.executeQuery(sql);
  26.             while (rs.next()) {
  27.                 System.out.print(rs.getInt(1) + "\t");
  28.                 System.out.println(rs.getString(2) + "\t");
  29.             }
  30.         }
  31.     }
  32.     public static void main(String[] args) throws SQLException {
  33.         DBUtil db = new DBUtil("jdbc:mysql://localhost:3306/test", "root", "123456");
  34.         // 创建表
  35.         db.execute("CREATE TABLE `books`  (\n" +
  36.                 "  `id` int(0) NOT NULL AUTO_INCREMENT,\n" +
  37.                 "  `name` varchar(20) NULL,\n" +
  38.                 "  PRIMARY KEY (`id`)\n" +
  39.                 ");");
  40.         System.out.println("建表成功");
  41.         // 插入一条记录
  42.         int dmlRes = db.execute("INSERT into books (`name`) values("朝花夕拾");");
  43.         System.out.println("修改行数:" + dmlRes);
  44.         // 打印结果
  45.         db.printData("select * from books");
  46.     }
  47. }
复制代码
输出
  1. 建表成功
  2. 修改行数:1
  3. 1        朝花夕拾       
复制代码
使用 execute 方法执行SQL 语句

Statement 的 execute() 方法几乎可以执行任何SQL 语句,根据返回判断是否返回了 ResultSet 对象,再通过getResultSet() 和 getUpdateCount() 获取查询语句返回的ResultSet 对象 或 执行DML 语句返回的影响记录行数
  1. // 判断是否返回ResultSet对象,不是则输出影响记录行数
  2. if (!statement.execute("INSERT into books (`name`) values("朝花夕拾");")) {
  3.     System.out.println("影响记录行数" + statement.getUpdateCount());
  4. }
  5. // 判断是否返回ResultSet对象,是则输出结果
  6. if (statement.execute("select * from students")) {
  7.     try (
  8.         // 获取该Statement 执行查询语句所返回的 ResultSet 对象
  9.         ResultSet rs = statement.getResultSet();
  10.     ) {
  11.         // 遍历输出
  12.         while (rs.next()) {
  13.             System.out.print(rs.getInt(1) + "\t");
  14.             System.out.println(rs.getString(2) + "\t");
  15.         }
  16.     }
  17. }
复制代码
输出
  1. 影响记录行数1
  2. 1        小红       
  3. 2        小兰       
  4. 3        小鹏       
  5. 4        小绿       
  6. 5        小花       
  7. 6        小强       
  8. 7        小五       
复制代码
使用PreparedStatement 执行SQL 语句

PreparedStatement  是 Statement 接口的子接口,使用它可以预编译SQL语句,预编译后的SQL 语句被存储在PreparedStatement 对象中,然后可以高效的执行该语句,还能有效防止SQL 注入
PreparedStatement 使用的SQL字符串 可以包含占位符,例如
  1. insert into students values(null, ?, 1)
复制代码
使用占位符时,PreparedStatement 提供了一系列的 setXxx(int index,Xxx value) 方法来传入参数值。
PreparedStatement 也提供了execute()、executeUpdate()、executeQuery() 三个方法来执行SQL 语句,不过这三个方法无需参数,因为已存储了预编译的SQL 语句
  1. public class PreparedDemo {
  2.     public static void main(String[] args) throws Exception {
  3.         try (
  4.                 // 使用DriverManager 获取数据库连接
  5.                 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  6.                 // 使用 Connection 来创建一个 Statement对象
  7.                 PreparedStatement pstmt = conn.prepareStatement("INSERT into books (`name`) values(?)");
  8.         ) {
  9.             pstmt.setString(1, "西游记");
  10.             pstmt.execute();
  11.         }
  12.     }
  13. }
复制代码
使用 CallableStatement 调用存储过程

创建一个简单的存储过程
  1. CREATE PROCEDURE add_pro(a int,b int, out sum int)
  2. BEGIN
  3. SET sum = a + b;
  4. END;
复制代码
调用存储过程通过 Connection 的 prepareCall() 方法来创建CallableStatement 对象,创建该对象时需要传入调用存储过程 SQL 语句。
调用存储过程的SQL 语句格式:{call 过程名(?,?,?)},其中?为存储过程参数的占位符
  1. conn.prepareCall("{call add_pro(?,?,?)}");
复制代码
存储过程的参数既有传入参数,也有传出参数。 可以通过CallableStatement 的 setXxx() 方法为传入参数设置值;传出参数就是 Java 程序可以通过该参数获取存储过程里的值,需要调用CallableStatement 的registerOutParameter() 方法来注册该参数
  1. cstmt.registerOutParameter(3, Types.INTEGER);
复制代码
代码案例
  1. public class CallableStatementTest {
  2.     public static void main(String[] args) throws Exception {
  3.         try (
  4.                 // 使用DriverManager 获取数据库连接
  5.                 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  6.                 // 调用存储过程,通过占位符传递参数
  7.                 CallableStatement cstmt = conn.prepareCall("{call add_pro(?,?,?)}");
  8.         ) {
  9.             cstmt.setInt(1,12);
  10.             cstmt.setInt(2,22);
  11.             // 注册 out 参数
  12.             cstmt.registerOutParameter(3, Types.INTEGER);
  13.             cstmt.execute();
  14.             System.out.println("执行结果:"+ cstmt.getInt(3));
  15.         }
  16.     }
  17. }
复制代码
输出
  1. 执行结果:34
复制代码
管理结果集

可更新的结果集

以默认方式打开的 ResultSet 是不可更新的,如果希望创建可更新的 ResultSet,则必须在创建Statement 或PreparedStatement 时传入额外的参数。
Connection 在创建Statement 或 PreparedStatement 时可以额外传入以下两个参数
需要指出的是,可更新的结果集还需要满足如下两个条件
ResultSet 提供了如下方法用来修改记录指针所指记录、特定列的值
最后需要调用 updateRow() 方法来提交当前行的修改
  1. public class ResultSetTest {
  2.     public static void main(String[] args) throws Exception {
  3.         try (
  4.                 // 使用DriverManager 获取数据库连接
  5.                 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  6.                 // 使用 Connection 来创建一个 Statement对象,设置结果集可滚动,可更新
  7.                 PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM `students`", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
  8.                 ResultSet rs = pstmt.executeQuery();
  9.         ) {
  10.             while (rs.next()) {
  11.                 // 修改第名称字段的值为 名称+行号
  12.                 rs.updateString("name", rs.getString(2) + rs.getRow());
  13.                 // 提交修改
  14.                 rs.updateRow();
  15.             }
  16.         }
  17.     }
  18. }
复制代码
使用 ResultSetMetaData 分析结果集

ResultSet 里包含一个getMetaData() 方法,该方法返回该ResultSet 对应的 ResultSetMetaData 对象,我们可以通过 ResultSetMetaData 对象来获取 ResultSet 里包含了哪些数据列,以及每个数据列的数据类型
常用的方法有如下三个
  1. public static void main(String[] args) throws Exception {
  2.     try (
  3.         Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
  4.         Statement statement = conn.createStatement();
  5.         ResultSet rs = statement.executeQuery("select * from students");
  6.     ) {
  7.         ResultSetMetaData metaData = rs.getMetaData();
  8.         // 获取列数量
  9.         int columns_num = metaData.getColumnCount();
  10.         for (int i = 0; i < columns_num; i++) {
  11.             // 打印列名
  12.             System.out.print(metaData.getColumnName(i + 1) + "\t");
  13.             // 获取枚举类型 返回值参考 java.sql.Types 类
  14.             System.out.println(metaData.getColumnType(i + 1) + "\t");
  15.         }
  16.     }
  17. }
复制代码
输出
  1. id        4       
  2. name        12       
  3. age        4       
  4. sex        12
复制代码
使用 RowSet 包装结果集

RowSet 接口继承了ResultSet 接口, RowSet 接口下包含JdbcRowSet,CachedRowSet,FilteredRowSet、JoinRowSet 和 WebRowSet 常用子接口,除了JdbcRowSet 需要保持与数据库的连接之外,处于4个子接口都是离线的RowSet,无需保持与数据库的连接

RowSetFactory 与 RowSet

Java 7 新增了 RowSetProvider 类和 RowSetFactory 接口, 其中 RowSetProvider 负责创建 RowSetFactory,而 RowSetFactory 则提供了如下方法来创建 RowSet 实例
下面程序通过 RowSetFactory 示范了使用JdbcRowSet 的可滚动性、可修改特性
  1. public class RowSetTest {
  2.     public static void main(String[] args) throws Exception {
  3.         RowSetFactory factory = RowSetProvider.newFactory();
  4.         try (
  5.                 JdbcRowSet rowSet = factory.createJdbcRowSet();
  6.         ) {
  7.             // 配置连接信息
  8.             rowSet.setUrl("jdbc:mysql://localhost:3306/test");
  9.             rowSet.setUsername("root");
  10.             rowSet.setPassword("123456");
  11.             // 设置 SQL 查询语句
  12.             rowSet.setCommand("select * from students");
  13.             // 执行查询
  14.             rowSet.execute();
  15.             while (rowSet.next()) {
  16.                 System.out.print(rowSet.getInt(1) + "\t");
  17.                 System.out.println(rowSet.getString(2) + "\t");
  18.                 if (rowSet.getInt(1) == 1) {
  19.                     // 修改指定记录行
  20.                     rowSet.updateString(2, "孙悟空");
  21.                     rowSet.updateRow();
  22.                 }
  23.             }
  24.         }
  25.     }
  26. }
复制代码
离线RowSet

使用 离线 RowSet 可以避免Connection 一旦关闭,再通过 ResultSet 访问数据引发异常的情况,离线RowSet直接将底层数据读入内存中,封装成 RowSet 对象,可以当作 Java Bean 使用
CachedRowSet 是所有离线RowSet 的父接口,并且该CachedRowSet 还支持了如下方法来控制分页
  1. public class CachedRowSetTest {
  2.     private String url = "jdbc:mysql://localhost:3306/test";
  3.     private String username = "root";
  4.     private String password = "123456";
  5.     public CachedRowSet query(String sql, int pageSize, int page) throws Exception {
  6.         try (
  7.                 Connection conn = DriverManager.getConnection(url, username, password);
  8.                 Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
  9.                 ResultSet rs = stmt.executeQuery(sql);
  10.         ) {
  11.             RowSetFactory factory = RowSetProvider.newFactory();
  12.             CachedRowSet cachedRowSet = factory.createCachedRowSet();
  13.             // 设置每页记录数量
  14.             cachedRowSet.setPageSize(pageSize);
  15.             // 根据页和每页记录数 计算出从第几条记录开始
  16.             cachedRowSet.populate(rs, (page - 1) * pageSize + 1);
  17.             return cachedRowSet;
  18.         }
  19.     }
  20.     public static void main(String[] args) throws Exception {
  21.         CachedRowSetTest test = new CachedRowSetTest();
  22.         CachedRowSet rs = test.query("select * from students",2,3);
  23.         while (rs.next()){
  24.             System.out.println(rs.getInt(1) + "\t" + rs.getString(2));
  25.         }
  26.     }
  27. }
复制代码
事务处理

JDBC 连接的事务支持由 Connection 提供, Connection 默认打开自动提交,即关闭事务
可以调用 Connection 的 setAutoCommit(boolean autoCommit) 方法来关闭自动提交
  1. // 关闭自动提交,开启事务
  2. conn.setAutoCommit(false);
复制代码
当程序执行完 DML 语句后,需要调用Connection 的 commit() 方法来提交事务
  1. // 提交事务
  2. conn.commit();
复制代码
如果 SQL语句 执行失败,可以用 Connection 的 rollback() 方法来回滚事务
  1. // 回滚事务
  2. conn.rollback();
复制代码
实例
  1. public class TransactionTest {
  2.     private static String url = "jdbc:mysql://localhost:3306/test";
  3.     private static String username = "root";
  4.     private static String password = "123456";
  5.     public static void main(String[] args) throws Exception {
  6.         try (
  7.                 Connection conn = DriverManager.getConnection(url, username, password);
  8.         ) {
  9.             // 关闭自动提交
  10.             conn.setAutoCommit(false);
  11.             try (Statement stmt = conn.createStatement()) {
  12.                 // 下面模拟转账过程 张三向李四转账20元
  13.                 stmt.execute("update account set money = money - 20 where `name` = '张三'");
  14.                 // 报错
  15.                 int i = 1 / 0;
  16.                 stmt.execute("update account set money = money + 20 where `name` = '李四'");
  17.             } catch (Exception ex) {
  18.                 ex.printStackTrace();
  19.                 // 事务回滚
  20.                 conn.rollback();
  21.             }
  22.         }
  23.     }
  24. }
复制代码
Connection 也提供了设置中间点的方法
批量更新

通过使用Statement 对象的addBatch() 方法将多条SQL语句收集起来,然后调用executeBatch()或 executeLargeBatch() 方法同时执行这些SQL 语句
  1. Statement stmt = conn.createStatement();
  2. // 添加多条SQL语句
  3. stmt.addBatch(sql1);
  4. stmt.addBatch(sql2);
  5. stmt.addBatch(sql3);
  6. // 批量更新
  7. stmt.executeBatch();
复制代码
使用连接池

数据库连接的建立及关闭是极耗费系统资源的操作,在多层架构的应用环境中,这种资源的耗费对系统性能影响尤为明显。
数据库连接池的解决方案是:当应用程序启动时,系统主动建立足够的数据库连接,并将这些连接组成一个连接池。每次请求数据库连接时,无需重新打开连接,而是从连接池中取走已有的连接使用,使用完不再关闭连接,而是归还给连接池。
引入C3P0 数据源
  1. <dependency>
  2.     <groupId>com.mchange</groupId>
  3.     <artifactId>c3p0</artifactId>
  4.     <version>0.9.5.5</version>
  5. </dependency>
复制代码
代码示例
  1. public class C3P0Test {
  2.     public static void main(String[] args) throws Exception {
  3.         // 创建连接池实例
  4.         ComboPooledDataSource ds = new ComboPooledDataSource();
  5.         // 设置连接数据库的URL
  6.         ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
  7.         // 设置数据库用户名
  8.         ds.setUser("root");
  9.         // 设置用户名的密码
  10.         ds.setPassword("123456");
  11.         // 设置最大连接数
  12.         ds.setMaxPoolSize(40);
  13.         // 设置最小连接数
  14.         ds.setMinPoolSize(2);
  15.         // 设置初始化线程数
  16.         ds.setInitialPoolSize(10);
  17.         // 设置连接池的缓存Statement 的最大数
  18.         ds.setMaxStatements(180);
  19.         try (
  20.                 // 获取连接
  21.                 Connection conn = ds.getConnection();
  22.                 Statement stmt = conn.createStatement();
  23.                 ResultSet rs = stmt.executeQuery("select * from students");
  24.         ) {
  25.             while (rs.next()) {
  26.                 System.out.print(rs.getInt(1) + "\t");
  27.                 System.out.println(rs.getString(2) + "\t");
  28.             }
  29.         }
  30.     }
  31. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4