- JDBC原理
- JDBC优势
- JDBC的使用
- 添加Maven依赖
- 创建数据库和表
- JDBC连接
- 加载mysql数据库驱动类
- 连接数据库
- 查询数据库
- 创建PreparedStatement对象
- 创建ResultSet对象
- next()方法读取结果
- 完整代码
- 数据库增删改(插入更新删除)
- 数据库增(插入)
- 数据库改(更新)
- 数据库删(删除)
- JDBC事务(Transaction)
JDBC(Java DataBase Connectivity)是JAva程序访问数据库的标准接口。用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
实际上MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器。
- 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;
- Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;
- 可随时替换底层数据库,访问数据库的Java代码基本不变。
JDBC是一套接口规范,它位于Java的标准库java.sql中,不过这里面大部分都是接口。
接口并不能直接实例化,而是必须实例化对应的实现类,然后通过接口引用这个实例。
那么问题来了:JDBC接口的实现类在哪?
因为JDBC接口并不知道我们要使用哪个数据库,所以,用哪个数据库,我们就去使用哪个数据库的“实现类”,我们把某个数据库实现了JDBC接口的jar包称为JDBC驱动。
如果我们选择MySQL作为数据库,所以我们首先得找一个MySQL的JDBC驱动。所谓JDBC驱动,其实就是一个第三方jar包,我们直接添加一个Maven依赖就可以。
mysql mysql-connector-java 8.0.28 runtime
关于runtime:注意到这里添加依赖的scope是runtime,因为编译Java程序并不需要MySQL的这个jar包,只有在运行期才需要使用。如果把runtime改成compile,虽然也能正常编译,但是在IDE里写程序的时候,会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆,所以坚决不要设置为compile.
创建数据库和表-- 创建数据库learjdbc:
DROp DATABASE IF EXISTS learnjdbc;
CREATE DATABASE learnjdbc;
-- 创建表students:
USE learnjdbc;
CREATE TABLE students (
id BIGINT AUTO_INCREMENT NOT NULL,
name VARCHAr(50) NOT NULL,
gender TINYINT(1) NOT NULL,
grade INT NOT NULL,
score INT NOT NULL,
PRIMARY KEY(id)
) Engine=INNODB DEFAULT CHARSET=UTF8;
-- 插入初始数据:
INSERT INTO students (name, gender, grade, score) VALUES ('小明', 1, 1, 88);
INSERT INTO students (name, gender, grade, score) VALUES ('小红', 1, 1, 95);
INSERT INTO students (name, gender, grade, score) VALUES ('小军', 0, 1, 93);
INSERT INTO students (name, gender, grade, score) VALUES ('小白', 0, 1, 100);
INSERT INTO students (name, gender, grade, score) VALUES ('小牛', 1, 2, 96);
INSERT INTO students (name, gender, grade, score) VALUES ('小兵', 1, 2, 99);
INSERT INTO students (name, gender, grade, score) VALUES ('小强', 0, 2, 86);
INSERT INTO students (name, gender, grade, score) VALUES ('小乔', 0, 2, 79);
INSERT INTO students (name, gender, grade, score) VALUES ('小青', 1, 3, 85);
INSERT INTO students (name, gender, grade, score) VALUES ('小王', 1, 3, 90);
INSERT INTO students (name, gender, grade, score) VALUES ('小林', 0, 3, 91);
INSERT INTO students (name, gender, grade, score) VALUES ('小贝', 0, 3, 77);
JDBC连接
使用JDBC时,我们先了解什么是Connection。
Connection代表一个JDBC连接,它相当于Java程序到数据库的连接(通常是TCP连接)。
打开一个Connection时,需要准备URL、用户名和口令,才能成功连接到数据库。
URL是由数据库厂商指定的格式,例如,MySQL的URL是:
jdbc:mysql://: / ?key1=value1&key2=value2
假设数据库运行在本机localhost,端口使用标准的3306,数据库名称是learnjdbc,那么URL如下:
jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456
JDBC连接可拆分为三步:
- 加载mysql数据库驱动类
- 连接数据库
- 查询数据库
Class.forName(“com,mysql.cj.jdbc.Driver”)
连接数据库Connection conn = DriverManger.getConnection(JDBC_URL) #or Connection conn = DriverManger.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)
JDBC连接是一种昂贵的资源,所以使用后要及时释放。使用try (resource)来自动释放JDBC连接是一个好方法:
try(Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)){
}
查询数据库
获取到JDBC连接后,下一步我们就可以查询数据库了。
查询数据库分三步:
第一步,通过Connection提供的createStatement()方法创建一个Statement对象,或者通过Connection提供的prepareStatement()方法创建一个PreparedStatement对象,用于执行一个查询
Statement stmt = conn.createStatement("SELECT id, grade, name, gender FROM students WHERe gender=1 AND grade=3");
#or
PreparedStatement ps = conn.prepareStatement("SELECt id, grade, name, gender FROM students WHERe gender=? AND grade=?"):
ps.setObject(1,1);
ps.setObject(2,3);
创建ResultSet对象
第二步,执行Statement对象或PreparedStatement对象提供的executeQuery(),执行查询并获得返回的结果集,使用ResultSet来引用这个结果集
ResultSet rs = ps.executeQuery();next()方法读取结果
第三步,反复调用ResultSet的next()方法并读取每一行结果。
while (rs.next()) {
long id = rs.getLong("id"); // 注意:索引从1开始
long grade = rs.getLong("grade");
String name = rs.getString(3);
int gender = rs.getInt(4);
System.out.println(name);
}
try{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456");
System.out.println("创建连接成功");
PreparedStatement ps = conn.prepareStatement("SELECt id, grade, name, gender FROM students WHERe gender=? AND grade=?");
ps.setObject(1,1);
ps.setObject(2,3);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
long id = rs.getLong("id"); // 注意:索引从1开始
long grade = rs.getLong("grade");
String name = rs.getString(3);
int gender = rs.getInt(4);
System.out.println(name);
}
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
完整代码
import java.sql.*;
public class test_mysql {
public static void main(String[] args) {
try{
// 1.加载mysql数据库驱动类
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
//2.连接数据库
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456");
System.out.println("创建连接成功");
PreparedStatement ps = conn.prepareStatement("SELECt id, grade, name, gender FROM students WHERe gender=? AND grade=?");
ps.setObject(1,1);
ps.setObject(2,3);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
long id = rs.getLong("id"); // 注意:索引从1开始
long grade = rs.getLong("grade");
String name = rs.getString(3);
int gender = rs.getInt(4);
System.out.println(name);
}
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
}
}
数据库增删改(插入更新删除)
数据库增(插入)
插入操作是INSERT,即插入一条新记录。通过JDBC进行插入,本质上也是用PreparedStatement执行一条SQL语句,不过最后执行的不是executeQuery(),而是executeUpdate()。示例代码如下:
try{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456");
System.out.println("创建连接成功");
PreparedStatement ps1 = conn.prepareStatement("INSERT INTO students (id,grade,name,gender,score) VALUES (?,?,?,?,?)");
ps1.setObject(1,999);
ps1.setObject(2,1);
ps1.setObject(3,"Bob");
ps1.setObject(4,1);
ps1.setObject(5,1);
int n = ps1.executeUpdate();
System.out.println(n);
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
设置参数与查询是一样的,有几个?占位符就必须设置对应的参数。虽然Statement也可以执行插入操作,但我们仍然要严格遵循绝不能手动拼SQL字符串的原则,以避免安全漏洞。当成功执行executeUpdate()后,返回值是int,表示插入的记录数量。此处总是1,因为只插入了一条记录。
数据库改(更新)更新操作是UPDATE语句,它可以一次更新若干列的记录。更新操作和插入操作在JDBC代码的层面上实际上没有区别,除了SQL语句不同:
import java.sql.*;
public class mysql_up {
public static void main(String[] args) {
// 1.加载mysql数据库驱动类
try{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456");
System.out.println("创建连接成功");
PreparedStatement ps1 = conn.prepareStatement("UPDATE students SET name=? where id=?");
ps1.setObject(1,"BOB");
ps1.setObject(2,999);
int n = ps1.executeUpdate();
System.out.println(n);
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
}
}
数据库删(删除)
删除操作是DELETE语句,它可以一次删除若干列**。和插入、更新一样,除了SQL语句不同外,JDBC代码都是相同的**:
import java.sql.*;
public class mysql_up {
public static void main(String[] args) {
// 1.加载mysql数据库驱动类
try{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/learnjdbc?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT&user=root&password=123456");
System.out.println("创建连接成功");
PreparedStatement ps1 = conn.prepareStatement("DELETE FROM students where id=?");
ps1.setObject(1,999);
int n = ps1.executeUpdate();
System.out.println(n);
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}
}
}
JDBC事务(Transaction)
数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:
- Atomicity:原子性
- Consistency:一致性
- Isolation:隔离性
- Durability:持久性
数据库事务可以并发执行,对于两个并发执行的事务,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。数据库系统提供了隔离级别来让我们有针对性地选择事务的隔离级别,避免数据不一致的问题。
SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:
| Isolation Level | 脏读(Dirty Read) | 不可重复读(Non Repeatable Read) | 幻读(Phantom Read) |
|---|---|---|---|
| Read Uncommitted | Yes | Yes | Yes |
| Read Committed | - | Yes | Yes |
| Repeatable Read | - | - | Yes |
| Serializable | - | - | - |
对应用程序来说,数据库事务非常重要,很多运行着关键任务的应用程序,都必须依赖数据库事务保证程序的结果正常。
例如:假设小明准备给小红支付100,两人在数据库中的记录主键分别是123和456,那么用两条SQL语句操作如下:
UPDATE accounts SET balance = balance - 100 WHERe id=123 AND balance >= 100; UPDATE accounts SET balance = balance + 100 WHERe id=456;
这两条语句必须以事务方式执行才能保证业务的正确性,因为一旦第一条SQL执行成功而第二条SQL失败的话,系统的钱就会凭空减少100,而有了事务,要么这笔转账成功,要么转账失败,双方账户的钱都不变。



