Java连接数据库——JDBC
一、概念
1.1 JDBC是什么
JDBC是sun公司制定的一套接口,JDBC是用于Java编程语言和数据库之间连接的标准Java API。
也就是说使用JAVA语言连接数据库进行操作,就需要使用JDBC API。
统一的JDBC API接口,屏蔽了底层数据库的细节,可以使用一致性的编码(跨数据库)对数据库进行操作。
通过JDBC将JAVA应用于数据库访问连接进行解耦,可以相互独立发展,又能够结合使用。
1.2 JDBC的设计
因为业界存在许多不同的数据库,且它们所使用的协议也各不相同。因此所有的数据库供应商和开发人员认为,如果Java能够为SQL访问提供一套“纯”Java API ,同时提供一个驱动管理器,以允许第三方驱动程序可以连接到特定的数据库,那它就会非常有用。这样数据库供应商就可以提供自己的驱动程序,将其插入到驱动管理器中。这将成为一种向驱动管理器注册第三方驱动程序的简单机制。只要是根据API编写的程序都可以与驱动程序进行通信,而驱动管理器则通过驱动程序与实际的数据库通信。
1.3 数据库驱动程序
“设备驱动程序”是一种可以使计算机和设备通信的特殊程序,可以说相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。 因此,驱动程序也称为“硬件和系统之间的桥梁”。
数据库驱动程序都是以jar包的形式存在,jar包当中由很多class文件,这些class文件就是jdbc接口的实现类,数据库驱动就是实现该JDBC接口的实现类,
例如mysql,只需要去官网下载mysql的jar包,导入就可以使用。
mysql驱动下载
1.4 模拟连接
/*
jdbc接口
sun公司制定接口
*/
public interface JDBC(){
//连接数据库的方法,由数据库厂家实现
void getConnection();
}
mysql驱动
public class Mysql implements JDBC{
public void getConnection(){
System.out.println("mysql数据库连接成功")
}
}
SqlServer驱动
public class SqlServer implements JDBC{
public void getConnection(){
System.out.println("SqlServer数据库连接成功")
}
}
Java程序员
public class JDBCUtils{
public static void main(String[] args) {
//直接new或者通过反射创建对象
//JDBC jdbc=new Mysql();
Class cl=Class.forName("Mysql");
JDBC jdbc=(JDBC)cl.newInstance();
//面向接口调用,连接成功
jdbc.getConnection();
}
}
二、JDBC编程
第一步:注册驱动(告诉Java程序,即将要连接的是哪个品牌的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,进程之间的通信,使用完之后关闭通道。)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行SQL语句(DQL DML….)
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)
第六步:释放资源(使用完资源之后要关闭资源。从小到大依次关闭。)
2.1 注册驱动
注册驱动之前要先导入JAR,才可以使用。驱动注册有三种形式,这里介绍两种方式
//第一种通过类加载的方式注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//第二种调用DriverManager.registerDriver()
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
查看MySQL驱动中的源码,注意在com.mysql.cj.jdbc包下,而不是com.mysql.jdbc包下的。
package com.mysql.cj.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!");
}
}
}
在类加载时,会执行静态代码块中的代码。因此Class.forName(“com.mysql.jdbc.cj.Driver”);也会去会调用java.sql.DriverManager.registerDriver(new Driver()); 完成了驱动的注册,两者在本质上一样,但类加载方式更常用。
DriverManager驱动管理器:
驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中,接受DriverManager的管理。
public class DriverManager{
//注册了JDBC驱动的集合
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
//先调用此方法
public static void registerDriver(java.sql.Driver driver)
throws SQLException {
//driverAction 为null, 连接建立后不做任何事, 有的数据库需要进一步的操作
registerDriver(driver, null);
}
public static void registerDriver(java.sql.Driver driver,DriverAction da)
throws SQLException {
if (driver != null) {
// 将 Driver 包装成 DriverInfo 装入集合
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
}
2.2 获取连接
DriverManager负责驱动程序管理,数据库驱动则是为了应用程序服务的,所以DriverManager的重要任务就是提供连接的获取。
通过getConnection方法获得Connection对象
/**
* Connection表示与特定数据库的连接(会话)。
*/
public interface Connection extends Wrapper, AutoCloseable

三个公共的getConnection方法并没有什么太多逻辑,只是参数形式的适配,方法内部调用的都是私有的getConnection()方法。
//下面只保留了部分代码,
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//遍历已加载的已注册驱动程序以尝试建立连接。
for (DriverInfo aDriver : registeredDrivers) {
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
//如果建立连接成功,就返回这个连接
println("getConnection returning " +.driver.getClass().getName()); return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
}
示例
//通用:jdbc:mysql://ip地址:3306/数据库的名称
//如果连接的时本地数据库服务并且端口号是3306时有一种简写的方式:jdbc:mysql:///数据库的名称
//在URL中也可以加入一些参数例如编码格式,时区...
//jdbc:mysql://ip地址:3306/数据库的名称?characterEncoding=UTF8&serverTimezone=GMT
String url="jdbc:mysql://IP:端口号/数据库名称";
String user="root";
String password="123456";
Connection conn=DriverManager.getConnection(String url,String user,String password);
2.3 获取数据库操作对象
数据库操作对象也就是专门执行sql语句的对象
主要有两种, statement 和 prepareStatement. PreparedStatement继承自Statement,两者都是接口。区别是:PreparedStatement是预编译的(mysql提供的能力),比Statement效率高,可以使用占位符,可防止SQL注入。
这里先介绍statement ,后面介绍prepareStatement。
//用于执行静态SQL语句并返回其生成的结果的对象。
public interface Statement extends Wrapper, AutoCloseable
示例
//调用Connection中的createStatement() 创建一个Statement对象
Statement stmt =conn.createStatement();
2.4 执行SQL语句
public interface Statement extends Wrapper, AutoCloseable{
//执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE语句,专门执行DML(增删改)语句。
//返回结果是影响数据库中的记录条数
int executeUpdate() throws SQLException;
//执行给定的SQL语句,select语句,专门执行DQL语句。返回单个ResultSet对象。
ResultSet executeQuery() throws SQLException;
}
示例
String sql = "insert into user(uaername,password) value('zzrt','123456789')";
int i = stmt.executeUpdate(sql) ;//执行DML语句
2.5 处理查询结果集
只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。
/*表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
*ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前。 next方法将光标移动到下一行,并 *且由于在ResultSet对象中没有更多行时返回false ,因此可以在while循环中使用循环来遍历结果集。
*/
public interface ResultSet extends Wrapper, AutoCloseable{
//将光标从当前位置向前移动一行,如果走到的位置有数据返回true,如果指向的位置没有数据返回false。
boolean next()
//根据当前行的第几列,读取数据,都以字符串的形式取出
//行的第几列,从1开始 int columnIndex
String getString(int columnIndex)
//根据当前行的列,读取数据,都以字符串的形式取出
//列的名称,查询结果表中的列名 String columnIndex
String getString(String columnIndex)
//还可以根据特定类型取出,getInt() getDouble()
}

示例:
//专门执行DQL的方法
ReslutSet rest=stmt.executeQuery(sql);
//方式一,使用列所在的数字,1:第一列,2第二列
while(rest.next()){
String username=rest.getString(1);
String password=rest.getString(2);
System.out.println(username+password);
}
//方式二:使用列名
while(rest.next()){
String username=rest.getString("username");
String password=rest.getString("password");
System.out.println(username+password);
}
2.6 释放资源
为了保证资源一定释放,在finally语句中关闭,从小到大依次关闭,分别对其try,catch,
if (rest != null) {
try {
rest.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
三、SQL注入
3.1 SQL注入现象
拼接sql语句时,使用了用户提供的sql关键字,并且这些关键字参与了sql语句的编译,导致原sql语句的含义被扭曲,进而达到sql注入。
例如,
String username="’ or 1 = 1 --";
String password="";
String sql = "select * from user_table where username=
' " + userName+" and password='"+password+"'";
//拼接后sql语句会变成
//select * from user_table where username=' ' or 1 = 1 -- and password=''
这样的sql语句永远都可以正确执行,如果涉及删除操作,后果不堪设想。
3.2 解决SQL语句
PreparedStatement继承自Statement,PreparedStatement是预编译的(mysql提供的能力),比Statement效率高,可以使用占位符,可防止SQL注入。

//预先对SQL的框架进行编译,然后再对SQL语句传入值。
public interface PreparedStatement extends Statement{
//parameterIndex -- 第一个参数是1,第二个是2,...
//x : 参数值
void setString(int parameterIndex, String x)
}
示例
// ?表示占位符,sql语句框架
String sql="select *from user where username=?"
//获取预编译的数据库操作对象,发送sql语句框架,进行预编译
PreparedStatement ps =conn.PrepareStatement(sql);
//给占位符传值,第一个 ? 下标是1,以此类推
ps.setString(1,"zhangsan");
//执行sql
rest = ps.executeQuery();
四、完整的代码
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rest=null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
String url="jdbc:mysql:///school? useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT";
String user="root";
String password="ZXCVBNM0000";
conn=DriverManager.getConnection( url, user, password);
// sql语句框架
String sql="select uid,age from student where name=?";
//获取预编译的数据库操作对象,发送sql语句框架,进行预编译
ps =conn.prepareStatement(sql);
//传入参数
ps.setString(1,"灰子");
//执行sql
rest = ps.executeQuery();
//处理查询结果集
while(rest.next()){
String uid=rest.getString("uid");
String age=rest.getString("age");
System.out.println(uid+age);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (rest != null) {
try {
rest.close();
} catch (SQLException e) {}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
}
参考文章
JDBC设计理念浅析