1 JDBC引言
1.1 JDBC简介
JDBC(Java DataBase Connectivity) :Java数据库连接技术:具体讲就是通过Java连接广泛的数据库,并对表中数据执行增、删、改、查等操作的技术。
此前我们学习过SQL后,可以通过 Navicat
、SQLyog
等图形化客户端发送SQL操作数据库。本质上,JDBC的作用和图形化客户端的作用相同,都是发送SQL操作数据库。差别在图形化界面的操作是图形化、傻瓜化的,而JDBC则需要通过编码(这时候不要思考JDBC代码怎么写,也不要觉得它有多难)完成图形操作时的效果。
总结:JDBC本质上也是一种发送SQL操作数据库的client技术,只不过需要通过Java编码完成。
为什么要学习JDBC? 既然说JDBC和 Navicat
的作用相同,而 Navicat
图形化操作很简单(我们也很熟练),那么为什么还要学习操作更复杂(需要编码)、更陌生的新的Java客户端技术呢?
Java是可编程的(可定制化),可以将普通用户输入的数据拼接成各种各样的可运行的SQL,从而降低普通用户使用数据库服务的难度。
举个例子:任何系统中都有登录功能,登录时需要从用户表中根据用户名查询然后比对密码,每个人的用户名密码肯定不同,执行的select语句的条件部分就不同,直接让用户通过 Navicat
操作数据库,就得由用户拼接SQL,这对于小白用户要求过高。而JDBC就可以通过Java代码将用户输入的用户名密码拼接到SQL中完成查询,普通小白用户通过JDBC程序操作数据库时,只要会输入用户名密码即可!!!
1.2 JDBC涉及的API
JDBC要通过Java代码操作数据库,JDBC中定义了操作数据库的各种接口和类型:
-
Driver 驱动接口 定义了Java如何和数据库获取连接
-
DriverManager 工具类 提供了管理驱动的便捷能力,可以获取和数据库的连接
-
Connection 连接接口 代表Java和数据库之间的联系
-
PreparedStatement 发送SQL的工具接口 该类型对象用于向数据库发送一条SQL
-
ResultSet 结果集接口 该类型的对象表示一条查询SQL返回的结果
注意:学习到这里不需要你掌握上述的任何一个类型(后续会逐步学习到每一个类型),只需要加深理解JDBC需要通过编码操作数据库(也就是JDBC的可编程属性)即可。
1.3 JDBC是规范不是实现
从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以轻松的用 JDBC编写适用于各种数据库的程序。
为什么JDBC只有接口,没有提供实现?
不同数据库的底层技术不同,不少数据库是闭源的,源代码不公开的。Sun公司无力为所有数据库提供具体实现,只能提供接口而由数据库厂商提供具体实现,Sun公司只是制定JDBC标准,各个厂商准守标准提供具体的实现。JDBC和数据库实现的关系就好比List接口和ArrayList、LinkedList之间的关系。
面向JDBC接口规范编程,写出的JDBC代码可以在不同的数据库间轻松的迁移。
2 JDBC编程
2.1 JDBC的编程步骤
之前,我们已经反复强调JDBC是一种和 Navicat
相当的客户端程序,那么JDBC操作数据库的步骤和 Navicat
操作数据库步骤就大同小异,下面我们先回顾下 Navicat
的操作步骤,然后分析出JDBC的步骤。
总结:开发步骤
1. 加载驱动
2. 获取链接
3. 准备SQL以及发送SQL的工具
4. 执行SQL
5. 处理结果集
6. 释放资源
2.2 第1个JDBC程序
需求:向t_person
表中新增一行数据
create table t_person(
person_id int primary key auto_increment,
person_name varchar(20),
age tinyint,
sex enum('男','女','其他'),
mobile varchar(20),
address varchar(200)
);
-- 需求 向t_person表中插入一行数据
insert into t_person values(null,'王鹏',88,'其他','138383838','北京');
准备工作(搭建开发环境)
需要在项目中引入数据库驱动jar包(jar文件:针对class文件的压缩格式包含了多个带包的class文件,类似于普通文件打包的zip、rar)
-
eclipse
-
将mysql-connector-java.jar添加到项目环境中
1. 右键选中项目,新建一个folder名为lib,将jar包复制到lib目录中
2. 右键选中jar包,build path-->add to build path -
idea
-
将mysql-connector-java.jar添加到项目环境中
1. 右键选中项目,新建一个Directory名为lib,将jar包复制到lib文件夹中
2. 右键选中jar包,Add as Library --> OK编码
(JDBC6步)
public class JDBCTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1 加载驱动
/*
驱动版本8.0.x com.mysql.cj.jdbc.Driver
驱动版本5.1.x com.mysql.jdbc.Driver
*/
Class.forName("com.mysql.cj.jdbc.Driver");
//2 获取连接
String username = "root";//用户名
String password = "123456";//密码
/*
url参数用来确定连接的数据库信息: 数据库机器ip 端口号port 数据库名db_name 连接的参数,比如编解码集、时区...
url格式:jdbc:mysql://ip:port/db_name?k=v参数 ,只需要了解url的组成,不需要记忆
*/
String url = "jdbc:mysql://localhost:3306/ziji?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
Connection conn = DriverManager.getConnection(url, username, password);//通过DriverManager管理驱动获取连接
//3 准备发送SQL的工具
String sql = "insert into t_person values(null,'王月华',88,'其他','138383838','北京')";
PreparedStatement pstm = conn.prepareStatement(sql);
//4 发送执行SQL
int update = pstm.executeUpdate();
System.out.println("影响数据的条数:"+update);
//5 处理结果集(如果有)
//6 释放资源(反序关闭:后出现先关闭)
pstm.close();
conn.close();
}
}
3 结果集的处理.
需求:查询t_person表中的数据
select * from t_person;
public class ResultSetTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2 创建和数据库之间的连接
String url = "jdbc:mysql://localhost:3306/ziji?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
//3 准备一个发送SQL的工具
String sql = "select * from t_person";
PreparedStatement pstm = conn.prepareStatement(sql);
//4 发送并执行SQL
ResultSet rs = pstm.executeQuery();
//5 处理结果集
while(rs.next()){
/*
rs.getXxx(列顺序从1开始) 或者 rs.getXxx("列名") 获取指定列的数据,Xxx为数据类型
实战中多使用列名,可读性强
*/
int personId1 = rs.getInt("person_id");
String personName1 = rs.getString("person_name");
int age1 = rs.getInt("age");
String sex1 = rs.getString("sex");
String mobile1 = rs.getString("mobile");
String address1 = rs.getString("address");
System.out.println("personId="+personId1+",personName="+personName1
+",age="+age1+",sex="+sex1+",mobile="+mobile1+",address="+address1);
int personId2 = rs.getInt(1);
String personName2 = rs.getString(2);
int age2 = rs.getInt(3);
String sex2 = rs.getString(4);
String mobile2 = rs.getString(5);
String address2 = rs.getString(6);
System.out.println("personId="+personId2+",personName="+personName2
+",age="+age2+",sex="+sex2+",mobile="+mobile2+",address="+address2);
}
//6 释放资源
rs.close();
pstm.close();
conn.close();
}
}
4 数据绑定
数据绑定:将用户输入的数据,绑定到要执行的SQL语句中。
JDBC执行的SQL中的数据要根据用户的输入发生变化,比如 登录功能背后的查询sql要根据用户名不同,执行不同的条件。这就需要将用户输入的数据绑定到执行的SQL中。
绑定数据的方式有2种:
-
字符串拼接
-
?占位符绑定
环境准备:
create table t_admin(
admin_id int primary key auto_increment,
admin_name varchar(20) unique not null,
password varchar(50) not null
);
-- 添加测试数据
insert into t_admin values(null,'xiaohei','123456');
4.1 字符串拼接
字符串拼接方式,本质上就是通过Java字符串拼接语法构造出正确的可执行的SQL语句。拼接步骤如下:
-
在需要数据的地方,使用变量名替换
-
在变量名前添加"+,变量后添加+"
-
注意:如果拼接的是字符串,那么需要在"+之前添加' 在+"后添加'
public class DataBindingStringJoinTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2 创建和数据库之间的连接
String url = "jdbc:mysql://localhost:3306/ziji?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
//3 准备一个发送SQL的工具
Scanner sc = new Scanner(System.in);
String adminName = sc.nextLine();
String adminPassword = sc.nextLine();
/*
① 在需要绑定数据的地方,使用变量名替换
② 在变量名前追加( "+ ) 在变量名后追加( +" )
③ 注意:如果拼接的数据是字符串,那么需要在"+前和+"后各添加一个'
*/
String sql = "select * from t_admin where admin_name = '"+adminName+"' and password = '"+adminPassword+"'";
System.out.println("sql = " + sql);
PreparedStatement pstm = conn.prepareStatement(sql);
//4 发送并执行SQL
ResultSet rs = pstm.executeQuery();
//5 处理结果集
if(rs.next()){
int adminId= rs.getInt("admin_id");
String name = rs.getString("admin_name");
String pwd = rs.getString("password");
System.out.println("adminId="+adminId+",adminName="+name+",adminPassword="+pwd);
}
//6 释放资源
rs.close();
pstm.close();
conn.close();
}
}
4.2 ?占位符
?占位符是JDBC的一种特殊语法,专用于参数绑定。使用步骤:
-
在需要使用数据的地方,使用?代替(占位)
-
发送sql前,通过pstm.setXXX方法给?赋值
public class DataBindingPreparedStatementTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2 创建和数据库之间的连接
String url = "jdbc:mysql://localhost:3306/ziji?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
//3 准备一个发送SQL的工具
Scanner sc = new Scanner(System.in);
String adminName = sc.nextLine();
String adminPassword = sc.nextLine();
/*
① sql中需要绑定数据的地方使用?占位
② 通过pstm.setXXX(?序号,数据)绑定数据;//序号从1开始,XXX为数据类型
*/
String sql = "select * from t_admin where admin_name = ? and password = ?";
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1,adminName);
pstm.setString(2,adminPassword);
//4 发送并执行SQL
ResultSet rs = pstm.executeQuery();
//5 处理结果集
if(rs.next()){
int adminId= rs.getInt("admin_id");
String name = rs.getString("admin_name");
String pwd = rs.getString("password");
System.out.println("adminId="+adminId+",adminName="+name+",adminPassword="+pwd);
}
//6 释放资源
rs.close();
pstm.close();
conn.close();
}
}
4.3 字符串拼接和?占位符的区别
方式 | 特点 | 使用场景 | 最佳实践 |
---|---|---|---|
字符串拼接 | 可能会被SQL注入攻击 | 可以拼接表名、列名、SQL关键字 | 动态的查询不同的表以及根据用户选择升序或者降序查询数据 |
?占位符 | 可以防止SQL注入攻击 | 绑定纯数值数据 | 通常情况下使用占位符 |
5 异常
解决问题的步骤:
-
定位问题(哪一行出错)
① 添加打印语句
② 根据异常信息定位(找到异常信息中关于自己写的代码部分)
-
为什么出错
根据异常信息和所学知识分析问题的原因
-
解决异常
根据问题的原因,解决异常记录异常
5.1 根据异常日志分析
分析异常最高效的办法是阅读异常日志,根据异常日志的信息,可以快速定位问题的位置、问题的原因设置是问题的解决方案。
-
先看异常类型:
ClassNotFoundException: com.mysql.cj.jdbc.Driver,说明是驱动类文件没有找到
-
再看异常位置:
自上而下看出现数字的行,找到自己写的代码。比如当前案例下:自己的代码 ResultSetTest.java第8行有问题,那么错误就在这里发生的
-
解决方案:
oracle.jdbc.OracleDriver类没找到,这行代码是加载驱动类的,就一定要先找到对应的类。
错误原因:① 类名写错了
② 驱动jar包没有导入当前项目中
说明:阅读异常日志挑错是最有效的,但是对于初学者而言难度过高。刚开始不用急于求成,异常信息也非常有规范,在学习过程中慢慢提升异常阅读能力,最终掌握阅读异常信息的能力。
5.2 添加打印语句
笨但有效的办法:手动添加打印语句
可以在代码的关键位置处添加打印语句,通过语句的输出与否判定异常发生的问题。 打印输出尽量有意义,比如关键代码有返回值,则可以打印返回值。这样通过返回值可以判断程序运行的状态,为调错提供指导。
5.3 掌握JDBC中的异常
JDBC的异常其实还是非常固定的,就那么几种:类加载问题、数据库用户名密码错误、sql语法错误、绑定数据错误、获取数据错误... 有1个办法可以快速的掌握JDBC中的异常:站在上帝视角,人为的造错,观察记录异常信息,日后出现时可以快速调错。