之前学习了JSP和Servlet以及JavaBean,本篇Blog补上通往MVC架构的最后一篇,数据的持久化,也就是JDBC的相关知识,了解JDBC的基本概念和用处,如何操作数据库。
JDBC基本概念
什么是JDBC?JDBC 指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库。JDBC API 库包含下面提到的每个任务,都是与数据库相关的常用用法。
- 创建或断开与数据库的连接
- 创建 SQL 或 MySQL 语句,执行 SQL 或 MySQL 查询或修改数据库中的数据,查看和修改所产生的记录
- 抛出SQL语句执行的错误
从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以用 Java 编写不同类型的可执行文件。为什么要有JDBC,没有JDBC,我们写代码要自己考虑如何连接数据库,要自己考虑如何连接各式各样的数据库,编写它们的驱动程序,而且要自己管理这些连接,现在有了JDBC,它为我们处理好了一切,我们只需要调用API接口就可以轻松操作数据库了,何乐而不为。
JDBC架构
JDBC 的 API 支持两层和三层处理模式进行数据库访问,但一般的 JDBC 架构由两层处理模式组成:
- JDBC API: 提供了应用程序对 JDBC数据库驱动程序管理器的连接。
- JDBC Driver API: 提供了 JDBC 数据库驱动程序管理器对数据库驱动程序的连接。
JDBC API 使用驱动程序管理器和数据库特定的驱动程序来提供异构数据库的透明连接。JDBC 数据库驱动程序管理器可确保正确的数据库驱动程序来访问每个数据源。该驱动程序管理器能够支持连接到多个异构数据库的多个并发的驱动程序。
JDBC常用接口
JDBC 的 API 提供了以下接口和类用来帮助我们连接数据库并进行相关操作,可以按照用途进行这样的划分:
与数据库连接操作相关的对象:
- Connection : 此接口具有连接数据库的所有方法。该连接对象表示通信上下文,即,所有与数据库的通信仅通过这个连接对象进行
- DriverManager :这个类管理一系列数据库驱动程序。匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。
- Driver : 这个接口处理与数据库服务器的通信。一般我们用不到这个类,我们直接使用 DriverManager类,它管理Driver对象,也抽象了与Driver对象工作相关的详细信息。
与数据库执行SQL语句相关的对象:
- Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。也就是通过此接口构建和执行SQL语句。
- ResultSet : 在使用语句对象执行 SQL 查询后,这些对象保存从数据获得的数据,也就是结果集,结果集可滚动。
数据库执行SQL语句产生的异常对象:
- SQLException : 这个类处理发生在数据库应用程序的任何错误。
总而言之,JDBC的常用操作就是围绕我们通常在数据库中的一系列操作展开的。
JDBC数据类型
JDBC 驱动程序在将 Java 数据类型发送到数据库之前,会将其转换为相应的 JDBC 类型。对于大多数数据类型都采用了默认的映射关系。例如,一个 Java int 数据类型转换为 SQL INTEGER。通过默认的映射关系来提供驱动程序之间的一致性,当调用 PreparedStatement
中的 setXXX()
方法时,Java 数据类型会转换为默认的 JDBC 数据类型:
JDBC事务
可以通过事务在任意时间来控制以及更改应用到数据库。它把单个 SQL 语句或一组 SQL 语句作为一个逻辑单元,如果其中任一语句失败,则整个事务失败,我们一般是默认事务自动提交的,如果想手动提交事务,也可以操作,示例如下:
try{ //关闭数据库事务自动提交 conn.setAutoCommit(false); Statement stmt = conn.createStatement(); //设置一个还原点 Savepoint savepoint1 = conn.setSavepoint("Savepoint1"); String SQL = "INSERT INTO Employees " + "VALUES (106, 20, 'Rita', 'Tez')"; stmt.executeUpdate(SQL); String SQL = "INSERTED IN Employees " + "VALUES (107, 22, 'Sita', 'Tez')"; stmt.executeUpdate(SQL); // 提交事务 conn.commit(); }catch(SQLException se){ // 如果捕获到了异常,回滚事务到还原点 conn.rollback(savepoint1); }
JDBC批处理
批处理是指将关联的 SQL 语句组合成一个批处理,并将他们当成一个调用提交给数据库。当一次发送多个 SQL 语句到数据库时,可以减少通信的资源消耗,从而提高了性能。我们可以批处理Statement对象的数据,也可以批处理PrepareStatement 对象数据:
批处理Statement对象的数据示例
// Create statement object Statement stmt = conn.createStatement(); // Set auto-commit to false conn.setAutoCommit(false); // Create SQL statement String SQL = "INSERT INTO Employees (id, first, last, age) " + "VALUES(200,'Zia', 'Ali', 30)"; // Add above SQL statement in the batch. stmt.addBatch(SQL); // Create one more SQL statement String SQL = "INSERT INTO Employees (id, first, last, age) " + "VALUES(201,'Raj', 'Kumar', 35)"; // Add above SQL statement in the batch. stmt.addBatch(SQL); // Create one more SQL statement String SQL = "UPDATE Employees SET age = 35 " + "WHERE id = 100"; // Add above SQL statement in the batch. stmt.addBatch(SQL); // Create an int[] to hold returned values int[] count = stmt.executeBatch(); //Explicitly commit statements to apply changes conn.commit();
批处理PrepareStatement 对象的数据示例
// Create SQL statement String SQL = "INSERT INTO Employees (id, first, last, age) " + "VALUES(?, ?, ?, ?)"; // Create PrepareStatement object PreparedStatemen pstmt = conn.prepareStatement(SQL); //Set auto-commit to false conn.setAutoCommit(false); // Set the variables pstmt.setInt( 1, 400 ); pstmt.setString( 2, "Pappu" ); pstmt.setString( 3, "Singh" ); pstmt.setInt( 4, 33 ); // Add it to the batch pstmt.addBatch(); // Set the variables pstmt.setInt( 1, 401 ); pstmt.setString( 2, "Pawan" ); pstmt.setString( 3, "Singh" ); pstmt.setInt( 4, 31 ); // Add it to the batch pstmt.addBatch(); int[] count = pstmt.executeBatch(); //Explicitly commit statements to apply changes conn.commit();
JDBC操作实践
由于后续要学习MyBatis,这里就不详细细说JDBC的各种各样的操作了,因为我们知道MyBatis做的更好,是一个持久层框架,我们这里只是简单使用下JDBC,大致了解下在框架出现之前代码是如何通过JDBC操作数据库的。我们先预置一个数据表user,包含四个字段:
安装JDBC的Mysql驱动
我们通过Maven构建项目可以直接加载数据库驱动,通过pom.xml文件安装驱动程序:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> /dependency>
建立JDBC数据库连接
可以编写代码加载安装驱动,也就是建立与数据库的连接:
package com.example.GoldenManage.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class JdbcUtil { public static Connection getConnection() { Connection connection; //JDBC驱动 String driver = "com.mysql.cj.jdbc.Driver"; //数据库url String url = "jdbc:mysql://localhost:3306/test?&useSSL=false&serverTimezone=UTC"; String user = "root"; String password = "root"; try { //注册JDBC驱动程序 Class.forName(driver); //建立连接 connection = DriverManager.getConnection(url, user, password); if (!connection.isClosed()) { System.out.println("数据库连接成功"); } return connection; } catch (ClassNotFoundException e) { System.out.println("数据库驱动没有安装"); } catch (SQLException e) { e.printStackTrace(); System.out.println("数据库连接失败"); } return null; } }
执行SQL语句并获取结果集
建立连接后就可以操作数据库执行相关的SQL语句了,执行SQL语句有两种常用对象,用来构建查询语句:
我们一般不在JDBC中执行存储过程
package com.example.GoldenManage.dao; import com.example.GoldenManage.model.UserModel; import com.example.GoldenManage.utils.JdbcUtil; import javax.xml.crypto.Data; import java.sql.*; import java.time.LocalDate; public class UserDao { /** * 通过用户名到数据库中获取凭证密码 * @param userName * @return */ public static String getPasswordByUserName(String userName) { //SQL语句 String sql = "select password from user where username = " +"'" + userName+"'"; Connection connection = JdbcUtil.getConnection(); Statement statement=null; ResultSet resultSet = null; String password=null; try { statement = connection.createStatement(); //执行语句,得到结果集 resultSet = statement.executeQuery(sql); while (resultSet.next()) { //这里只查询的密码 password = resultSet.getString(1); } resultSet.close(); connection.close();//关闭连接 } catch (SQLException e1) { e1.printStackTrace(); } return password; } /** * 插入數據 * @return */ public static int regiserUser(UserModel userModel) { int flag=0; //SQL语句 String sql = "insert into user values (?,?,?,?)"; Connection connection = JdbcUtil.getConnection(); PreparedStatement statement=null; try { statement = connection.prepareStatement(sql); statement.setString(1, userModel.getUsername()); statement.setString(2, userModel.getPhone()); statement.setString(3, userModel.getPassword()); statement.setString(4, userModel.getEmail()); //执行语句,得到结果集 flag = statement.executeUpdate(); connection.close();//关闭连接 } catch (SQLException e1) { e1.printStackTrace(); } return flag; } }
同样我们也可以对结果集做多种操作,例如获取第一条数据或者或者最后一条数据等操作,结果集其实就是返回一个数据表,数据表的光标是可滚动的,有些常用的操作方法:
SQL语句执行异常
当SQL语句执行异常时会有SQLException报出来:
JDBC事务操作实践
我们编写一段事务验证的代码,确认JDBC确实可以操作事务回滚:
public static void main(String[] args) throws SQLException { Connection connection = JdbcUtil.getConnection(); Savepoint savepoint = null; try{ //关闭数据库事务自动提交 connection.setAutoCommit(false); Statement stmt = connection.createStatement(); //设置一个还原点 savepoint = connection.setSavepoint("Savepoint1"); String sql = "insert into user values ('tml','13345678907','666666','123@qq.com')"; stmt.executeUpdate(sql); //错误的sql语句 String errorSql = "inserted into user values ('tml','13345678907','666666','123@qq.com')"; stmt.executeUpdate(errorSql); // 提交事务 connection.commit(); }catch(SQLException se){ // 如果捕获到了异常,回滚事务到还原点 connection.rollback(savepoint); System.out.println("sql语句执行异常,事务已回滚"); }
当执行时就会报错,可以发现数据也并没有落库:
当我们修正错误的sql语句后,可以发现数据正常落库:
JDBC批处理操作实践
我们还是往user表里插数据,只不过这次是批量插入数据:
public static void main(String[] args) throws SQLException { Connection connection = JdbcUtil.getConnection(); try{ String sql = "insert into user values (?,?,?,?)"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, "AA"); stmt.setString(2, "AA"); stmt.setString(3, "AA"); stmt.setString(4, "AA"); stmt.addBatch(); stmt.setString(1, "BB"); stmt.setString(2, "BB"); stmt.setString(3, "BB"); stmt.setString(4, "BB"); stmt.addBatch(); stmt.setString(1, "CC"); stmt.setString(2, "CC"); stmt.setString(3, "CC"); stmt.setString(4, "CC"); stmt.addBatch(); stmt.executeBatch(); }catch(SQLException se){ System.out.println("批处理异常"+se); } }
查看结果可以看到批量插了进来:
总结一下
JDBC其实就是提供了一套我们通过代码去访问数据库的平台无光的规范,帮助我们连接数据库,执行SQL语句,执行数据库事务,批处理数据库数据或SQL语句,操作返回的结果集,并且给我们提供数据类型的映射,打印执行SQL失败的报错。其实还提供一些数据库的流数据处理等高级操作,这里就不一一说明了。