一、JDBC简介
JDBC 通过Java代码来操作数据库
实际工作中大部分的数据库操作,都是通过代码来完成的.格子编程语言都可以操作数据库,主要是数据库提供了各种版本的API(一组函数 / 一组类),调用这些API就可以操作数据库。
但是我们要注意:数据库的种类有许多,不同的数据库,提供的API不太一样(因为不同的数据库是不同的厂商实现的)
程序员,要想通过代码来操作不同的数据库,就需要写多份代码,操作数据库1(写一份代码),操作数据库二(写一份代码),这样造成开发成本变高,学习成本变高。
那么我们想解决这个:很多编程语言的做法,就是把各种数据库的API再次封装一层,封装出一套统一的API,其中JAVA里面,这样的封装就是由Java标准库来完成的,此时这一套封装,称为JDBC
更准确的说JDBC就是Java标准库提供的API,这组API相当于把不同的数据库都统一成一种风格了,通过这一组API,就可以操作任何数据库
(不需要关心数据库与数据库之间,API细节的差异了)
二、JDBC原理
这里看一个图:
了解了上面,我们开始进行JDBC开发。
JDBC API 是标准库自带的,直接使用
二、 JDBC使用
MySQL的JDBC驱动,这个不是系统自带的,需要额外的进行下载安装
那么我们怎么安装呢?在哪里下载
- 对应的官网对应MySQL的JDBC驱动来说,一定可以在官网找到(Oracle管网)
- 对应开源的库,可以去github对应的项目主页,去找,
- 中央仓库,有一些大佬,搞的第三方中央仓库,中央仓库上收集许多第三方库,后面一些第三方也会主动把自己上传到这个库上。
但是上面的前2个方法很难找到,官网的差别很大,我们这里一般是中央仓库,后面的学习中用到的第三方库,全是中央仓库去找。
然后点进去发现有许多版本,我们选择什么版本呢?具体使用什么版本还是看你安装的mysql服务器的版本~
Mysql主流的服务器版本就是两套
8.x系列(使用8.x系列)
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/76bc2bbac0ee4a49bdc6ffc0b0b5af3c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5oSP5oS_5LiJ5LiD,size_20,color_FFFFFF,t_70,g_se,x_16)
- 5.x系列 (使用5.1.x系列) 小版本区别不大,关键是大版本
小版本指的是:5.1.49 和5.1.48 这些区别不大,如果5版本使用8版本的 可能会出现连不上的问题。
下载:点击按钮下载驱动
这个驱动包,是一个jar包,就称为jar包,这是java程序打包部署的一种常见格式,这个.jar 就和压缩包类似,jar包本质就是把一些.class文件以压缩包的形式,给打包在一起了
引用jar包:开始使用 先来搞一个简单的以后教大家更加科学的方式:
开始JDBC 写代码
使用mysql workbench,也需要建立一个连接,也需要指定数据库的ip和端口
![]()
ip:127.0.0.1表示主机自身 (是区分在哪个主机上)
port:3306安装数据库服务器的时候,手动设置的一般默认就是3306(区分在主机上的什么程序上)
使用JDBC来实现数据库的增加:
我们这里有个insert方法里面就是写代码来实现功能
- 创建一个数据源
DataSource 是标准库里面的,也就是自带的,它是一个interface 接口
MysqlDataSource 是jar包的,也就是说如果jar包没有导入成功就没有办法使用 实现了DataSource interface的类
DataSource 这个概念表示“数据库在哪里”,对于Mysql来说,数据库就是一个服务器程序,因此就可以通过DataSource 来描述服务器的地址,端口,用户名,密码,要访问数据库名等等
- 把数据库的位置信息,设置到. DataSource 中
1)数据库的ip端口 ,数据名通过URL表示
使用到一个 setUTL 这个方法,这个setURL方法属于 MysqlDataSource 不是DataSource 的方法,此处需要向下转型
这个操作涉及了向下转型,我们为什么要这样写:
使用这种转型的写法,也很常见,带来的好处就是,代码中其他部分拿到的dataSource都是 DataSource类型,和具体的数据库种类无关.日后如果需要切换数据库,其他代码就完全不用改动了.比如使用的是MysqlDataSource ,因为MysqlDataSource 是Mysql的如果数据库变量了那么其他需要变一点不方便
来看看set.URL里面的内容分别是什么意思
还有新增的2个 useSSL:加密
为什么要加密:
到底需要打开吗加密吗?2)设置登录数据库的用户名
MySQL支持自己创建用户.
MySQL只要安装好之后,就会自带一个用户,就是root.表示"管理员”,拥有最高的权限,可以管理其他用户的权限.当前阶段咱们不必过多的关注这里的权限.(权限的话以后到公司里,都是有专门的人来给你分配好的)
3)设置登录数据库的密码
![]()
3.连接数据库,进行真正的网络通信
![]()
这个就是开始进行网络操作,如果数据库连接上了,会返回一个实例,如果失败会抛出一个异常
![]()
啥时候会连接失败呢?原因非常多,例如 ip ,端口,密码,这些错误会失败,用户没有权限也会失败,数据库没有正确启动也会失败
异常处理 注意是这个库里面的:import java.sql.Connection;
![]()
4.构造一个SQL语句,为插入准备!
通过其他语言来操作数据库,其实还是通过SQL来完成!
这里有一个数据库,结构是这样的:
![]()
此处不需要use数据库的操作,在URL中已经设定好 数据库的名字了,此时客户端连上去之后就能直接确定数据库
![]()
执行语句写好了,我们还需要执行, 有一个专门的对象 :PreparedStatement
![]()
这里要介绍一下为什么要这个对象:我们的SQL语句不一定是直接写死的值直接给你的,可能里面的信息是要动态拼接,就是不是已经给值的,说白了重要的功能是动态拼接SQL5.执行SQL客户端把SQL通过网络请求,发送给mysql服务器,mysqk服务器来解析这个SQL请求,执行具体操作,并返回响应结果,此处使用excuteUpdate 来完成数据库内容的变更(边更包含 insert,update,delete),返回值是整数,返回多少就是影响多少行操作 executeQuery:数据查询.针对select =>返回值是个ResultSet .
最后一步:我们要关闭资源
为什么关闭,客户端通过网络和服务器建立连接,客户端和服务器之间各自会分配一些资源,来保持这样的连接信息(记录对端的ip port)每维护一个连接,就都得分配一些硬件资源,如果一直不关闭资源越耗越多,最后没有资源,程序就无法正常运行调用close的顺序,应该是按照申请顺序的“逆序”来进行的~
前面的代码,是先创建Connection,再创建PreparedStatement
后面的代码,就是先关闭PreparedStatement,后关闭Connection
最后我们执行,全部的代码放在下面最后看看结果:
public class start {
public static void main(String[] args) throws SQLException {
insert();
}
public static void insert() throws SQLException {
//1. 创建一个数据库源
MysqlDataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java101?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("200224");
//2.连接数据库,进行真正的网络通信
Connection connection = dataSource.getConnection();
//3.构造一SQL语句,为插入做准备
//此处不需要use数据库的操作,在URL中已经设定好数据库的名字了,此时客户端连上去之后就能直接确定数据库
String sql = "insert into test values (1,'张三') ";
//执行SQL 专门的对象prepareStatement
PreparedStatement statement = connection.prepareStatement(sql);
//影响行数
int ret = statement.executeUpdate();
System.out.println("ret:"+ret);
//执行完SQL之后,还要回收一些资源
statement.close();
connection.close();
}
}
数据库最后执行成功会新增一个数据:
如果我们想动态拼接怎么办呢??
可以尝试这样的写法:
但是通过字符串拼接,确实可以动态的构造出SQL,但是不建议这样使用,这样拼接比较麻烦,另一方面这样不安全,容易被SQL注入。
那么我们要怎么样实现,更加安全呢?
我们只需要把,SQL语句的拼接全部变成? ?,这样也可以清楚的看清楚我们有那几个值,而且也更加方便,下面是赋值
那个1,就是id替换了第一个?的地方,那个2就是name替换了第二个? 然后第一个是int类型,第二个替换成String类型的。
statement就支持一组setxxx 这样的方法,xxx值得是一个具体的类型~,根据要设置的类型不同,来决定使用不同的方法,数据库的所以的类型基本上全都支持
setxxx方法内部对于设置的值,进行了比较严格的校验,如果用户插入的内容是包含这种疑似SQL注入的时候,setxxx 就可以识别出来。
我们可以看statement里面的SQL
拼装后的样子:
最后数据库也成功了:
public static void insert2() throws SQLException {
//1. 创建一个数据库源
MysqlDataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java101?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("200224");
//2.连接数据库,进行真正的网络通信
Connection connection = dataSource.getConnection();
//3.构造一SQL语句,为插入做准备
//此处不需要use数据库的操作,在URL中已经设定好数据库的名字了,此时客户端连上去之后就能直接确定数据库
Scanner scanner = new Scanner(System.in);
System.out.println("请输入学号");
int id = scanner.nextInt();
System.out.println("请输入姓名");
String name = scanner.next();
String sql = "insert into test values (?,?)";
//执行SQL 专门的对象prepareStatement
PreparedStatement statement = connection.prepareStatement(sql);
statement.setInt(1,id);
statement.setString(2,name);
System.out.println("sql"+statement);
//影响行数
int ret = statement.executeUpdate();
System.out.println("ret:"+ret);
//执行完SQL之后,还要回收一些资源
statement.close();
connection.close();
}
写完了插入,我们来写一下删除呗:
public static void delete() throws SQLException {
//删除和插入差不多 只是把SQL从SQL变成delete
//1.创建数据源,吧数据库的位置信息设置进去
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java101?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("200224");
//2.和数据库建立连接
Connection connection= dataSource.getConnection();
//3.构造SQL
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要删除的id:");
int id= scanner.nextInt();
String sql = "delete from test where id = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setInt(1,id);
//4.执行SQL
int ret = statement.executeUpdate();
System.out.println("ret="+ret);
//5.释放资源
statement.close();
connection.close();
}
其实发现就是SQL有区别其他地方没有区别对吧
但是发现我们的JDBC 操作数据库是不是有点太麻烦了,的确,但是我们之后会告诉简化的方法的,毕竟代码是给我们带来方便的 。
结果:
数据库:
写一下修改:
public static void update() throws SQLException {
//1.创建数据源
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java101?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("200224");
//2.创建连接
Connection connection = dataSource.getConnection();
//3.构造数据库
System.out.println("请输入要修改的id");
Scanner scanner = new Scanner(System.in);
int id = scanner.nextInt();
System.out.println("请输入要修改的name");
Scanner scanner1 = new Scanner(System.in);
String name = scanner1.nextLine();
String sql = "update test set name = ? where id = ?";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1,name);
statement.setInt(2,id);
//4.执行SQL
int ret = statement.executeUpdate();
System.out.println("ret"+ret);
//5.关闭
statement.close();
connection.close();
}
数据库:
查找:查询和修改插入删除,就有一些区别了!
插入删除修改,执行完的返回结果,只有一个int而已
查找操作,执行完的结果,是包含一组结果集合(一张临时表),
此处就需要写一些额外的代码,把这个临时表/结果表里的内容获取到
这个结果的集合的遍历,就非常类似于"迭代器"的遍历
resultSet.next() 获取当前行,同时切换到下一行 这个操作类似 i++
resultSet 就相当于一张表,每次next得到其中的一行,就可以进一步的根据这一行,得到里面的列
如果获取到了这一行 会返回一个true 如果遍历到了表的末尾,在尝试next 就会返回false
使用getxxx方法要和表里的类型相对
public static void search() throws SQLException {
//1.创建数据源
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/java101?characterEncoding=utf8&&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("200224");
//2.创建数据库
Connection connection = dataSource.getConnection();
//3.SQL语句
String sql = "select * from test ";
PreparedStatement statement = connection.prepareStatement(sql);
//4.执行SQL语句
ResultSet resultSet = statement.executeQuery();
//5.遍历结果集合
// resultSet.next() 获取当前行,同时切换到下一行 这个操作类似 i++
//resultSet 就相当于一张表,每次next得到其中的一行,就可以进一步的根据这一行,得到里面的列
//如果获取到了这一行 会返回一个true 如果遍历到了表的末尾,在尝试next 就会返回false
while(resultSet.next()){
//resultSet 就表示当前的这一行
//从这一行里面就能获取到具体的列
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("id:"+id);
System.out.println("name:"+name);
}
//6.释放资源
resultSet.close();
statement.close();
connection.close();
}
数据库:
还有些编程语言/库里,把ResultSet也叫做光标
三、JDBC小结
1.DataSource和MysqlDataSource表示数据源(数据在哪里)
- DataSource :通用的,能够支持各种数据库
- MysqlDataSource:是MySQL驱动包里提供的专门针对·MySQL的类,设置用户名,密码,URL...基本信息
2 .Connection connection = dataSource.getConnection()
- 通过DataSource来建立Connection (要先知道服务器的位置才可以连接)
3.构造SQL
PreparedStatement:
里面包含的不仅仅是SQL,要通过这个对象来把用户构造的SQL通过网络发送给服务器
Connection是自身真的服务器的位置的(从DataSource过来的)通过connection创建出的PreparedStatement,PreparedStatement也知道服务器的位置
executeQuery / executeUpdate:通过网络发送,前提知道在哪里
4.遍历结果集合
通过网络,数据集合已经拿回来了,到了本地,响应的结果已经在客户端代码的内存中,可以在本地遍历,获取内容
通过这些,大家要记住一句话,MySQL是一个“客户端 服务器” 结构的程序,服务器是Mysql的本体,负责管理数据,客户端有许多形态(cmd,workbench ,JDBC....)
四、JDBC封装
刚刚上面的代码比较冗余,许多代码重复的还写,其实我们可以把部分代码封装一下,即可轻松一点:
URL部分
接下来创建数据源
其实每个项目里面有一个数据源就可以了
像DataSource这样的实例只要有1个就可以了,不应该被创建多个
我们通过Static来修饰,此处的Static是表示的是“类成员”,“类方法”
DBUtil在程序中,只存在一份!! 这个类里面的成员,也就是只有一份了!
我们写的类,被编译成.class文件,程序运行的时候,jvm就会从指定的路径中,加载.class文件,.class文件是有固定格式的...(从jvm的实现规范上看到)
JVM就会把.class文件内容进行解析,并且加载到内存中,并且在内存构造一个“类对象”,(类对象里面就包含了这个类的关键信息,这个类叫什么名字,类里面有哪些属性,每个属性叫什么,每个属性是public还是private,还有什么方法,叫什么名字,方法的参数叫什么,返回值是啥public /private)
类就是图纸~
JVM在加载的时候,就会先看看这个类是不是已经在内存中存在了,如果存在,就不要重复加载,如果不存在才从.class加载
所以才要加static
像这样的操作,保证类中在程序只有唯一实例,称为“单例模式”,也是一种设计模式
接下来我们需要执行数据库连接,以及后续的建立连接和关闭释放资源
直接调用close就可以关闭,调用connectio可以连接,最后的代码就是这样的DBUtil
package bookManager;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
/**
* Created by Lin
* Description:
* User: Administrator
* Date: 2022-01-14
* Time: 14:45
*/
//通过这个类 把数据库连接操作封装一下
public class DBUtil {
private static String url = "jdbc:mysql://127.0.0.1:3306/good?characterEncoding=utf8&useSSL=false";
private static String username ="root";
private static String password = "200224";
//接下来创建数据源
//其实每个项目里面有一个数据源就可以了
//像DataSource这样的实例,不应该被创建多个
private static DataSource dataSource = new MysqlDataSource();
//静态代码块 执行时间是在类加载阶段
static {
((MysqlDataSource)dataSource).setURL(url);
((MysqlDataSource)dataSource).setUser(username);
((MysqlDataSource)dataSource).setPassword(password);
}
//提供一个方法,来建立连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放资源代码
//这里的参数不要null就关闭
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection !=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
为什么我们这里要这样写,因为如果我们就嵌套一层try catch的话,那就会出现问题,如果第一个close出现异常,那么就会去catch语句,其他的就不执行了,所以我们使用这样的