文章目录
Java的数据库编程:JDBC
JDBC,即Java Database Connectivity (Java数据库连接)。是一种用于执行SQL语句的Java API,它是Java中的数据库连接规范。这个API由 java.sql. * , javax.sql.* 包中的一些类和接口组成,它为Java开发人员操作数据库提供了一个标准的API,可以为不同的关系型数据库提供统一访问。
JDBC工作原理
JDBC 为不同关系型数据库提供了统一访问方式,作为特定厂商数据库访问API的一种高级抽象,它主要包含一些通用的接口类。
JDBC优势:
- Java访问数据库操作完全面向抽象接口编程
- 开发数据库应用不用限定在特定数据库厂商的API
- 程序的可移植性大大增强
Java通过 JDBC 这样的技术来操作 MySQL
MySQL提供了一系列API让程序猿调用,从而通过代码来操作数据库
应用程序编程接口(Application Programming Interface), 简称API
API都是现成的,可以直接进行调用,实现一些效果
对于Java来说,Java提供了"标准库",只要安装了Java,此时就可以使用标准库中的类和方法(标准库的API), 也可以使用其他大佬写好的类和方法(第三方库的API),但这不是自带的,需要额外安装
同理C/C++也都有标准库自带的API,也有第三方库的API
有的库,提供的API特别多,形成了一系列体系,这时也可以称为"SDK" (software development kit :软件开发工具包),Java中的 JDK 就是一个SDK, 只不过给他起了个专属的名字: Java软件开发工具包
数据库驱动包:不同的数据库,对应不同的编程语言提供了不同的数据库驱动包,如:MySQL提
供了Java的驱动包mysql-connector-java,同样的,要基于Java操作Oracle数据库则需要Oracle的数据库驱动包ojdbc。
Oracle, SQL Server, SQLite…也都提供了这样的API让程序猿调用,这些数据库都是不同的厂商/不同的人开发的,这些不同数据库的API提供的功能大同小异,但是细节上存在很大的差异。一个程序猿如果在项目中用到不同的数据库,就需要学习掌握多份API的使用,非常麻烦。Java就说他那边出一套API接口标准,数据库厂商都照着他那一套适配过来,每个数据库厂商额外写一些代码,能够按照Java提供的这一套标准把原生的API重新封装。后续程序猿只需要学习掌握Java这一套API就可以无缝切换各种数据库了,而这一套API就是JDBC
这样的"接口转换"程序称为"数据库驱动",类似于电脑上的"转接头"
具体步骤
准备数据库驱动包
JDBC 是 Java标准库提供的,只要安装了JDK就自带 JDBC
但是使用JDBC 操作 MySQL 就需要下载并导入MySQL的驱动包
下载安装一个程序最好去官网下载,但是Oracle官网特别复杂,很难找。
而对Java程序猿来说,日常开发中会用到大量的第三方库,就有大佬把这些第三方库的安装包收集到一起,统一整理到一个网站上,称为"中央仓库"(类似于收集APP的应用商店)。我们直接去中央仓库下载就行了
中央仓库网址:https://mvnrepository.com/
在搜索框搜索mysql
这两个都是MySQL提供的JDBC的驱动包,我的数据库是 mysql 5.7 就搭配旧版本使用了,如果是 mysql 8, 使用新版本就行
有很多小版本,但区别不大。只要最前面第一位的大版本匹配,后续的小版本无所谓
我就以5.1.14示范:
这一框显示的是漏洞
自己的数据库非常安全,不怕漏洞,但未来实际开发中,公司里要选择稳健一点的版本
下载的地方比较隐蔽,下载到哪都无所谓,待会要拷贝到项目中
就是这个:
.jar其实是一个类似于.rar这样的压缩包文件,包含了很多的.class文件(.java 编译生成的字节码文件)
将编译出来的.class拷贝给别人,别人就能运行使用了
由于一个程序中,往往.class很多,会涉及到一些复杂的目录结构.直接拷贝一堆.class会乱(不知道拷多还是拷少了)
于是约定把这些要发布的.class按照特定格式,打包压缩,就得到了.jar文件.后续直接拷贝.jar文件即可,JVM可以直接识别.jar内部的.class文件并直接运行
.jar的方式是最常见的一种发布Java程序的方式
把驱动包添加到项目的依赖中
1.在项目中随便创建一个目录,命名为lib (library: 图书馆, 库 的缩写)
2.把刚才的依赖包mysql-connector-java-5.1.14.jar拷贝到lib中
3.右键lib这个目录,点击选项中的添加为库…(Add as Library…),弹出框点击确定.这是告诉IDEA,当前这个目录是存放第三方库的目录,此时IDEA就能识别到咱们拷贝进来的驱动包了
添加之后, 此处就能看到"可展开",IDEA已经识别到驱动包了
上述操作是JDBC程序的准备工作,之后就可以编写代码了
编写代码
先建一个类,我取名为JDBCDemo
Demo 是例子的意思,很多时候阅读一些别人写的文档,一般都是先看Demo/Example/Quick Start…
要想编写JDBC,还需要准备好数据库和数据表(虽然 JDBC 也能进行建表操作,但一般都是提前创建好的)
我就以 javamysql数据库里的 test表 为例进行演示
数据库连接Connection
Connection接口实现类由数据库提供,获取Connection对象通常有两种方式:
-
一种是通过DriverManager(驱动管理类)的静态方法获取:
-
一种是通过DataSource(数据源)对象获取。实际应用中会使用DataSource对象。
DataSource dataSource = new MysqlDataSource();//这个类就来自于驱动包
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/javamysql?characterEncoding=utf8&useSSL=false");//向下转型
//除了要设置URl,还要设置用户名和密码
//用户名
((MysqlDataSource) dataSource).setUser("root");//向下转型
//密码
((MysqlDataSource) dataSource).setPassword("123456");//向下转型
//2.和数据库服务器建立连接
Connection connection = dataSource.getConnection();
以上两种方式的区别是:
- DriverManager类来获取的Connection连接,是无法重复利用的,每次使用完以后释放资源
时,通过connection.close()都是关闭物理连接。 - DataSource提供连接池的支持。连接池在初始化时将创建一定数量的数据库连接,这些连接
是可以复用的,每次使用完数据库连接,释放资源调用connection.close()都是将Conncetion连接对象回收。
Url (唯一资源定位符)
MySQL是一个客户端-服务器程序
服务器是保存数据的本体
JDBC 代码就是在写一个 MySQL客户端(之前用的cmd黑框就是MySQL自带的客户端)
我们自己写的客户端就要通过网络访问到数据库服务器,进一步进行增删改查,而URL描述了服务器/服务器上的资源在网络中所在的位置
URL格式:协议的名称://IP地址:端口号/数据库名?参数=值&参数=值
因为我的客户端和服务器在同一主机上,URL就是jdbc:mysql://127.0.0.1:3306/javamysql?characterEncoding=utf8&useSSL=false
jdbc:mysql
:描述的是给jdbc中的 mysql 使用的 url127.0.0.1
:IP地址,描述的是一个主机在网络上的位置3306
: 端口号,区分主机上的应用程序。3306 是mysql 默认的端口号,安装的时候可以设置,如果没修改,默认是 3306javamysql
:数据库的名字characterEncoding=utf8
:指定字符集UTF8useSSL=false
:关闭加密通信(不写的话默认开启。有些主机上对应的依赖程序可能有问题,导致连不上)
mysql中可以手动创建各种名字的用户,默认自带一个root 用户,而且这个 root 是管理员账户(权限最大)
DataSource要能对接到不同的数据库,而不同的数据库设置数据源的方式是不一样的,对于mysql是设置url, user, password ,对于其他数据库就不一定了。所以如果是向上转型创建的DataSource对象需要向下转型回去
进行 客户端 - 服务器之间通信的时候,常见有两种通信模式:
1)有连接(JDBC就属于"有连接"),类似于打电话,拨号之后,对方得接通了,才能说话,对方要是挂断了,没的通信
2)无连接,类似于发短信,无论对方是否接受,都能把数据发过去
选第一个,这个Connection就表示 客户端和服务器之间的 “连接对象”
如果一切顺利,连接建立成功,此时就能够得到 Connection 对象
但 getConnection 很可能失败(服务器没有接受连接),原因有很多,包括不限于:
- 数据库服务器没有正确启动
- url写错了
- 用户名写错了
- 密码写错了
- 网络断开了(网线掉了)
…
虽然是通过Java来操作数据库,实际上核心还是SQL,只不过是把SQL嵌入到Java中
这是字符串结构的 sql, 往往还需构造一个"语句对象"
Statement对象
Statement对象主要是将SQL语句发送到数据库中。JDBC API中主要提供了三种Statement对象。
Statement | 用于执行不带参数的简单SQL语句 |
---|---|
PreparedStatement | 1.用于执行带或者不带参数的SQL语句 2.SQL语句会预编译在数据库系统 3.执行速度快于Statement对象 |
CallableStatement | 用于执行数据库存储过程的调用 |
一个字符串 sql 语句发送到数据库服务器上,是要先对其进行解析和各种校验(判定是否符合语法要求)才能执行,这个解析操作也是需要花费一定的开销,虽然开销不是很大,但mysql服务器同时要给多个客户端提供服务。为了减轻数据库服务器的负担,就可以在客户端这边完成预编译,此时把解析后的结果发给服务器,服务器直接执行即可。所以实际开发中最常用的是PreparedStatement对象,以下对其的总结:
- 参数化SQL查询
- 占位符不能使用多值
- 性能比Statement高
- 占位符:? 下标从1开始
- SQL预编译
- 阻止常见SQL注入攻击
SQL语句不用加分号
这个就是把字符串形式的SQL 转为 语句对象(内部会对sql进行解析校验)
执行SQL的方法
主要掌握两种执行SQL的方法:
executeQuery() | 方法执行后返回单个结果集的,通常用于select语句 |
---|---|
executeUpdate() | 方法返回值是一个整数,指示受影响的行数,通常用于update、insert、delete语句 |
执行这个方法,就会给数据库服务器发起请求,请求中就是包含了解析后的 SQL 语句,等待数据库执行 SQL。数据库执行完 SQL 后, 返回响应。这个方法再获取到响应,并且把数据库返回的结果通过 返回值 体现出来
释放资源
关于为什么释放资源,参考此文
这里的释放顺序是确保 先创建的后释放。释放顺序通常和创建的顺序相反
内存是有限的,用了之后不释放,继续再申请,迟早会把内存耗尽。Java有 垃圾回收机制(GC), 可以自动帮我们回收不用的内存,但是编程过程中涉及到的"有限资源" 不仅仅是内存,这些资源Java无能为力,需要手动释放了
完整的代码:
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;//JDBC 自带的 Interface
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class JDBCDemo {
public static void main(String[] args) throws SQLException {
//接下来通过 JDBC 进行一个简单的 插入数据 的操作
Scanner in = new Scanner(System.in);
//1,创建"数据源"(DataSource) -- 要操作的数据库,数据是在哪里。
// 这个过程就是所谓的数据库厂商写代码和JDBC进行对接,让数据库厂商实现JDBC提供的 interface
//在 MySQL 中,就需要设定好 MySQL服务器的位置,要访问的数据库的名字,访问的数据库的用户名和密码
DataSource dataSource = new MysqlDataSource();//这个类就来自于驱动包
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/javamysql?characterEncoding=utf8&useSSL=false");//向下转型
//除了要设置URl,还要设置用户名和密码
//用户名
((MysqlDataSource) dataSource).setUser("root");//向下转型
//密码
((MysqlDataSource) dataSource).setPassword("123456");//向下转型
//2.和数据库服务器建立连接
Connection connection = dataSource.getConnection();
//3.构造一个操作数据库的SQL语句
System.out.println("请输入学号: ");
int id = in.nextInt();
System.out.println("请输入姓名: ");
String name = in.next();
String sql = "insert into test values("+ id + ", '" + name + "')";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.执行SQL ,把刚才解析好的语句发给数据库服务器
//返回值是一个int 类型, 表示这个操作影响了几行数据
int n = preparedStatement.executeUpdate();
System.out.println("成功插入" + n + "条记录");
//5.执行完毕,进行收尾操作,释放前面创建的各种资源
//主要是释放 语句对象 和 连接对象, DataSource不必释放
preparedStatement.close();
connection.close();
}
}
运行一下:
切到控制台查看一下:
拼接字符串,就可以完成输入的预期效果,这种做法虽然可行。但不推荐,理由如下:
1)看起来非常奇怪,可读性不高
2)不太安全, 可能会引起 sql 注入攻击这样的漏洞
比较推荐通过PreparedStatement 提供的 API 来完成动态内容的设置
? 就是占位符,需要后续代码做出内容替换
当前这里需要啥类型的数据就使用哪个方法
完整代码:
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;//JDBC 自带的 Interface
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class JDBCDemo {
public static void main(String[] args) throws SQLException {
//接下来通过 JDBC 进行一个简单的 插入数据 的操作
Scanner in = new Scanner(System.in);
//1,创建"数据源"(DataSource) -- 要操作的数据库,数据是在哪里。
// 这个过程就是所谓的数据库厂商写代码和JDBC进行对接,让数据库厂商实现JDBC提供的 interface
//在 MySQL 中,就需要设定好 MySQL服务器的位置,要访问的数据库的名字,访问的数据库的用户名和密码
DataSource dataSource = new MysqlDataSource();//这个类就来自于驱动包
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/javamysql?characterEncoding=utf8&useSSL=false");//向下转型
//除了要设置URl,还要设置用户名和密码
//用户名
((MysqlDataSource) dataSource).setUser("root");//向下转型
//密码
((MysqlDataSource) dataSource).setPassword("123456");//向下转型
//2.和数据库服务器建立连接
Connection connection = dataSource.getConnection();
//3.构造一个操作数据库的SQL语句
System.out.println("请输入学号: ");
int id = in.nextInt();
System.out.println("请输入姓名: ");
String name = in.next();
String sql = "insert into test values(?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id );
preparedStatement.setString(2, name);
//4.执行SQL ,把刚才解析好的语句发给数据库服务器
//返回值是一个int 类型, 表示这个操作影响了几行数据
int n = preparedStatement.executeUpdate();
System.out.println("成功插入" + n + "条记录");
//5.执行完毕,进行收尾操作,释放前面创建的各种资源
//主要是释放 语句对象 和 连接对象, DataSource不必释放
preparedStatement.close();
connection.close();
}
}
运行一下:
切到控制台查看一下:
查询:
ResultSet对象
ResultSet对象被称为结果集,它代表符合SQL语句条件的所有行,并且它通过一套getXXX方法提供了对这些行中数据的访问。
ResultSet里的数据一行一行排列,每行有多个字段,并且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCDemo2 {
public static void main(String[] args) throws SQLException {
//1.创建数据源
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/javamysql?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("123456");
//2.建立连接
Connection connection = dataSource.getConnection();
//3.构造sql
String sql = "select * from test";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
//5.遍历结果集
while (resultSet.next()) {
//针对这里的光标指向的行进行处理
//初始情况下,光标在第一行数据的前一个位置
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("id = " + id + ", name = " + name);
}
//6.释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}