JDBC基础(一)
JDBC
什么是JDBC
今天我来介绍下JDBC. JDBC英文全程Java Database Connectivity.是一个独立于特定DBMS(数据库管理系统), 通用的SQL数据库存储和操作的公共接口集合
。该接口定义在java.sql
和javax.sql
下。
说白了JDBC是一套接口, 规定了访问数据库的规范和标准
JDBC的好处
-
统一数据库访问途径, 方便开发和维护
.JDBC为访问不同数据库提供了统一的途径
.屏蔽了不同数据库系统之间的差异. -
解耦数据库访问, 方便扩展支持不同数据库
。使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统, 具有更好的扩展性, 兼容性。这些驱动由数据库厂商根据JDBC接口约定的规范进行开发.这就像java中的子类实现接口, 这其实是一种面向接口编程的思想.我们使用的时候, 不去关心子类的实现, 屏蔽掉了子类实现细节, 直接new一个子类赋值个接口引用即可引用.
JDBC
JDBC体系结构
JDBC接口包括两个层次
- 面向应用的API
Java API, 抽象接口,供应用程序开发人员使用。 - 面向数据库的API
Java Driver API, 供开发商开发数据库驱动用。
JDBC驱动程序的分类
- 第一类: JDBC-ODBC, Windows实现的为方便
- 第二类: 部分本地API部分Java的驱动程序
第二类
- 第三类: JDBC网络纯Java驱动程序
第三类
- 这种驱动采用中间件的
应用服务器
来访问数据库。应用服务器作为一个到多个数据库的网关,客户端通过它间接联接数据库。应用服务器
通常有自己的网络协议
.Java程序通过JDBC驱动程序将JDBC调用发送给应用服务器, 应用服务器使用本地程序驱动访问数据, 从而完成请求。
例子:举个例子, 比如阿里云提供了访问数据库服务器的接口, 这就是阿里云厂商提供的本地API, 而应用服务器者是买了阿里云的第三方自己去订制开发, 根据需求可以自己定义一套通信协议,并实现JDBC驱动来间接的调用阿里云
- 第四类: 本地协议的存Java驱动协议
- 数据库厂商已经提供了
网络协议
, 用来约定客户端程序通过网络直接与数据库通信- 这类驱动完全使用Java编写, 通过与数据库建立的Socket连接, 采用具体的
网络协议
, 把JDBC调用转换为直接连接的网络调用
第四类
第三和第四种都是走的是网络
.
不同的是前者是通过数据库网关间接和数据库通信, 而不同的数据库网关可能有不同的协议, 后者是直接和数据库建立网络连接, 进行远程接口调用。
第二种走的是数据库本地API, 相当于提供头文件和库直接给你调用
.扩展性不好,对于不同的语言就需要提供另外一套的接口,没有走网络协议的兼容性好
。
第一种的Windows提供的已经淘汰忽略.
JDBC的API构架
JDBC API是一套接口集合
.约定了应用程序能够进行数据库联接、执行SQL语句、并且得到返回结果。
JDBC的API构架
Driver 接口
-
java.sql.Driver
接口是所有JDBC驱动程序要实现的接口。这个接口提供给数据库厂商使用,让数据库厂商根据自己的技术实现这些接口的规范。 - 在程序中不需要直接访问实现了Driver接口的类。而是由驱动管理器去调用这些Driver实现
常用的数据库驱动
- Oracle驱动:
oracle.jdbc.driver.OracleDriver
- Mysql驱动:
com.mysql.jdbc.Driver
JDBC建立和数据库通信和交互的步骤
- 加载驱动并注册
- 获取连接对象
- 获取预编译对象(
代码模板更易读懂
、更高效
(mysql不支持)、防止SQL注入
) - 执行SQL语句
- 释放资源(连接对象、预编译对象、结果集)(有顺序要求,注意异常处理的正确顺序)
引入驱动包,这里我使用Maven引入
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
加载和注册驱动
一、通过Class.forName
通过反射直接使用Class.forName
方式进行加载和注册.
Class.forName("com.mysql.jdbc.Driver");
原理
通过Class.forName直所以能够加载注册驱动, 是因为Mysql驱动的实现类,使用了静态代码块, 并在静态代码块中使用
驱动管理器(DriverMangaer)
进行驱动的注册。如下是Mysql驱动实现的代码
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
二、通过DriverManager自动进行加载和注册.
在JDK6.0, JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了, 我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载.
- 只要一调用这句代码, 就会加载DriverManager,其会去classpath下面扫描数据库驱动, 并自动进行加载和注册.
public static Connection getConn() throws ClassNotFoundException, SQLException {
Connection conn = null;
String url = config.getProperty(JDBCUtil.URL_KEY);
conn = DriverManager.getConnection(
url,
config);
return conn;
}
原理
注意
- 虽然可以这样做, 但是在Web开发中这种方式隐式加载驱动方式不被认可, 我们还是要手动的使用Class.forName来加载驱动
- 主要原因在于Web开发过程中, 我们需要将数据库做成配置文件, 方便部署和维护, 而这时候需要我们在
配置文件
中显示的指定
需要加载的数据库驱动
的全限定类名
.所以不用这种自动加载方式。
Web开发
中我们还需要加入Class.forName.
public static Connection getConn() throws ClassNotFoundException, SQLException {
Connection conn = null;
String url = config.getProperty(JDBCUtil.URL_KEY);
Class.forName(config.getProperty(JDBCUtil.DRIVE_KEY));
conn = DriverManager.getConnection(
url,
config);
return conn;
}
建立连接
一旦加载完驱动, 我们就可以通过驱动和数据库服务器建立远程通信联接, 并获取到该联接对象, 使用如下代码.也可以使用Class.forName的方式,通过反射, 创建Driver对象,并进行注册
public static Connection getConn() throws ClassNotFoundException, SQLException {
Connection conn = null;
String url = config.getProperty(JDBCUtil.URL_KEY);
Class.forName(config.getProperty(JDBCUtil.DRIVE_KEY));
conn = DriverManager.getConnection(
url,
config);
return conn;
}
获取执行体对象或其子对象
我们可以通过联接对象,来获取执行体对象
.通过执行体对象来执行sql语句.一般我们都用预加载执行体对象PrepareStatement
, 而不不用其父类Statement
对象.主要原因在于Statement对象对于存在sql注入的,没有对sql语句进行严格的过滤.
- 创建执行提对象,绑定好预编译sql。
- 设置预编译sql中占位符所对应数据。
@Test
public void exer1() {
// 创建Customer表, 包含id, name, age, gender,birthday。插入数据
Connection conn = null;
PreparedStatement ps = null;
StringBuilder sb = new StringBuilder();
try {
conn = JDBCUtil.getConn();
sb.append("create table if not exists Customer (id int auto_increment, name varchar(20),").
append("age int, gender varchar(3) not null,").
append("birthdate date,").
append("primary key(id)").append(") engine innodb charset utf8");
System.out.println(sb);
PreparedStatement preparedStatement = conn.prepareStatement(sb.toString());
preparedStatement.execute();
sb = new StringBuilder();
sb.append(" insert into Customer (name, age, gender, birthdate) ");
sb.append(" values (?,?,?,?) ");
preparedStatement = conn.prepareStatement(sb.toString());
preparedStatement.setObject(1, "SweetCS");
preparedStatement.setObject(2, 26);
preparedStatement.setObject(3, "男");
preparedStatement.setObject(4, LocalDate.of(1992,1,1).toString());
System.out.println(preparedStatement);
preparedStatement.execute();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
执行查询
调用执行体对象的executeQuery()方法或者executeUpdate()执行sql。
释放资源
- 由于我们是通过网络联接和数据库进行通信,这是属于系统调用, 系统调用JVM并没有权限回收资源, 所以我们需要自己关闭网络资源.
- 既然要关闭资源,在finally语句块中进行关闭是最合适
Connection conn = null;
String url = "jdbc:mysql://127.0.0.1:3306/jdbc";
try {
conn = DriverManager.getConnection(url, "root", "admin");
System.out.println(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JDBCUtil封装(版本一)
只要思路在于,方便后续进行数据库联接。如果我们使用JDBC除了每次肯定要进行的操作主要有四步.
- 数据库驱动的加载
- 数据库连接的建立
- 数据库查询语句的执行
- 资源的释放
除了第三点之外, 其他三个可以说是完全一样的操作,对于重复代码我们要进行封装.
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JDBCUtil {
static Properties config = null;
private static final String URL_KEY = "url";
private static final String DRIVER_CLASS_NAME_KEY = "driverClassName";
private static String configFileName = "config.properties";
public static String getConfigFileName() {
return configFileName;
}
public static void setConfigFileName(String configFileName) {
JDBCUtil.configFileName = configFileName;
}
static {
config = new Properties();
try {
config.load(JDBCTest.class.getResourceAsStream(configFileName));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConn() throws ClassNotFoundException, SQLException {
Class.forName(config.getProperty(DRIVER_CLASS_NAME_KEY));
String url = config.getProperty(JDBCUtil.URL_KEY);
Connection conn = DriverManager.getConnection(
url,
config);
return conn;
}
public static void close(Connection conn) {
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection conn, Statement stmt) {
try {
if (null != stmt) {
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn);
}
}
public static void close(Connection conn, Statement stmt, ResultSet rs) {
try {
if (null != rs) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, stmt);
}
}
}
-
释放顺序问题
。需要注意的是执行体
对象需要在连接对象
之前先释放。 -
字符串复用问题
。字符串最好提取出来方便复用。 -
配置文件字段名规范问题
。该工具类对应config.properties
的数据库配置文件
config.properties
-
乱码问题
。如果需要插入中文文字,记得在配置文件中加入编码声明语句characterEncoding=utf8
.否则会导致存储到数据库中乱码。