JDBC小记

本文最后更新于:2021年10月10日 上午

JDBC小记

前言

昨天刚冲完了jdbc,今天来总结一下

一.JDBC介绍

JDBC (百度百科): Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。

说白了 JDBC就是Java数据库连接,就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句.

二.JDBC原理图

img

三.JDBC的本质:

JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

JDBC到底是什么

SUN公司制定的一套接口(interface),(在java.sql.*;包下有很多接口)

目的:解耦合——>降低程序的耦合度,提高程序的扩展力


**附:**java链接MySQL驱动包最新版下载地址:https://dev.mysql.com/downloads/connector/j/,解压后得到jar库文件,然后在项目中导入该库文件.

博主使用的版本是mysql-connector-java-5.1.47.jar

image-20210918094830349

四.什么是驱动

驱动,是指驱动计算机里软件的程序。驱动程序全称设备驱动程序,是添加到操作系统中的特殊程序,其中包含有关硬件设备的信息。此信息能够使计算机与相应的设备进行通信。驱动程序是硬件厂商根据操作系统编写的配置文件,可以说没有驱动程序,计算机中的硬件就无法工作。

例如:网卡,声卡,显卡等等 必须要安装驱动程序,不然这些硬件就无法正常工作。

五.为什么要面向接口编程?

解耦合:降低程序的耦合度,提高程序的扩展力
多态机制就是非常典型的:面向抽象编程(不要面向具体编程)

Animal a = new Cat();
Animal a = new Dog();
//喂养的方法
public void feed(Animal a ){ //面向父类型编程

}
不建议:
Dog d = new Dog();
Cat c = new Cat();

思考:为什么sun公司要制定一套JDBC接口呢?
因为每个数据库的实现原理不一样,oracle、mysql、ms sqlserver都有自己的原理,每个数据库产品都有自己独特的实现原理.

六.JDBC核心类(接口)介绍

JDBC中的核心类有:DriverManagerConnectionStatement,和ResultSet

DriverManger(驱动管理器)的作用有两个:

  • 注册驱动:这可以让JDBC知道要使用的是哪个驱动;

    DriverManager.registerDriver(new com.mysql.jdbc.Driver());

  • 获取Connection:如果可以获取到Connection,那么说明已经与数据库连接上了。

    DriverManager.getConnection(url,username,password)

Connection对象表示连接,与数据库的通讯都是通过这个对象展开的:

  • Connection最为重要的一个方法就是用来获取Statement对象;

    Statement stmt = con.createStatement();

  • Statement是用来向数据库发送SQL语句的,这样数据库就会执行发送过来的SQL语句

  • void executeUpdate(String sql):执行更新操作(insert、update、delete等);

  • ResultSet executeQuery(String sql):执行查询操作,数据库在执行查询后会把查询结果,查询结果就是ResultSet;

ResultSet对象表示查询结果集,只有在执行查询操作后才会有结果集的产生。结果集是一个二维的表格,有行有列。操作结果集要学习移动ResultSet内部的“行光标”,以及获取当前行上的每一列上的数据:

  • boolean next():使“行光标”移动到下一行,并返回移动后的行是否存在;

    rs.next();//光标移动到第一行
    rs.getInt(1);//获取第一行第一列的数据
  • XXX getXXX(int col):获取当前行指定列上的值,参数就是列数,列数从1开始,而不是0。

    //常用的方法
    Object getObject(int col)
    String getString(int col)
    int getInt(int col)
    double getDouble(int col)

七.JDBC编程6步(超级重要,需要背会!!!!)

第1步:注册驱动 (只做一次)

第2步:获取数据库连接对象(Connection)

第3步:获取数据库操作对象(Statement)

第4步:执行sql语句(增删改查)

第5步:处理查询结果集(ResultSet)

第6步:释放资源


第1步:注册驱动 (只做一次)

作用:告诉java程序,即将要连接的是哪个牌子的数据库.

下载

注册驱动的两种方法 (例如,注册MySQL的数据库驱动) :

// 第一种:  推荐
Class.forName(com.mysql.jdbc.Driver);

Class.forName是把这个类加载到JVM中,加载的时候,就会执行其中的静态初始化块,完成驱动的初始化的相关工作。


// 第二种:  不推荐
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// DriverManager类的registerDriver()方法的参数是java.sql.Driver,但java.sql.Driver是一个接口,实现类由mysql驱动来提供,mysql驱动中的java.sql.Driver接口的实现类为com.mysql.jdbc.Driver

上面代码虽然可以注册驱动,但是出现硬编码(代码依赖mysql驱动jar包),如果将来想连接Oracle数据库,那么必须要修改代码的。并且其实这种注册驱动的方式是注册了两次驱动!


通过初始化驱动类com.mysql.jdbc.Driver,该类就在 mysql-connector-java-5.0.8-bin.jar中。如果你使用的是oracle数据库那么该驱动类将不同。

注意:Class.forName需要捕获ClassNotFoundException.

try {
    Class.forName("com.mysql.jdbc.Driver");		
    } catch (ClassNotFoundException e) { 				
     e.printStackTrace();
    }

第2步:获取数据库连接对象(Connection)

表示jvm的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完一定要关闭

可以使用 DriverManager.getConnection()方法建立连接。根据传入参数的不同,有三种重载的DriverManager.getConnection()方法:

  • getConnection(String url)
  • getConnection(String url, Properties prop)
  • getConnection(String url, String user, String password)

还可以在url中提供参数

jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF8

useUnicode参数指定这个连接数据库的过程中,使用的字节集是Unicode字节集;

characherEncoding参数指定穿上连接数据库的过程中,使用的字节集编码为UTF-8编码。请注意,mysql中指定UTF-8编码是给出的是UTF8,而不是UTF-8。

这里每个格式都需要一个数据库URL。 数据库URL是指向数据库的地址。制定数据库URL是建立连接相关联的大多数错误问题发生的地方。各数据库对应的URL如下所示:

下载

 假设我们现在需要连接MySQL数据库,格式为:jdbc:mysql://hostname:port/datebaseName。我们需要的信息是hostname主机名和端口号,一般默认为localHost:3306;还需要datebaseName数据库名,假设为mydb;当然还有URL格式未包含的也是必须的信息:连接数据库的用户名和密码,假设为root和123456。那么就有URL:

  • ```
    1 String url = “jdbc:mysql//localhost:3306/mydb”;

    
      下面分别使用四种方法来实现:
    
    - 使用一个URL作为参数的方式:需要将username+password以参数的形式放到URL中,但是每种数据库的放置都不太相同
    
      ```java
      //连接mysql的纯URL
      String url = "jdbc:mysql//localhost:3306/mydb?username=root&password=123456";
      Connection conn  = DriverManager.getConnection(url,p);
      //连接Oracle的纯URL String url = "jdbc:oracle:thin:root/123456@192.0.0.10:1521:mydb";Connection conn  = DriverManager.getConnection(url);
  • 使用URL、properties作为参数的方式:即需要将username和password以键值对形式存放在properties对象中作为参数

    //MySql
    String url = "jdbc:mysql//localhost:3306/mydb";
    Properties p = new Properties();
    p.put("username","root");
    p.put("password","123456");
    Connection conn  = DriverManager.getConnection(url,p);
  • 使用URL、username、password三个参数分开的方式(推荐

    String url = "jdbc:mysql//localhost:3306/mydb";
    String username =  "root";
    String password  = "123456";
    Connection conn = DriverManager.getConnection(url,username,password);
  • 利用java.sql.Drivermanager类中的getConnection()方法与数据库建立连接。

Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?characterEncoding=UTF-8", "root", "123456");

// 说明:
// DriverManager.getConnection(“jdbc:mysql://数据库地址:端口号/数据库名”,”用户名”, “密码”);
// 数据库服务端的IP地址:127.0.0.1 (这是本机,如果连接其他电脑上的数据库,需填写相应的IP地址)
// 数据库的端口号: 3306 (mysql专用端口号)
// 数据库名称 mydb(根据你自己数据库中的名称填写)
// 编码方式 UTF-8
// 账号 root
// 密码 123456(如果你在创建数据库的时候没有使用默认的账号和密码,请填写自己设置的账号和密码)

注意: Connection是与特定数据库连接回话的接口,使用的时候需要导包,而且必须在程序结束的时候将其关闭。getConnection方法也需要捕获SQLException异常。

因为在进行数据库的增删改查的时候都需要与数据库建立连接,所以可以在项目中将建立连接写成一个工具方法,用的时候直接调用即可:

/**
  * 取得数据库的连接
  * @return 一个数据库的连接
  */
public static Connection getConnection(){
		Connection conn = null;
		 try {
			 	//初始化驱动类com.mysql.jdbc.Driver
	            Class.forName("com.mysql.jdbc.Driver");
	            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?characterEncoding=UTF-8","root", "123456");
	            //该类就在 mysql-connector-java-5.0.8-bin.jar中,如果忘记了第一个步骤的导包,就会抛出ClassNotFoundException
	        } catch (ClassNotFoundException e) { 				
	            e.printStackTrace();
	        }catch (SQLException e) {							
	            e.printStackTrace();
	        }
		 return conn;
	}

第3步:获取数据库操作对象(Statement或者PreparedStatement)

SQL语句的执行对象按理说有Statement和PreparedStatement两个,但我们一般都不会去使用Statement,先看下两者的基本描述:

  • Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句,即静态SQL语句。
  • PreparedStatement 继承于Statement。实例包含已编译的 SQL 语句,这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX() 方法来提供

  简言之,Statement执行静态SQL语句,而它的子类PreparedStatement执行预编译SQL,即可传入参数。两者相比之下,PreparedStatement有以下优势

  • 预编译处理,可动态执行SQL语句。很明显,SQL语句的预编译,使用占位符?去代替未知数据,因而一个句子可以执行多种不同的SQL,而Statement需要重新书写SQL语句,笨重。
  • 速度快,执行效率高。SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。我们要利用预编译的特性

(1). 利用java.sql.Connection创建用于执行SQL语句的Statement。

Statement stmt = connection.createStatement();

注意:使用Statement会引起sql注入问题,在实际开发中很少使用,用的的更多的是它的子类PreparedStatement

比如sql注入问题如下:

String sql = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"

验证需要用户输入用户名和密码,正确则执行查询语句(登录),但如果这样输入:

userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";

那么执行语句就变成了:

1 String sql = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"

这样,where语句恒为真,就能实现无账号登录。此外便可能被恶意修改甚至删除数据表。然而使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统(eg:MySQL)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,且占位符?不允许多值,只能填入一个值,因此就算参数中含有破坏性的指令,也不会被数据库所运行。

(2) .使用Connection对象的PreparedStatement(sql)方法进行获取:

String sql  = "select * from user where name=? and ange=?";//预处理,需要我们先写好sql语句
PreparedStatement ps = conn.preparedStatement(sql);//conn是连接对象,参数为sql语句

第4步:执行sql语句(增删改查)

SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL

sql语句有增删查改等几种类型,所以执行方法有以下三种:

  • execute():执行SQL语句,可以是任何种类的 SQL 语句。返回值是boolean类型。
  • executeQuery()执行SQL语句查询,查询结果返回为**ResultSet 对象**。
  • executeUpdate()执行更新语句。该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERTUPDATEDELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。返回值是int。

  例如本例中的语句是查询语句,所以执行代码为:

  • ```java
    //执行查询语句,并把结果集返回给集合ResultSet
    ResultSet rs = ps.executeQuery();
    
    ### **第5步:处理查询结果集(ResultSet)**
    
    如果返回值是boolean或者int很好处理,但如果是查询结果集ResultSet对象,一般使用while循环来处理: 
    
    `  ResultSet` 对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。`next()` 方法将光标移动到下一行;因为该方法在 `ResultSet` 对象没有下一行时返回 `false`,所以可以在 `while` 循环中使用它来**迭代**结果集。另外,可以使用ResultSet对象的getXXX(int columnIndex)获得游标所在行指定列的值。原理如下图所示:
    
    ![下载 (1)](https://i.loli.net/2021/09/21/vhreUMbBG5En1KH.png)
    
    所以,本例的结果集处理如下:
    
    ```java
    1 while(rs.next()){
    2        system.out.println(rs.getString(1));
    3        system.out.println(rs.getInt(2));    
    4 }

第6步:释放资源

在JDBC程序结束之后,显式地需要关闭与数据库的所有连接以结束每个数据库会话。 但是,如果在编写程序中忘记了关闭也没有关系,Java的垃圾收集器将在清除过时的对象时也会关闭这些连接。

  依靠垃圾收集,特别是数据库编程,是一个非常差的编程实践。所以应该要使用与连接对象关联的close()方法关闭连接。要确保连接已关闭,可以将关闭连接的代码中编写在“finally”块中。 一个finally块总是会被执行,不管是否发生异常。

conn.close();

八.JDBCUtils工具类

因为传统JDBC的开发,注册驱动,获得连接,释放资源这些代码都是重复编写的。所以可以将重复的代码提取到一个类中来完成

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
 * JDBC的工具类
 * @author CodeAnime
 *
 */
public class JDBCUtils {
	private static final String driverClassName;
	private static final String url;
	private static final String username;
	private static final String password;
	
	static{
		driverClassName="com.mysql.jdbc.Driver";
		url="jdbc:mysql:///web_test3";
		username="root";
		password="123456";
	}

	/**
	 * 注册驱动的方法
	 */
	public static void loadDriver(){
		try {
			Class.forName(driverClassName);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获得连接的方法
	 */
	public static Connection getConnection(){
		Connection conn = null;
		try{
			// 将驱动一并注册:
			loadDriver();
			// 获得连接
			conn = DriverManager.getConnection(url,username, password);
		}catch(Exception e){
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * 释放资源的方法
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		// 资源释放:
		if(rs != null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

测试案例:导入工具类,查询用户信息

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import org.junit.Test;
import com.xdr630.jdbc.utils.JDBCUtils;

/**
 * JDBC工具类的测试
 * @author xdr
 *
 */
public class JDBCDemo3 {
	@Test
	/**
	 * 查询操作:使用工具类
	 */
	public void demo1(){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user";
			// 执行查询:
			rs = stmt.executeQuery(sql);
			// 遍历结果集:
			while(rs.next()){
				System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 释放资源:
			JDBCUtils.release(rs, stmt, conn);
		}
	}
}

另一种写法:

jdbc.properties属性文件

注意:属性配置文件必须以properties结尾,且存储在src源目录下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
user=root
password=123456

JDBCUtils.java工具类

public class JDBCUtil(){
    //类文件属性,可以在类文件所有的方法中使用
    private Connection conn = null;  
    private PreparedStatement ps = null;
    
    /**
     * 在当前类文件第一次被加载到JVM时,JVM将会自动调用当前类文件静态语句块
     */
    static {
        //1. 注册数据库服务器提供的Driver接口实现类
        try{
            ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
            String driver = bundle.getString("driver");
            Class.forName(driver);
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }
    }
    
    /**
     * 封装Connection对象创建细节
     * @return 数据库连接的对象
     */
     //这里我们选择抛出异常,而不是自行内部处理。是让调用此方法的人知道出现异常时的控制台出现的异常信息
    public Connection creatConnection() throws SQLException {
        //采用资源绑定器来绑定属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        String url = bundle.getString("url");
        String username = bundle.getString("username");
        String password = bundle.getString("password");
        //2. 创建一个连接通道交给Connection接口的实例对象[Connection]管理
        conn = DriverManager.getConnection(url,username,password);
        return conn;
    }
    
    /**
     *  封装PreparedStatement对象创建细节
     * @param sql sql语句
     * @return  
     */
    public PreparedStatement createStatement(String sql) throws SQLException {
        Connection conn = creatConnection();
        //3. 创建一个交通工具交给 PreparedStatement 接口的实例对象[PreparedStatement]管理
        ps = conn.prepareStatement(sql);
        //4. 由交通工具在Java工程与数据库服务器之间进行传输,推送SQL命令并带回执行结果
        return ps;
    }
    
    /**
     * 封装PreparedStatement与Connection对象销毁细节
     */
    public void close(){
        if (ps != null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 封装PreparedStatement与Connection与ResultSet对象销毁细节
     * @param rs 查询结果集
     */
    public void close(ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        close();
    }
}

JDBCTest01.java测试

public class JDBCTest01 {
    public static void main(String[] args) {
    	//创建JDBC工具类对象
        JdbcUtil util = new JdbcUtil();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            String sql = "select * from studnet where studentid = ?";
            //这里由于在工具类中是抛出异常的,那么在调用时再抛出异常给JVM虚拟机就显然不太合理了,此时我们选择try...catch...内部处理
            ps = util.createStatement(sql);
            ps.setString(5,"田七");
            rs = ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getInt("studentid") + rs.getString("studnetname"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
        	//在finally语句块中的语句必定执行
            util.close(rs);
        }
    }
}

参考文章:


本文作者: CodeAnime
本文链接: https://codeanime.cc/JDBC%E5%B0%8F%E8%AE%B0.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!