0%

JDBC介绍与使用

  • 简介
  • 导入/移除 jar包
  • JDBC使用
  • 对数据增删改查
  • 实体类封装结果集

JDBC介绍

JDBC概述

JDBC(Java DataBase Connectivity, Java数据库连接) ,是一种用于执行SQL语句的Java API,为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成

JDBC

有了JDBC,程序员只需用JDBC API写一个程序,就可访问所有数据库

流程

Sun公司、数据库厂商、程序员三方关系

SUN公司是规范制定者,制定了规范JDBC(连接数据库规范)

  • DriverManager类 作用:管理各种不同的JDBC驱动

  • Connection接口 作用:Java程序和mysql建立的连接

  • Statement接口和PreparedStatement接口 作用:承载SQL给数据库

  • ResultSet接口 作用:返回SQL运算的结果给Java程序

JDBC规范关系图

数据库厂商微软、甲骨文等分别提供实现JDBC接口的驱动jar包

程序员学习JDBC规范来应用这些jar包里的类。

关系图

JDBD历史版本及特征

JDBC 1.0

JDBC 1.0 随JDK1.1一起发布,JDBC操作相关的接口和类位于java.sql包中

JDBC 2.0

JDBC 2.0 API被划分为两部分:核心API和扩展API,有两个包,分别是java.sql包和javax.sql包

1
2
3
4
5
6
7
8
9
10
11
12
13
# java.sql核心API包
在支持新功能方面:包括结果集可以向后滚动,批量的更新数据。另外,还提供了UNICODE字符集的字符流操作。
在支持SQL的数据类型方面:新增加的BLOB, CLOB,和数组接口能够是应用程序操作大块的数据类型

# javax.sql扩展API包
# DataSource数据源接口:
JDBC1.0是原来是用DriverManager类来产生一个对数据源的连接。JDBC2.0用一种替代的方法,使用DataSource的实现,代码变的更小巧精致,也更容易控制。
# Connection pooling:
如果DataSource对象实现与一个支持连接池的中间层的服务器一起工作,DataSource对象就会自动的返回连接池中的连接,这个连接也是可以重复利用的。
# Distrubute transaction:
在一个事务中涉及到了多个数据库服务器。获得一个用来支持分布式事务的连接与获得连接池中的连接是很相似的。同样,不同之处在于DataSource的实现上的不同,而不是在应用程序中获得连接的方式上有什么不同。
# Rowsets:
RowSet接口扩展了ResultSet接口。这样RowSet对象就有了ResultSet对象所有的功能。不可以滚动的ResultSet变成了可以滚动的RowSet。
JDBC访问数据库编码步骤
  1. 加载一个Driver驱动
  2. 创建数据库连接(Connection)
  3. 创建SQL命令发送器Statement
  4. 通过Statement发送SQL命令并得到结果
  5. 处理结果(select语句)
  6. 关闭数据库资源ResultSet Statement Connection

模块/项目 导入依赖jar包

创建项目
  1. 创建Java项目和模块

Java项目

next

命名

新建模块

命名

新建lib目录

  1. 结构图

结构图

导入jar包

mysql-connector-java-8.0.11.jar

  1. 将jar文件放入项目的lib目录中
    单击lib目录 Ctrl+v

  2. 给当前项目添加依赖(告诉当前项目/模块可以依赖jar文件中的代码)
    添加依赖
    点击OK

移除jar包
  1. 移除依赖

进入配置

点击减号

  1. 移除jar包

删除

JDBC使用

结构图

  • 数据库名:mydb

  • 字符集:utf8mb4

  • 表:4张表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.excepenxi.test1;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Statement;

public class TestJDBC {
public static void main(String[] args) throws Exception {
/* 向Dept表增加一条数据 */
//1. 加载驱动 Driver 下方有异常,先 throws Exception 处理完之后,再回来处理异常
Driver driver =new com.mysql.cj.jdbc.Driver();
//2. 注册驱动 DriverManager
DriverManager.registerDriver(driver);
//3. 获得链接 Connection
/*
* user:用户名
* password:密码
* url:统一资源定位符 定位我们要连接的数据库的
* 1协议 jdbc:mysql
* 2IP 127.0.0.1/localhost
* 3端口号 3306
* 4数据库名字 mydb
* 5参数
* 协议://ip:端口/资源路径?参数名=参数值&参数名=参数值&....
* jdbc:mysql://127.0.0.1:3306/mydb
*
* useSSL=false 不使用SSL认证机制
* useUnicode=true 使用Unicode字符集
* characterEncoding=UTF-8 指定字符集为 UTF-8
* serverTimezone=Asia/Shanghai 服务器时区表示东八区 UTC 或 Asia/Shanghai
* */
String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
String user="root";
String password="root";
Connection connection =DriverManager.getConnection(url, user,password );
//4获得语句对象 Statment
Statement statement = connection.createStatement();
//5执行SQL语句,返回结果
/*
* insert、delete、update 操作都是调用statement.executeUpdate
* executeUpdate返回一个int值,代表数据库多少行数据发生了变化
* */
String sql="insert into dept values(50,'教学部','北京');";
int rows = statement.executeUpdate(sql);
System.out.println("影响数据行数为:"+rows);
//6释放资源
/* 注意顺序;后获得的先关闭,先获得的后关闭 */
statement.close();
connection.close();
}
}

插入数据

JDBC常见异常分析

错误1:没有添加jar包或者com.mysql.jdbc2.Driver路径错误

Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc2.Driver

解决:导入依赖jar包

错误2:URL错误

Exception in thread "main" java.sql.SQLException:No suitable driver found for jbdc:mysql://127.0.0.1:3306/stumgr

解决:检查协议、IP、端口号、库名

错误3:用户名或者密码错误

Exception in thread "main" java.sql.SQLException:Access denied for user 'root'@'localhost' (using password: YES)

解决:更正用户名和密码

错误4:主键冲突

Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Duplicate entry '90' for key 'PRIMARY'

解决:已经存在90,更新为其他数值或者 default/null 默认自增(前提主键设置自增)

错误5:mysql安全加密机制

Exception in thread "main" java.sql.SQLException:Public Key Retrieval is not allowed

如果用户使用 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥;可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者AllowPublicKeyRetrieval=True参数以允许客户端从服务器获取公钥;但是需要注意的是 AllowPublicKeyRetrieval=True可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启
在jdbc连接添加上参数allowPublicKeyRetrieval=true即可,注意参数间用&

解决:添加参数 allowPublicKeyRetrieval=true(参数最好不加,容易被攻击)

驱动的加载

加载数据库驱动时,我们可以通过自己创建一个实例的方式,然后去注册驱动

加载驱动

在查看Driver的源代码时我们发现,该类内部有一个静态代码块,在代码块中就是在实例化一个驱动并在驱动中心注册.静态代码块会在类进入内存时执行,也就是说,我们只要让该类字节码进入内存,就会自动完成注册,不需要我们手动去 new com.mysql.cj.jdbc.Driver(),在代码中直接使用反射,通过Class.forName(“com.mysql.jdbc.Driver”),加载该类进入内存即可

1
2
3
4
Driver driver =new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);
// 更改为 通过反射加载驱动并注册
Class.forName("com.mysql.cj.jdbc.Driver");

查看jar包发现jar包中已经默认配置了驱动类的加载

自动加载驱动

jar–META-INF–services–java.sql.Driver–com.mysql.jdbc.Driver,在加载jar包时,会自动读取该内容并加载驱动,所以我们不去编写Class.forName(“com.mysql.jdbc.Driver”),程序也是可以自动完成加载驱动的;稳妥起见,建议加上,以免出现mysql版本不一致,以及jar包不一致导致无法加载驱动

添加异常捕获处理

Ctrl+Alt+T

记录

close异常

close处理

主键自动递增

优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.excepenxi.test1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC2 {
// 1.添加 private static
// 2.报错,公钥不可用,加入 &allowPublicKeyRetrieval=true
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
private static String user="root";
private static String password="root";

public static void main(String[] args) {
// 3.优化
Connection connection=null;
Statement statement=null;
try{
Class.forName(driver);
connection =DriverManager.getConnection(url, user,password);
statement = connection.createStatement();
String sql="insert into dept values(DEFAULT,'助教部门','北京');";
int rows = statement.executeUpdate(sql);
System.out.println("影响数据行数为:"+rows);
}catch (Exception e){
e.printStackTrace();
}finally {
// 4.添加判断
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

对数据库进行增删改查

增加、删除和修改操作

如果出现报错,可能是有外键约束,删除或者修改外键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.excepenxi.test1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC3 {
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
private static String user="root";
private static String password="root";

public static void main(String[] args) {
// testInsert();
// testDelete();
testUpdate();
}

/*
public static void testDelete(){
Connec......;
}
*/

public static void testUpdate(){
Connection connection=null;
Statement statement=null;
try{
Class.forName(driver);
connection =DriverManager.getConnection(url, user,password);
statement = connection.createStatement();
//String sql="insert into dept values(50,'助教部门','北京');";
//String sql="delete from dept where deptno=50;";
String sql="update dept set dname='总部',loc='上海' where deptno=50;";
int rows = statement.executeUpdate(sql);
System.out.println("影响数据行数为:"+rows);
}catch (Exception e){
e.printStackTrace();
}finally {
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

}
查询操作

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.excepenxi.test1;
//import java.sql.*;
import java.sql.*;

public class TestJDBC4 {
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
private static String user="root";
private static String password="root";

public static void main(String[] args) {
testQuery();
}

public static void testQuery(){
// 注释import java.sql.*;,重新导包;
// 点击Connection,按住alt+回车,回车导包,选择java.sql
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;

try{
Class.forName(driver);
connection = DriverManager.getConnection(url, user,password);
statement = connection.createStatement();
String sql="select * from emp;";
// 结果集对象 ResultSet 局部访问不到,指定到上面(外部)
//ResultSet resultSet = statement.executeQuery(sql);
resultSet = statement.executeQuery(sql);

/*
// next() 1.判断游标有没有下一行,如果有下一行,则返回true
// 2.向下移动游标一行,当移动最后一行,下一行为空行,返回false
boolean next = resultSet.next();
System.out.println(next);
// 可以写列号(1表示第一列),也可以写列名(列名可清晰知道是哪列信息)
//int anInt = resultSet.getInt(1);
//System.out.println(anInt);
*/

//以上可以循环,移动游标,如果是true,执行
while (resultSet.next()){
int empno = resultSet.getInt("empno");
//System.out.println(empno);
// 获取其他列信息
String ename = resultSet.getString("ename");
String job = resultSet.getString("job");
int mgr = resultSet.getInt("mgr");
Date hiredate = resultSet.getDate("hiredate");
double sal = resultSet.getDouble("sal");
double comm = resultSet.getDouble("comm");
int deptno = resultSet.getInt("deptno");
System.out.println(""+empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "
+sal+" "+comm+" "+deptno);
}

}catch (Exception e){
e.printStackTrace();
}finally {
//关闭结果集,和statement一样 try/catch 一下
//注意关闭顺序,打开和关闭颠倒
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

}
  • ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
  • ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行。
  • 初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面。
  • 作为一种好的编程风格,应在不需要Statement对象和Connection对象时显式地关闭它们。关闭Statement对象和Connection对象的语法形式为:用户不必关闭ResultSet。当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭。
方法名 说明
boolean next() 将光标从当前位置向下移动一行
boolean previous() 游标从当前位置向上移动一行
void close() 关闭ResultSet 对象
int getInt(int colIndex) 以int形式获取结果集当前行指定列号值
int getInt(String colLabel) 以int形式获取结果集当前行指定列名值
float getFloat(int colIndex) 以float形式获取结果集当前行指定列号值
Float getFloat(String colLabel) 以float形式获取结果集当前行指定列名值
String getString(int colIndex) 以String 形式获取结果集当前行指定列号值
StringgetString(String colLabel) 以String形式获取结果集当前行指定列名值

实体类封装结果集

为什么将结果封装成对象或者对象集合?

  1. Java是面向对象的编程语言,java中所有的数据处理都是基于面向对象的编码风格实现的,让数据以符合java风格的形式存在,便于对数据的后续处理

  2. ResultSet 集合虽然可以存放数据,但是它是JDBC中查询数据的一种手段,是一种数据的临时存储方案,使用完毕是要进行释放和关闭

如何将结果集中的数据在java中进行存储和传递?

准备和数据库表格相对应的一个实体类,用于封装结果集中的每一条数据,数据库表格中的每一个字段就是实体类的一个属性,实体类的一个对象就可以用于存储数据库表中的一条记录。

准备实体类

创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package com.excepenxi.entity;

/*
* 实体类:
* 和数据库表格名称和字段是一一对应的类
* 该类的对象主要用处是存储从数据库中查询出来的数据
* 除此之外,该类没有任何的其他功能

* 要求
* 1类名和表名保持一致 (见名知意)
* 2属性个数和数据库的表的列数保持一致
* 3属性的数据类型和列的数据类型保持一致
* 4属性名和数据库表格的列名要保持一致
* 5所有的属性必须都是私有的 (出于安全考虑)
* 6实体类的属性推荐写成包装类
* 7日期类型推荐写成java.util.Date
* 8所有的属性都要有get和set方法
* 9必须具备空参构造方法
* 10实体类应当实现序列化接口 (mybatis缓存 分布式需要 ) implements Serializable
* 11实体类中其他构造方法可选
* */

import java.io.Serializable;
import java.util.Date;

public class Emp implements Serializable {
// 建议写成包装类 Integer,而不是int;如果数据出现null,int会报错
private Integer empno;
private String ename;
private String job;
private Integer mgr;
// date导入类 建议选择java.util对象,是java.sql的父类
private Date hiredate;
private Double sal;
private Double comm;
private Integer deptno;

@Override
public String toString() {
return "Emp{" +
"empno=" + empno +
", ename='" + ename + '\'' +
", job='" + job + '\'' +
", mgr=" + mgr +
", hiredate=" + hiredate +
", sal=" + sal +
", comm=" + comm +
", deptno=" + deptno +
'}';
}

public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double comm, Integer deptno) {
this.empno = empno;
this.ename = ename;
this.job = job;
this.mgr = mgr;
this.hiredate = hiredate;
this.sal = sal;
this.comm = comm;
this.deptno = deptno;
}

//空参构造方法
public Emp(){
}

//所有的属性都要有get和set方法
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public Integer getMgr() {
return mgr;
}
public void setMgr(Integer mgr) {
this.mgr = mgr;
}
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
public Double getSal() {
return sal;
}
public void setSal(Double sal) {
this.sal = sal;
}
public Double getComm() {
return comm;
}
public void setComm(Double comm) {
this.comm = comm;
}
public Integer getDeptno() {
return deptno;
}
public void setDeptno(Integer deptno) {
this.deptno = deptno;
}
}
实体类封装结果集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.excepenxi.test1;
//import java.sql.*;
import com.excepenxi.entity.Emp;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class TestJDBC5 {
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
private static String user="root";
private static String password="root";
// 以上信息不变

public static void main(String[] args) {

// 2.返回集合
List<Emp> emps = testQuery();
// 8.遍历集合
for (Emp emp : emps) {
System.out.println(emp);
}
}

// 1.返回list集合,Emp对象
public static List<Emp> testQuery(){
// 注释import java.sql.*;,重新导包,点击Connection,按住alt+回车,回车导包,选择java.sql
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
// 4.集合
List<Emp> list =null;

try{
Class.forName(driver);
connection = DriverManager.getConnection(url, user,password);
statement = connection.createStatement();
String sql="select * from emp;";
// 结果集对象 ResultSet 局部访问不到,指定到上面(外部)
//ResultSet resultSet = statement.executeQuery(sql);
resultSet = statement.executeQuery(sql);

/*
// next() ①.判断游标有没有下一行,如果有下一行,则返回true
// ②.向下移动游标一行,当移动最后一行,下一行为空行,返回false
boolean next = resultSet.next();
System.out.println(next);
// 可以写列号(1表示第一列),也可以写列名(列名可清晰知道是哪列信息)
//int anInt = resultSet.getInt(1);
//System.out.println(anInt);
*/

// 5.初始化
list=new ArrayList<>();
//以上可以循环,移动游标,如果是true,执行
while (resultSet.next()){
int empno = resultSet.getInt("empno");
//System.out.println(empno);
// 获取其他列信息
String ename = resultSet.getString("ename");
String job = resultSet.getString("job");
int mgr = resultSet.getInt("mgr");
Date hiredate = resultSet.getDate("hiredate");
double sal = resultSet.getDouble("sal");
double comm = resultSet.getDouble("comm");
int deptno = resultSet.getInt("deptno");
// 3.构造方法,放入数据
Emp emp =new Emp(empno, ename, job, mgr, hiredate, sal, comm, deptno);
// 6.添加数据
list.add(emp);
}

}catch (Exception e){
e.printStackTrace();
}finally {
//关闭结果集,和statement一样 try/catch 一下
//注意关闭顺序,打开和关闭颠倒
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return list;
}
}

返回结果



----------- 本文结束 -----------




Buy me a coffee.