前言
- 大二上学期跟着学校里面的老师学JavaSE的时候写了一篇非常垃圾的JDBC的文章JDBC,那时候自认为自己对JDBC的认识已经很高了,认为它很简单,现在再回头看,发现自己那时候完全是井底之蛙,所以我觉得得再写一篇文章深入谈谈JDBC,算是给自己的黑历史抹白吧。
- 我之前写的那篇jdbc文章,使用
statement
操作sql
语句会存在SQL注入
问题,之后会再写一篇文件谈谈这个问题 - 鉴于之前写了关于MySQL事务机制的文章,之后,我也会谈谈JDBC事务机制的使用
JDBC本质
JDBC,本质上就是一套接口,是SUN公司制定的一套接口(interface
),包名为:java.sql.*
(这个软件包下有很多接口)
为什么面向接口编程
我们先来看看什么是面向接口编程:
接口都有调用者和实现者,面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
- 解耦合:
- 降低程序的耦合度
- 提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
思考
现在思考:为什么SUN公司制定一套JDBC接口呢?
- 因为每一个数据库的底层实现原理都不一样。
- Oracle数据库有自己的原理。
- MySQL数据库也有自己的原理。
- MS SqlServer数据库也有自己的原理。
每一个数据库产品都有自己独特的实现原理,如果我们不写一套接口,那么我们这些程序员对同一套Java程序,需要准备几套面向不同数据库的Java代码,程序员会累死在重复且无意义的工作上。
而面向接口编程可以实现一个规范,从而让我们java程序员写一套java代码,适配不同的数据库,达到解放程序员的效果
三方模拟JDBC本质
我们扮演SUN公司,Java程序员,数据库厂商这三方来模拟一下JDBC
SUN公司
/*
SUN公司负责制定这套JDBC接口。
*/
public interface JDBC{
/*
连接数据库的方法。
*/
void getConnection();
}
数据库厂商
数据库厂商对SUN公司制定出的接口的实现类,我们称之为驱动(Driver)
MySQL
/*
MySQL的数据库厂家负责编写JDBC接口的实现类
*/
public class MySQL implements JDBC{
public void getConnection(){
// 具体这里的代码怎么写,对于我们Java程序员来说没关系
// 这段代码涉及到mysql底层数据库的实现原理。
System.out.println("连接MYSQL数据库成功!");
}
}
Oracle数据库厂商
/*
Oracle的数据库厂家负责编写JDBC接口的实现类
*/
public class Oracle implements JDBC{
public void getConnection(){
System.out.println("连接Oracle数据库成功!");
}
}
Java程序员
显然,java程序员在操作MySQL数据库时,需要先把MySQL数据库厂商的对JDBC的实现类的jar包导入工程,也就是需要先把mysql驱动导入工程,不然接口的方法没有实现类,程序自然无法执行。
同理,java程序员在操作Oracle数据库时,也需要先导入Oracle驱动
/*
Java程序员角色。
不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码。
面向接口编程,面向抽象编程,不要面向具体编程。
*/
import java.util.*;
public class JavaProgrammer
{
public static void main(String[] args) throws Exception{
// JDBC jdbc = new MySQL();
// JDBC jdbc = new SqlServer();
// 创建对象可以通过反射机制。
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String className = bundle.getString("className");
Class c = Class.forName(className);
JDBC jdbc = (JDBC)c.newInstance();
// 以下代码都是面向接口调用方法,不需要修改
jdbc.getConnection();
}
}
三方之间的关系图
JDBC编程六步法
第一步:注册驱动
- 作用:告诉Java程序,即将要连接的是哪个品牌的数据库
Driver driver = new com.mysql.cj.jdbc.Driver(); // 使用多态,父类型引用指向子类型对象
DriverManager.registerDriver(driver);
上面代码可以合并为下面一行
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
注册驱动这里还可以再进行代码优化,我们就在下面JDBC工具类,DBUtils
里面进行
第二步:获取连接
- 表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的操作,使用完之后一定要关闭通道
获取连接的url需要遵循通信协议
- 通信协议是通信之前就提前定好的数据传送格式
- 它包括数据包具体怎样传数据,其格式是提前定好的
url案例
jdbc:mysql://127.0.0.1:3306/swu
swu是西南大学的英文简写,我自己设置的数据库的名字
-
jdbc:mysql://
-------->协议 -
127.0.0.1
------------->IP地址 -
3306/mysql
----------->数据库端口号 -
swu
--------------------->具体的数据库名
String url = "jdbc:mysql://192.168.151.9:3306/bjpowernode";
String user = "root";
String password = "981127";
String connection = DriverManager.getConnection(url,user,password);
注意
不同数据库的通信的协议不同,例如Oracle数据库的通信协议如下:
jdbc:oracle:thin:@localhost:1521:数据库名
第三步:获取数据库操作对象
- 专门执行sql语句的对象
Statement stmt = connection.createStatement();
一般较少使用statement执行SQL语句,因为有SQL注入问题。
我们一般使用PrepareStatement
对象,之后会谈到
第四步:执行SQL语句
JDBC中的sql
语句不需要提供分号结尾
String sql = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";
// 专门执行DML语句的(insert delete update)
// 返回值是“影响数据库中的记录条数”
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "保存成功" : "保存失败");
第五步:处理查询结果集
- 只有当第四步执行的是
select
语句的时候,才要处理结果集
我们用ResultSet对象来接收执行sql语句的返回值后,这个数据集是什么样子呢?
如下图:
这里详细谈谈ResultSet的nextLine()
方法,getString()
方法
nextLine()
方法判断下一行是否有数据,它类似于一个浮标的移动,如果下一行有数据,则返回true
,否则返回false
getString()
方法:不管数据库中的数据类型是什么,都以String
的形式取出,同理:还有getInt,getDouble等
当然,如果在()内输入字段名,则会以列的名字获得该数据,输入单纯的数字n,则取出第n列的数据
注意:
- JDBC中所有下标从1开始,不是从0开始。
- 列名称不是表中的列名称,是查询结果集的列名称
第六步:释放资源
- 使用完资源之后一定要关闭资源。
- Java和数据库属于进程间的通信,开启之后一定要关闭。
- 不关闭通道,时间一长可能出现问题
为了保证资源一定释放,在finally
语句块中关闭资源,并且要遵循从小到大依次关闭
try{
if(stmt != null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
JDBC工具类的写法
我们通常创建一个类名为DBUtils
的工具类来封装JDBC中注册驱动,获取连接,释放资源部分的代码,提高代码复用性
import java.sql.*;
/**
* JDBC工具类,简化JDBC编程。
*/
public class DBUtil {
/**
* 工具类中的构造方法都是私有的。
* 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用。
*/
private DBUtil() {
}
// 静态代码块在类加载时执行,并且只执行一次。
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
*
* @return 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/swu", "root", "password");
}
/**
* 关闭资源
* @param conn 连接对象
* @param ps 数据库操作对象
* @param rs 结果集
*/
public static void close(Connection conn, Statement ps, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
ResourceBundle绑定属性配置文件
资源配置文件准备
文件名:jdbc.properties
内容:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://主机IP/数据库名
user=数据库用户名
password=数据库用户密码
绑定
使用ResourceBundle的getBundle
方法,传递参数为资源配置文件(.properties后缀文件)名字,不包括后缀
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");