自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

看完這篇文章,你也可以手寫MyBatis部分源碼(JDBC)

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù)
ODBC(Open Database Connectivity,開放式數(shù)據(jù)庫(kù)連接),是微軟在Windows平臺(tái)下推出的。使用者在程序中只需要調(diào)用ODBC API,由 ODBC 驅(qū)動(dòng)程序?qū)⒄{(diào)用轉(zhuǎn)換成為對(duì)特定的數(shù)據(jù)庫(kù)的調(diào)用請(qǐng)求。

一、持久化機(jī)制

持久化(persistence): 把數(shù)據(jù)保存到可調(diào)電式存儲(chǔ)設(shè)備中以供之后使用。大多數(shù)情況下,特別是企業(yè)級(jí)應(yīng)用,數(shù)據(jù)持久化意味著將內(nèi)存中的數(shù)據(jù)保存到硬盤上加以”固化”,而持久化的實(shí)現(xiàn)過(guò)程大多通過(guò)各種關(guān)系數(shù)據(jù)庫(kù)來(lái)完成。就是將內(nèi)存中的數(shù)據(jù)存儲(chǔ)在關(guān)系型數(shù)據(jù)庫(kù)中,當(dāng)然也可以存儲(chǔ)在磁盤文件、XML數(shù)據(jù)文件中。 而在 Java中,數(shù)據(jù)庫(kù)存取技術(shù)只能通過(guò) JDBC 來(lái)訪問數(shù)據(jù)庫(kù)。

JDBC 訪問數(shù)據(jù)庫(kù)的形式主要有兩種:

  1. 直接使用 JDBC 的 API 去訪問數(shù)據(jù)庫(kù)服務(wù)器 (MySQL/Oracle)。
  2. 間接地使用 JDBC 的 API 去訪問數(shù)據(jù)庫(kù)服務(wù)器,使用第三方O/R Mapping工具,如 Hibernate, MyBatis 等。(底層依然是 JDBC ,JDBC 是 Java 訪問數(shù)據(jù)庫(kù)的基石,其他技術(shù)都是對(duì) JDBC 的封裝)

二、JDBC概述

JDBC (Java DataBase Connectivity)是一個(gè)獨(dú)立于特定數(shù)據(jù)庫(kù)管理系統(tǒng)、通用的SQL數(shù)據(jù)庫(kù)存取和操作的公共接口(一組API),定義了用來(lái)訪問數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)Java類庫(kù),(java.sql,javax.sql)使用這些類庫(kù)可以以一種標(biāo)準(zhǔn)的方法、方便地訪問數(shù)據(jù)庫(kù)資源。 JDBC為訪問不同的數(shù)據(jù)庫(kù)提供了一種統(tǒng)一的途徑,為開發(fā)者屏蔽了一些細(xì)節(jié)問題。 JDBC 的目標(biāo)是使 Java 程序員使用 JDBC 可以連接任何提供了 JDBC 實(shí)現(xiàn)(驅(qū)動(dòng)程序)的數(shù)據(jù)庫(kù)系統(tǒng),這樣就使得程序員無(wú)需對(duì)特定的數(shù)據(jù)庫(kù)系統(tǒng)的特點(diǎn)有過(guò)多的了解,從而大大簡(jiǎn)化和加快了開發(fā)過(guò)程。

簡(jiǎn)單來(lái)說(shuō),JDBC 本身是 Java 連接數(shù)據(jù)庫(kù)的一個(gè)標(biāo)準(zhǔn),是進(jìn)行數(shù)據(jù)庫(kù)連接的抽象層,由 Java編寫的一組類和接口組成,接口的實(shí)現(xiàn)由各個(gè)數(shù)據(jù)庫(kù)廠商來(lái)完成。

JDBC編寫的步驟如下:

ODBC(Open Database Connectivity,開放式數(shù)據(jù)庫(kù)連接),是微軟在Windows平臺(tái)下推出的。使用者在程序中只需要調(diào)用ODBC API,由 ODBC 驅(qū)動(dòng)程序?qū)⒄{(diào)用轉(zhuǎn)換成為對(duì)特定的數(shù)據(jù)庫(kù)的調(diào)用請(qǐng)求。

三、獲取數(shù)據(jù)庫(kù)連接

3.1、加載注冊(cè)驅(qū)動(dòng)

java.sql.Driver 接口是所有 JDBC 驅(qū)動(dòng)程序需要實(shí)現(xiàn)的接口。這個(gè)接口是提供給數(shù)據(jù)庫(kù)廠商使用的,不同數(shù)據(jù)庫(kù)廠商提供不同的實(shí)現(xiàn)。在程序中不需要直接去訪問實(shí)現(xiàn)了 Driver 接口的類,而是由驅(qū)動(dòng)程序管理器類(java.sql.DriverManager)去調(diào)用這些Driver實(shí)現(xiàn)。

  • Oracle的驅(qū)動(dòng):oracle.jdbc.driver.OracleDriver
  • mySql的驅(qū)動(dòng): com.mysql.jdbc.Driver

加載驅(qū)動(dòng):加載 JDBC 驅(qū)動(dòng)需調(diào)用 Class 類的靜態(tài)方法 forName(),向其傳遞要加載的 JDBC 驅(qū)動(dòng)的類名

  • Class.forName(“com.mysql.jdbc.Driver”);

3.2、注冊(cè)驅(qū)動(dòng)的原理

注冊(cè)驅(qū)動(dòng):DriverManager 類是驅(qū)動(dòng)程序管理器類,負(fù)責(zé)管理驅(qū)動(dòng)程序

  • 使用DriverManager.registerDriver(com.mysql.jdbc.Driver)來(lái)注冊(cè)驅(qū)動(dòng)
  • 通常不用顯式調(diào)用 DriverManager 類的 registerDriver()方法來(lái)注冊(cè)驅(qū)動(dòng)程序類的實(shí)例,因?yàn)?Driver 接口的驅(qū)動(dòng)程序類都包含了靜態(tài)代碼塊,在這個(gè)靜態(tài)代碼塊中,會(huì)調(diào)用 DriverManager.registerDriver()方法來(lái)注冊(cè)自身的一個(gè)實(shí)例。下圖是MySQL的Driver實(shí)現(xiàn)類的源碼:

他通常有以下兩個(gè)步驟:

1. 把 com.mysql.jdbc.Driver 這一份字節(jié)碼加載進(jìn) JVM。

2. 字節(jié)碼被加載進(jìn)JVM,就會(huì)執(zhí)行其靜態(tài)代碼塊.而其底層的靜態(tài)代碼塊在完成注冊(cè)驅(qū)動(dòng)工作,將驅(qū)動(dòng)注冊(cè)到DriverManger 中。

3.3、獲取連接對(duì)象

我們一般使用 DriverManager 的 getConnection 方法創(chuàng)建 Connection 對(duì)象

Connection conn = DriverManager.getConnection(url,username,password);
// url=jdbc:mysql://localhost:3306/jdbcdemo
// 如果連接的是本機(jī)的 MySQL,并且端口是默認(rèn)的 3306 ,則可以簡(jiǎn)寫:
//  url=jdbc:mysql:///jdbcdemo
// username:當(dāng)前訪問數(shù)據(jù)庫(kù)的用戶名
// password:當(dāng)前訪問數(shù)據(jù)庫(kù)的密碼

3.3.1、URL詳解

JDBC URL 用于標(biāo)識(shí)一個(gè)被注冊(cè)的驅(qū)動(dòng)程序,驅(qū)動(dòng)程序管理器通過(guò)這個(gè) URL 選擇正確的驅(qū)動(dòng)程序,從而建立到數(shù)據(jù)庫(kù)的連接。

JDBC URL的標(biāo)準(zhǔn)由三部分組成(協(xié)議:子協(xié)議:子名稱),各部分間用冒號(hào)分隔。

  1. 協(xié)議:JDBC URL中的協(xié)議總是jdbc(固定寫法)。
  2. 子協(xié)議:子協(xié)議用于標(biāo)識(shí)一個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序。
  3. 子名稱:一種標(biāo)識(shí)數(shù)據(jù)庫(kù)的方法。子名稱可以依不同的子協(xié)議而變化,用子名稱的目的是為了定位數(shù)據(jù)庫(kù)提供足夠的信息。包含主機(jī)名(對(duì)應(yīng)服務(wù)端的ip地址),端口號(hào),數(shù)據(jù)庫(kù)名。

4.url的常見寫法:

  • jdbc:mysql://主機(jī)名稱:mysql服務(wù)端口號(hào)/數(shù)據(jù)庫(kù)名稱?參數(shù)=值&參數(shù)=值
  • jdbc:mysql://localhost:3306/atguigu
  • jdbc:mysql://localhost:3306/atguigu**?useUnicode=true&characterEncoding=utf8**(如果JDBC程序與服務(wù)器端的字符集不一致,會(huì)導(dǎo)致亂碼,那么可以通過(guò)參數(shù)指定服務(wù)器端的字符集)
  • jdbc:mysql://localhost:3306/atguigu?user=root&password=123456

3.4、數(shù)據(jù)庫(kù)連接方法

3.4.1、方式一

我們可以通過(guò)直接獲取注冊(cè)驅(qū)動(dòng),將數(shù)據(jù)庫(kù)信息寫在代碼中,這個(gè)的問題也是很明顯的,那就是一個(gè)硬編碼問題,當(dāng)我們需要修改數(shù)據(jù)庫(kù)的時(shí)候,需要直接修改Java源代碼。

@Test
    public void testConnection4() {
        try {
            //1.數(shù)據(jù)庫(kù)連接的4個(gè)基本要素:
            String url = "jdbc:mysql://localhost:3306/test";
            String user = "root";
            String password = "admin";
            String driverName = "com.mysql.jdbc.Driver";

            //2.加載驅(qū)動(dòng) (①實(shí)例化Driver ②注冊(cè)驅(qū)動(dòng))
            Class.forName(driverName);

            //3.獲取連接
            Connection conn = DriverManager.getConnection(url, user, password);
            System.out.println(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.4.2、方式二

我們?yōu)榱私鉀Q方式一硬編碼的問題,需要將驅(qū)動(dòng)信息放入配置文件,然后通過(guò)讀取的方式來(lái)進(jìn)行賦值。

@Test
    public  void testConnection5() throws Exception {
    	//1.加載配置文件
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(is);
        
        //2.讀取配置信息
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

        //3.加載驅(qū)動(dòng)
        Class.forName(driverClass);

        //4.獲取連接
        Connection conn = DriverManager.getConnection(url,user,password);
        System.out.println(conn);

    }

 使用配置文件的好處:

  1. 實(shí)現(xiàn)了代碼和數(shù)據(jù)的分離,如果需要修改配置信息,直接在配置文件中修改,不需要深入代碼。
  2. 如果修改了配置信息,省去重新編譯的過(guò)程。

四、DAO思想

4.1、沒有DAO

在沒有 DAO 的時(shí)候,我們的代碼存在大量的重復(fù)。

4.2、DAO介紹

DAO(Data Access Object) 數(shù)據(jù)訪問對(duì)象是一個(gè)面向?qū)ο蟮臄?shù)據(jù)庫(kù)接口. 顧名思義就是與數(shù)據(jù)庫(kù)打交道,夾在業(yè)務(wù)邏輯與數(shù)據(jù)庫(kù)資源中間,將所有對(duì)數(shù)據(jù)源的訪問操作抽象封裝在一個(gè)公共 API 中。程序書寫就是建立一個(gè)接口,接口中定義了此應(yīng)用程序中將會(huì)用到的所有事務(wù)方法。DAO 中的主要操作: 增刪改查(CRUD)。

通過(guò)以上圖,DAO 作為組件,那其主要的是方法的設(shè)計(jì),方法設(shè)計(jì)需要注意什么呢?

  1. 在保存功能中,調(diào)用者需要傳遞多個(gè)參數(shù)進(jìn)來(lái),然后把這些數(shù)據(jù)保存到數(shù)據(jù)庫(kù)中。
  2. 在查詢功能中,結(jié)果集的每行數(shù)據(jù)有多個(gè)列的值,然后把這些數(shù)據(jù)返回給調(diào)用者。
  3. 在開發(fā)過(guò)程中,如果遇到需要傳遞的數(shù)據(jù)有多個(gè)的時(shí)候,通常需要使用 JavaBean 對(duì)其進(jìn)行封裝

4.3、DAO規(guī)范

DAO本質(zhì)就是一個(gè)可以重復(fù)使用的組件。他包括了兩個(gè)規(guī)范:

  1. 分包規(guī)范(域名倒寫.項(xiàng)目模塊名.組件)
cn.util;    //存放工具類
   cn.domain; //裝pss模塊的domain類,模型對(duì)象.(Student)
   cn.dao; //裝pss模塊的dao接口.調(diào)用者將需要保存的數(shù)據(jù)封裝到一個(gè)對(duì)象中,然后傳遞進(jìn)來(lái)
    cn.dao.impl; //裝dao接口的實(shí)現(xiàn)類.
   cn.test; //暫時(shí)存儲(chǔ)DAO的測(cè)試類,以后的測(cè)試類不應(yīng)該放這里.
  1. 命名規(guī)范 DAO 接口 : 表示對(duì)某個(gè)模型的 CRUD 操作做規(guī)范,以 I 開頭,interface。例如: IStudentDAO DAO 實(shí)現(xiàn)類: 表示對(duì)某個(gè) DAO 接口的實(shí)現(xiàn),例如:EmployeeDAOImpl DAO 測(cè)試類: 測(cè)試 DAO 組件中的所有方法,例如:EmployeeDAOTest

調(diào)用建議:面向接口編程

  • 傳統(tǒng)的做法: EmployeeDAOImpl dao = new EmployeeDAOImpl();
  • 面向接口編程: IEmployeeDAO dao = new EmployeeDAOImpl();

五、JDBC之CRUD操作

5.1、Statement對(duì)象及其弊端

5.1.1、Statement對(duì)象

Statement對(duì)象是用于執(zhí)行靜態(tài) SQL 語(yǔ)句并返回它所生成結(jié)果的對(duì)象。

  • 通過(guò)調(diào)用 Connection 對(duì)象的 createStatement() 方法創(chuàng)建該對(duì)象。該對(duì)象用于執(zhí)行靜態(tài)的 SQL 語(yǔ)句,并且返回執(zhí)行結(jié)果。
  • Statement 接口中定義了下列方法用于執(zhí)行 SQL 語(yǔ)句:
int excuteUpdate(String sql):執(zhí)行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):執(zhí)行查詢操作SELECT

5.1.2、使用Statement對(duì)象的弊端

用Statement操作數(shù)據(jù)表存在弊端:

  1. 存在拼串操作,繁瑣
  2. 存在SQL注入問題

SQL 注入是利用某些系統(tǒng)沒有對(duì)用戶輸入的數(shù)據(jù)進(jìn)行充分的檢查,而在用戶輸入數(shù)據(jù)中注入非法的 SQL 語(yǔ)句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,從而利用系統(tǒng)的 SQL 引擎完成惡意行為的做法。

public class StatementTest {

	// 使用Statement的弊端:需要拼寫sql語(yǔ)句,并且存在SQL注入的問題
	@Test
	public void testLogin() {
		Scanner scan = new Scanner(System.in);

		System.out.print("用戶名:");
		String userName = scan.nextLine();
		System.out.print("密   碼:");
		String password = scan.nextLine();

		// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
		String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
				+ "'";//字符串拼接過(guò)于繁雜
		User user = get(sql, User.class);
		if (user != null) {
			System.out.println("登陸成功!");
		} else {
			System.out.println("用戶名或密碼錯(cuò)誤!");
		}
	}

	// 使用Statement實(shí)現(xiàn)對(duì)數(shù)據(jù)表的查詢操作
	public <T> T get(String sql, Class<T> clazz) {
		T t = null;

		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			// 1.加載配置文件
			InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
			Properties pros = new Properties();
			pros.load(is);

			// 2.讀取配置信息
			String user = pros.getProperty("user");
			String password = pros.getProperty("password");
			String url = pros.getProperty("url");
			String driverClass = pros.getProperty("driverClass");

			// 3.加載驅(qū)動(dòng)
			Class.forName(driverClass);

			// 4.獲取連接
			conn = DriverManager.getConnection(url, user, password);

			st = conn.createStatement();

			rs = st.executeQuery(sql);

			// 獲取結(jié)果集的元數(shù)據(jù)
			ResultSetMetaData rsmd = rs.getMetaData();

			// 獲取結(jié)果集的列數(shù)
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {

				t = clazz.newInstance();

				for (int i = 0; i < columnCount; i++) {
					// //1. 獲取列的名稱
					// String columnName = rsmd.getColumnName(i+1);

					// 1. 獲取列的別名
					String columnName = rsmd.getColumnLabel(i + 1);

					// 2. 根據(jù)列名獲取對(duì)應(yīng)數(shù)據(jù)表中的數(shù)據(jù)
					Object columnVal = rs.getObject(columnName);

					// 3. 將數(shù)據(jù)表中得到的數(shù)據(jù),封裝進(jìn)對(duì)象
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnVal);
				}
				return t;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 關(guān)閉資源
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (st != null) {
				try {
					st.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}

		return null;
	}
}

5.2、PreparedStatement

我們可以通過(guò)調(diào)用 Connection 對(duì)象的 preparedStatement(String sql) 方法獲取PreparedStatement對(duì)象,PreparedStatement 接口是 Statement 的子接口,它表示一條預(yù)編譯過(guò)的 SQL 語(yǔ)句。

PreparedStatement 對(duì)象所代表的 SQL 語(yǔ)句中的參數(shù)用問號(hào)(?)來(lái)表示,調(diào)用PreparedStatement對(duì)象的 setXxx() 方法來(lái)設(shè)置這些參數(shù). setXxx() 方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是要設(shè)置的 SQL 語(yǔ)句中的參數(shù)的索引(從 1 開始),第二個(gè)是設(shè)置的 SQL 語(yǔ)句中的參數(shù)的值。

// 常用方法:
void setXxx(int parameterIndex,Xxx value); //設(shè)置第幾個(gè)占位符的真正參數(shù)值.
// Xxx 表示數(shù)據(jù)類型,比如 String,int,long,Date等.
void setObject(int parameterIndex, Object x); //設(shè)置第幾個(gè)占位符的真正參數(shù)值.
int executeUpdate(); //執(zhí)行DDL/DML語(yǔ)句. 注意:沒有參數(shù)
// 若當(dāng)前 SQL是 DDL語(yǔ)句,則返回 0.
// 若當(dāng)前 SQL是 DML語(yǔ)句,則返回受影響的行數(shù).
ResultSet executeQuery(); //執(zhí)行DQL語(yǔ)句,返回結(jié)果集.
close(); //釋放資源

5.3、PreparedStatement vs Statement

  1. PreparedStatement對(duì)象比Statement對(duì)象的代碼的可讀性和可維護(hù)性。
  2. PreparedStatement能最大可能提高性能。
  3. DBServer會(huì)對(duì)預(yù)編譯語(yǔ)句提供性能優(yōu)化。因?yàn)轭A(yù)編譯語(yǔ)句有可能被重復(fù)調(diào)用,所以語(yǔ)句在被DBServer的編譯器編譯后的執(zhí)行代碼被緩存下來(lái),那么下次調(diào)用時(shí)只要是相同的預(yù)編譯語(yǔ)句就不需要編譯,只要將參數(shù)直接傳入編譯過(guò)的語(yǔ)句執(zhí)行代碼中就會(huì)得到執(zhí)行。
  4. 在statement語(yǔ)句中,即使是相同操作但因?yàn)閿?shù)據(jù)內(nèi)容不一樣,所以整個(gè)語(yǔ)句本身不能匹配,沒有緩存語(yǔ)句的意義.事實(shí)是沒有數(shù)據(jù)庫(kù)會(huì)對(duì)普通語(yǔ)句編譯后的執(zhí)行代碼緩存。這樣每執(zhí)行一次都要對(duì)傳入的語(yǔ)句編譯一次。
  5. PreparedStatement 可以防止 SQL 注入 。

5.4、 ResultSet

查詢需要調(diào)用PreparedStatement 的 executeQuery() 方法,查詢結(jié)果是一個(gè)ResultSet 對(duì)象,ResultSet 對(duì)象以邏輯表格的形式封裝了執(zhí)行數(shù)據(jù)庫(kù)操作的結(jié)果集,ResultSet 接口由數(shù)據(jù)庫(kù)廠商提供實(shí)現(xiàn)。

ResultSet 返回的實(shí)際上就是一張數(shù)據(jù)表。有一個(gè)指針指向數(shù)據(jù)表的第一條記錄的前面。

ResultSet 對(duì)象維護(hù)了一個(gè)指向當(dāng)前數(shù)據(jù)行的游標(biāo),初始的時(shí)候,游標(biāo)在第一行之前,可以通過(guò) ResultSet 對(duì)象的 next() 方法移動(dòng)到下一行。調(diào)用 next()方法檢測(cè)下一行是否有效。若有效,該方法返回 true,且指針下移。相當(dāng)于Iterator對(duì)象的hasNext()和next()方法的結(jié)合體。

當(dāng)指針指向一行時(shí), 可以通過(guò)調(diào)用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。*Java與數(shù)據(jù)庫(kù)交互涉及到的相關(guān)Java API中的索引都從1開始。*例如:

getInt(1), getString("name")

5.5、CRUD操作

5.5.1、User類

package domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author Xiao_Lin
 * @date 2021/1/2 19:44
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
  //id
  private Integer id;
  //用戶名
  private String username;
  //密碼
  private String pwd;

  //構(gòu)造方法的重載
  public User(String username, String pwd) {
    this.username = username;
    this.pwd = pwd;
  }
}

5.5.3、UserDAOImpl

package dao.impl;

import dao.IUserDAO;

import dao.utils.DaoUtils;
import domain.User;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author Xiao_Lin
 * @date 2021/1/2 19:49
 */

public class UserDAOImpl implements IUserDAO {
  Connection connection = null;
  PreparedStatement ps = null;
  ResultSet rs = null;
  @Override
  public void insert(User user) {
    try {
      connection = DaoUtils.getConnection();
      String sql = "insert into user (username,pwd) values (?,?)";
       ps = connection.prepareStatement(sql);
      ps.setString(1,user.getUsername());
      ps.setString(2,user.getPwd());
      ps.executeUpdate();
      System.out.println("添加成功?。?);
    } catch (SQLException e) {
      e.printStackTrace();
    }finally {
      DaoUtils.close(connection,ps,null);
    }
  }

  @Override
  public void delete(Integer id) {
    try {
      connection = DaoUtils.getConnection();
      ps = connection.prepareStatement("delete from user where id = ?");
      ps.setInt(1,id);
      ps.executeUpdate();
      System.out.println("刪除成功??!");

    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      DaoUtils.close(connection,ps,null);
    }
  }

  @Override
  public void update(User user) {
    try {
      connection = DaoUtils.getConnection();
       ps = connection.prepareStatement("update user set username = ? , pwd = ? where id = ?");
       ps.setString(1,user.getUsername());
       ps.setString(2,user.getPwd());
       ps.setInt(3,user.getId());
      ps.executeUpdate();
      System.out.println("修改成功");
    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      DaoUtils.close(connection,ps,null);
    }

  }

  @Override
  public List<User> selectAll() {
    List<User> users = new ArrayList<>();
    try {
      connection = DaoUtils.getConnection();
      ps = connection.prepareStatement("select * from user");
      rs = ps.executeQuery();
      while (rs.next()){
        users.add(new User(rs.getInt("id"),rs.getString("username"),rs.getString("pwd")));
      }

    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      DaoUtils.close(connection,ps,rs);
    }
    return users;
  }

  @Override
  public User selectUserById(Integer id) {
    User user = null;
    try {
      connection = DaoUtils.getConnection();
      ps = connection.prepareStatement("select * from user where id = ?");
      ps.setInt(1,id);
      rs = ps.executeQuery();
      while (rs.next()){
        user = new User(rs.getInt("id"),rs.getString("username"),rs.getString("pwd"));
      }

    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      DaoUtils.close(connection,ps,rs);
    }
    return user;
  }
}

5.5.4、DaoUtils

package dao.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author Xiao_Lin
 * @date 2021/1/2 19:56
 */
public class DaoUtils {
  static Connection connection = null;
  static Properties properties = null;

  static {
    InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
        .getResourceAsStream("db.properties");
    properties = new Properties();
    try {
      properties.load(resourceAsStream);
      Class.forName(properties.getProperty("DriverClassName"));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static Connection getConnection() throws SQLException {
   connection= DriverManager.getConnection(properties.getProperty("url"),properties.getProperty("username"),properties.getProperty("password"));
    return connection;
  }

  public  static void  close(Connection conn , PreparedStatement ps , ResultSet rs){

    try {
      if(rs!=null){
        rs.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }finally{
      try {
        if(ps!=null){
          ps.close();
        }
      } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }finally{
        try {
          if(conn!=null){
            conn.close();
          }
        } catch (SQLException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }
  }
}

5.5.5、測(cè)試類

package dao.impl;


import dao.IUserDAO;
import dao.utils.DaoUtils;
import domain.User;
import java.sql.SQLException;
import java.util.List;
import org.junit.Test;

/**
 * @author Xiao_Lin
 * @date 2021/1/2 20:11
 */
public class UserDAOImplTest {
  IUserDAO userDAO = new UserDAOImpl();
  @Test
  public void insert() {
    userDAO.insert(new User("ghy","123"));

  }

  @Test
  public void delete() {
    userDAO.delete(14);
  }

  @Test
  public void update() {
    userDAO.update(new User(1,"張三","666"));
  }

  @Test
  public void selectAll() {
    List<User> users = userDAO.selectAll();
    users.forEach(System.out::print);
  }

  @Test
  public void selectUserById() {
    User user = userDAO.selectUserById(1);
    System.out.println(user);
  }
}

六、操作BLOB類型字段

6.1、BLOB類型簡(jiǎn)介

MySQL中,BLOB是一個(gè)二進(jìn)制大型對(duì)象,是一個(gè)可以存儲(chǔ)大量數(shù)據(jù)的容器,它能容納不同大小的數(shù)據(jù)。插入BLOB類型的數(shù)據(jù)必須使用PreparedStatement,因?yàn)锽LOB類型的數(shù)據(jù)無(wú)法使用字符串拼接寫的。MySQL有四種BLOB類型,他們除了在存儲(chǔ)的最大信息量上不同外,除此之外他們是等同的。

如果在指定了相關(guān)的Blob類型以后,還報(bào)錯(cuò):xxx too large,那么在mysql的安裝目錄下,找my.ini文件加上如下的配置參數(shù): max_allowed_packet=16M。同時(shí)注意:修改了my.ini文件之后,需要重新啟動(dòng)mysql服務(wù)。

6.2、插入BLOB類型

//獲取連接
Connection conn = JDBCUtils.getConnection();
		
String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);

// 填充占位符
ps.setString(1, "張三");
ps.setString(2, "zs@126.com");
ps.setDate(3, new Date(new java.util.Date().getTime()));
// 操作Blob類型的變量
FileInputStream fis = new FileInputStream("zs.png");
ps.setBlob(4, fis);
//執(zhí)行
ps.execute();
		
fis.close();
JDBCUtils.closeResource(conn, ps);

6.3、修改BLOB類型

Connection conn = JDBCUtils.getConnection();
String sql = "update customers set photo = ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);

// 填充占位符
// 操作Blob類型的變量
FileInputStream fis = new FileInputStream("coffee.png");
ps.setBlob(1, fis);
ps.setInt(2, 25);

ps.execute();

fis.close();
JDBCUtils.closeResource(conn, ps);

6.4、從數(shù)據(jù)庫(kù)表中讀取BLOG類型

String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?";
conn = getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, 8);
rs = ps.executeQuery();
if(rs.next()){
	Integer id = rs.getInt(1);
    String name = rs.getString(2);
	String email = rs.getString(3);
    Date birth = rs.getDate(4);
	Customer cust = new Customer(id, name, email, birth);
    System.out.println(cust); 
    //讀取Blob類型的字段
	Blob photo = rs.getBlob(5);//這里也可以通過(guò)列的索引來(lái)讀取
	InputStream is = photo.getBinaryStream();
	OutputStream os = new FileOutputStream("c.jpg");
	byte [] buffer = new byte[1024];
	int len = 0;
	while((len = is.read(buffer)) != -1){
		os.write(buffer, 0, len);
	}
    JDBCUtils.closeResource(conn, ps, rs);
		
	if(is != null){
		is.close();
	}
		
	if(os !=  null){
		os.close();
	}
    
}

七、批量處理

八、數(shù)據(jù)庫(kù)事務(wù)

8.1、問題引出

案例: 銀行轉(zhuǎn)賬, 從張無(wú)忌賬戶上給趙敏轉(zhuǎn) 1000 塊錢。

我們有一張account(賬戶表)。然后我們開始轉(zhuǎn)賬。

id

name

balance

1

張無(wú)忌

20000

2

趙敏

0

轉(zhuǎn)賬的步驟大概細(xì)分為以下幾個(gè)步驟:

  1. 查詢張無(wú)忌的賬戶余額是否大于等于1000。余額小于1000就提示溫馨提示:親,你的余額不足。 如果余額大于等于1000轉(zhuǎn)賬。
SELECT * FROM account WHERE name = '張無(wú)忌' AND balance >= 1000;
  1. 從張無(wú)忌的賬戶余額中減少1000。
UPDATE account SET balance = balance - 1000 WHERE name = '張無(wú)忌';
  1. 在趙敏的賬戶余額中增加1000。
UPDATE account SET balance = balance + 1000 WHERE name = '趙敏';

這個(gè)時(shí)候問題來(lái)了,當(dāng)程序執(zhí)行到第②步和第③步之間,突然出現(xiàn)一個(gè)異常,此時(shí)會(huì)造成轉(zhuǎn)賬前后數(shù)據(jù)不一致的問題,會(huì)造成轉(zhuǎn)賬了,但是對(duì)方的賬戶上沒有多錢。 造成這個(gè)問題的根本原因是因?yàn)檗D(zhuǎn)入轉(zhuǎn)出是兩個(gè)單獨(dú)的操作,其中一個(gè)失敗后,不會(huì)影響到另一個(gè)的執(zhí)行。但是在轉(zhuǎn)賬這個(gè)業(yè)務(wù)中,我們需要保證進(jìn)出兩個(gè)操作要么都成功,要么都失敗。

這個(gè)時(shí)候就需要引出事務(wù)的概念。

import org.junit.Test;

@Test
public void testTx() throws Exception {
// 賈璉欲執(zhí)事
// 1 查詢張無(wú)忌的賬戶余額是否大于等于1000
    Connection conn = JDBCUtil.getConnection();
    String sql = "SELECT * FROM account WHERE balance>=? AND name=?";
    PreparedStatement pst = conn.prepareStatement(sql);
// 給 ? 設(shè)置數(shù)據(jù)
    pst.setBigDecimal(1,new BigDecimal("1000"));
    pst.setString(2,"張無(wú)忌");
    ResultSet rs = pst.executeQuery();
    if(!rs.next()){
    System.out.println("余額不足");
    return;
    }
// 2 從張無(wú)忌的賬戶余額中減少1000.
    sql = "UPDATE account SET balance = balance-? WHERE name=?";
    pst = conn.prepareStatement(sql);
//設(shè)置? 的數(shù)據(jù)
    pst.setBigDecimal(1,new BigDecimal("1000"));
    pst.setString(2,"張無(wú)忌");
    pst.executeUpdate();
// 模擬出異常
    int a = 10/0;
// 3 在趙敏的賬戶余額中增加1000.
    sql = "UPDATE account SET balance = balance+? WHERE name=?";
    pst = conn.prepareStatement(sql);
//設(shè)置? 的數(shù)據(jù)
    pst.setBigDecimal(1,new BigDecimal("1000"));
    pst.setString(2,"趙敏");
    pst.executeUpdate();
// 釋放資源
    JDBCUtil.close(conn,pst,rs);
    }

8.2、事務(wù)

事務(wù)(Transaction,簡(jiǎn)寫為tx):一組邏輯操作單元,使數(shù)據(jù)從一種狀態(tài)變換到另一種狀態(tài)。

事務(wù)處理(事務(wù)操作):保證所有事務(wù)都作為一個(gè)工作單元來(lái)執(zhí)行,即使出現(xiàn)了故障,都不能改變這種執(zhí)行方式。當(dāng)在一個(gè)事務(wù)中執(zhí)行多個(gè)操作時(shí),要么所有的事務(wù)都被提交(commit),那么這些修改就永久地被看成是撤銷操作。

為確保數(shù)據(jù)庫(kù)中數(shù)據(jù)的一致性,數(shù)據(jù)的操縱應(yīng)當(dāng)是離散的成組的邏輯單元:當(dāng)它全部完成時(shí),數(shù)據(jù)的一致性可以保持,而當(dāng)這個(gè)單元中的一部分操作失敗,整個(gè)事務(wù)應(yīng)全部視為錯(cuò)誤,所有從起始點(diǎn)以后的操作應(yīng)全部回退到開始狀態(tài)。

8.3、事務(wù)的ACID屬性

  1. 原子性(Atomicity) 原子性是指事務(wù)是一個(gè)不可分割的工作單位,事務(wù)中的操作要么都發(fā)生,要么都不發(fā)生。
  2. 一致性(Consistency) 事務(wù)必須使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變換到另外一個(gè)一致性狀態(tài)。
  3. 隔離性(Isolation) 事務(wù)的隔離性是指一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾,即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對(duì)并發(fā)的其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾。
  4. 持久性(Durability) 持久性是指一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就是永久性的,接下來(lái)的其他操作和數(shù)據(jù)庫(kù)故障不應(yīng)該對(duì)其有任何影響。

8.4、數(shù)據(jù)庫(kù)的并發(fā)問題

對(duì)于同時(shí)運(yùn)行的多個(gè)事務(wù), 當(dāng)這些事務(wù)訪問數(shù)據(jù)庫(kù)中相同的數(shù)據(jù)時(shí), 如果沒有采取必要的隔離機(jī)制, 就會(huì)導(dǎo)致各種并發(fā)問題:

  1. 臟讀: 對(duì)于兩個(gè)事務(wù) T1, T2, T1 讀取了已經(jīng)被 T2 更新但還沒有被提交的字段。之后, 若 T2 回滾, T1讀取的內(nèi)容就是臨時(shí)且無(wú)效的。
  2. 不可重復(fù)讀: 對(duì)于兩個(gè)事務(wù)T1, T2, T1 讀取了一個(gè)字段, 然后 T2 更新了該字段。之后, T1再次讀取同一個(gè)字段, 值就不同了。
  3. 幻讀: 對(duì)于兩個(gè)事務(wù)T1, T2, T1 從一個(gè)表中讀取了一個(gè)字段, 然后 T2 在該表中插入了一些新的行。之后, 如果 T1 再次讀取同一個(gè)表, 就會(huì)多出幾行。

8.4.1、事務(wù)的隔離性

數(shù)據(jù)庫(kù)事務(wù)的隔離性: 數(shù)據(jù)庫(kù)系統(tǒng)必須具有隔離并發(fā)運(yùn)行各個(gè)事務(wù)的能力, 使它們不會(huì)相互影響, 避免各種并發(fā)問題。

一個(gè)事務(wù)與其他事務(wù)隔離的程度稱為隔離級(jí)別。數(shù)據(jù)庫(kù)規(guī)定了多種事務(wù)隔離級(jí)別, 不同隔離級(jí)別對(duì)應(yīng)不同的干擾程度, 隔離級(jí)別越高, 數(shù)據(jù)一致性就越好, 但并發(fā)性越弱。

8.4.2、數(shù)據(jù)庫(kù)的隔離級(jí)別

MySQL數(shù)據(jù)庫(kù)支持4種事務(wù)隔離級(jí)別。Mysql 默認(rèn)的事務(wù)隔離級(jí)別為: REPEATABLE READ。

8.4.3、設(shè)置隔離級(jí)別

每啟動(dòng)一個(gè) mysql 程序, 就會(huì)獲得一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)連接. 每個(gè)數(shù)據(jù)庫(kù)連接都有一個(gè)全局變量 @@tx_isolation, 表示當(dāng)前的事務(wù)隔離級(jí)別。

8.4.3.1、查看當(dāng)前的隔離級(jí)別

SELECT @@tx_isolation;

8.4.3.2、設(shè)置當(dāng)前mysql隔離級(jí)別

set transaction isolation level read committed;

8.4.3.3、設(shè)置mysql的全局隔離級(jí)別

set global transaction isolation level read committed;

8.5、事務(wù)的操作步驟

  1. 先定義開始一個(gè)事務(wù),然后對(duì)數(shù)據(jù)作修改操作。
  2. 執(zhí)行過(guò)程中,如果沒有問題就提交(commit)事務(wù),此時(shí)的修改將永久地保存下來(lái)。
  3. 如果執(zhí)行過(guò)程中有問題(異常),回滾事務(wù)(rollback),數(shù)據(jù)庫(kù)管理系統(tǒng)將放棄所作的所有修改而回到 開始事務(wù)時(shí)的狀態(tài)。
try{
	//取消事務(wù)的自動(dòng)提交機(jī)制,設(shè)置為手動(dòng)提交.
	connection對(duì)象.setAutoCommit(false);
	//操作1
	//操作2
	//異常
	//操作3
	//....
	//手動(dòng)提交事務(wù)
	connection對(duì)象.commit();
}catch(Exception e){
	//處理異常
    //回滾事務(wù)
connection對(duì)象.rollback();
}

8.6、事務(wù)的注意事項(xiàng)

  1. 在默認(rèn)情況下,事務(wù)會(huì)在執(zhí)行完DML操作后會(huì)自動(dòng)提交。
  2. 在進(jìn)行查詢操作的時(shí)候一般是不需要事務(wù)的,但是我們一般也會(huì)在查詢中寫事務(wù)
  3. 在寫代碼的時(shí)候,如果代碼完全正常沒有異常,但是數(shù)據(jù)庫(kù)中的數(shù)據(jù)沒有任何改變的話,說(shuō)明是沒有提交事務(wù)。
  4. 在MySQL中,只有InnoDB存儲(chǔ)引擎支持事務(wù),支持外鍵,MyISAM是不支持事務(wù)的。
  5. 以后處理事務(wù)的時(shí)候,必須在service層中進(jìn)行控制。

九、連接池

9.1、JDBC數(shù)據(jù)庫(kù)連接池的必要性

在使用開發(fā)基于數(shù)據(jù)庫(kù)的web程序時(shí),傳統(tǒng)的模式基本是按以下步驟:

  1. 在主程序中建立數(shù)據(jù)庫(kù)連接。
  2. 進(jìn)行sql操作。
  3. 斷開數(shù)據(jù)庫(kù)連接

這種模式會(huì)存在幾個(gè)很顯著的問題:

普通的JDBC數(shù)據(jù)庫(kù)連接使用 DriverManager 來(lái)獲取,每次向數(shù)據(jù)庫(kù)建立連接的時(shí)候都要將Connection 加載到內(nèi)存中,再驗(yàn)證用戶名和密碼(得花費(fèi)0.05s~1s的時(shí)間)。需要數(shù)據(jù)庫(kù)連接的時(shí)候,就向數(shù)據(jù)庫(kù)要求一個(gè),執(zhí)行完成后再斷開連接。這樣的方式將會(huì)消耗大量的資源和時(shí)間。數(shù)據(jù)庫(kù)的連接資源并沒有得到很好的重復(fù)利用。若同時(shí)有幾百人甚至幾千人在線,頻繁的進(jìn)行數(shù)據(jù)庫(kù)連接操作將占用很多的系統(tǒng)資源,嚴(yán)重的甚至?xí)斐煞?wù)器的崩潰。

對(duì)于每一次數(shù)據(jù)庫(kù)連接,使用完后都得斷開,否則,如果程序出現(xiàn)異常而未能關(guān)閉,將會(huì)導(dǎo)致數(shù)據(jù)庫(kù)系統(tǒng)中的內(nèi)存泄漏,最終將導(dǎo)致重啟數(shù)據(jù)庫(kù)。

這種開發(fā)不能控制被創(chuàng)建的連接對(duì)象數(shù),系統(tǒng)資源會(huì)被毫無(wú)顧忌的分配出去,如連接過(guò)多,也可能導(dǎo)致內(nèi)存泄漏,服務(wù)器崩潰。

9.2、數(shù)據(jù)庫(kù)連接池

為解決傳統(tǒng)開發(fā)中的數(shù)據(jù)庫(kù)連接問題,我們可以采用數(shù)據(jù)庫(kù)連接池技術(shù)。

數(shù)據(jù)庫(kù)連接池的基本思想:就是為數(shù)據(jù)庫(kù)連接建立一個(gè)“緩沖池”。預(yù)先在緩沖池中放入一定數(shù)量的連接,當(dāng)需要建立數(shù)據(jù)庫(kù)連接時(shí),只需從“緩沖池”中取出一個(gè),使用完畢之后再放回去。

數(shù)據(jù)庫(kù)連接池負(fù)責(zé)分配、管理和釋放數(shù)據(jù)庫(kù)連接,它允許應(yīng)用程序重復(fù)使用一個(gè)現(xiàn)有的數(shù)據(jù)庫(kù)連接,而不是重新建立一個(gè)。

數(shù)據(jù)庫(kù)連接池在初始化時(shí)將創(chuàng)建一定數(shù)量的數(shù)據(jù)庫(kù)連接放到連接池中,這些數(shù)據(jù)庫(kù)連接的數(shù)量是由最小數(shù)據(jù)庫(kù)連接數(shù)來(lái)設(shè)定的。無(wú)論這些數(shù)據(jù)庫(kù)連接是否被使用,連接池都將一直保證至少擁有這么多的連接數(shù)量。連接池的最大數(shù)據(jù)庫(kù)連接數(shù)量限定了這個(gè)連接池能占有的最大連接數(shù),當(dāng)應(yīng)用程序向連接池請(qǐng)求的連接數(shù)超過(guò)最大連接數(shù)量時(shí),這些請(qǐng)求將被加入到等待隊(duì)列中。

9.2.1、數(shù)據(jù)庫(kù)連接池的原理以及優(yōu)勢(shì)

使用數(shù)據(jù)庫(kù)連接池的優(yōu)點(diǎn)也是很明顯的:

資源重復(fù)使用

由于數(shù)據(jù)庫(kù)連接得以重用,避免了頻繁創(chuàng)建,釋放連接引起的大量性能開銷。在減少系統(tǒng)消耗的基礎(chǔ)上,另一方面也增加了系統(tǒng)運(yùn)行環(huán)境的平穩(wěn)性。

更快的系統(tǒng)反應(yīng)速度

數(shù)據(jù)庫(kù)連接池在初始化過(guò)程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫(kù)連接置于連接池中備用。此時(shí)連接的初始化工作均已完成。對(duì)于業(yè)務(wù)請(qǐng)求處理而言,直接利用現(xiàn)有可用連接,避免了數(shù)據(jù)庫(kù)連接初始化和釋放過(guò)程的時(shí)間開銷,從而減少了系統(tǒng)的響應(yīng)時(shí)間。

新的資源分配手段

對(duì)于多應(yīng)用共享同一數(shù)據(jù)庫(kù)的系統(tǒng)而言,可在應(yīng)用層通過(guò)數(shù)據(jù)庫(kù)連接池的配置,實(shí)現(xiàn)某一應(yīng)用最大可用數(shù)據(jù)庫(kù)連接數(shù)的限制,避免某一應(yīng)用獨(dú)占所有的數(shù)據(jù)庫(kù)資源。

統(tǒng)一的連接管理,避免數(shù)據(jù)庫(kù)連接泄漏

在較為完善的數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn)中,可根據(jù)預(yù)先的占用超時(shí)設(shè)定,強(qiáng)制回收被占用連接,從而避免了常規(guī)數(shù)據(jù)庫(kù)連接操作中可能出現(xiàn)的資源泄露。

9.2.2、數(shù)據(jù)庫(kù)連接池的屬性

基本屬性:連接池存了連接對(duì)象,而連接對(duì)象依賴四要素,所以四要素(driverClassName,url,username,password)是基本要求。

其他屬性:對(duì)連接對(duì)象做限制的配置

1. 初始化連接數(shù):在連接池中事先準(zhǔn)備好初始化Connection對(duì)象。

2. 最多連接數(shù):在連接池中最多有一定數(shù)量的Connection對(duì)象,其他客戶端進(jìn)入等待狀態(tài)。

3. 最少連接數(shù):在連接池中最少一定數(shù)量的Connection對(duì)象。

4. 最長(zhǎng)等待時(shí)間:使用一定時(shí)間來(lái)申請(qǐng)獲取Connection對(duì)象,如果時(shí)間到還沒有申請(qǐng)到,則提示,自動(dòng)放棄。

5. 最長(zhǎng)超時(shí)時(shí)間:如果你在一定時(shí)間之內(nèi)沒有任何動(dòng)作,則認(rèn)為是自動(dòng)放棄Connection對(duì)象。

9.3、數(shù)據(jù)庫(kù)連接池的分類

JDBC 的數(shù)據(jù)庫(kù)連接池使用javax.sql.DataSource來(lái)表示,DataSource 只是一個(gè)接口,該接口通常由服務(wù)器(Weblogic, WebSphere, Tomcat)提供實(shí)現(xiàn),也有一些開源組織提供實(shí)現(xiàn):

  1. DBCP 是Apache提供的數(shù)據(jù)庫(kù)連接池。tomcat 服務(wù)器自帶dbcp數(shù)據(jù)庫(kù)連接池。速度相對(duì)c3p0較快,但因自身存在BUG,Hibernate3已不再提供支持。
  2. C3P0 是一個(gè)開源組織提供的一個(gè)數(shù)據(jù)庫(kù)連接池,速度相對(duì)較慢,穩(wěn)定性還可以。hibernate官方推薦使用。
  3. Proxool 是sourceforge下的一個(gè)開源項(xiàng)目數(shù)據(jù)庫(kù)連接池,有監(jiān)控連接池狀態(tài)的功能,穩(wěn)定性較c3p0差一點(diǎn)。
  4. BoneCP 是一個(gè)開源組織提供的數(shù)據(jù)庫(kù)連接池,速度快。
  5. Druid 是阿里提供的數(shù)據(jù)庫(kù)連接池,據(jù)說(shuō)是集DBCP 、C3P0 、Proxool 優(yōu)點(diǎn)于一身的數(shù)據(jù)庫(kù)連接池,但是速度不確定是否有BoneCP快。

DataSource 通常被稱為數(shù)據(jù)源,它包含連接池和連接池管理兩個(gè)部分,習(xí)慣上也經(jīng)常把 DataSource 稱為連接池,DataSource用來(lái)取代DriverManager來(lái)獲取Connection,獲取速度快,同時(shí)可以大幅度提高數(shù)據(jù)庫(kù)訪問速度。

注意:

  1. 數(shù)據(jù)源和數(shù)據(jù)庫(kù)連接不同,數(shù)據(jù)源無(wú)需創(chuàng)建多個(gè),它是產(chǎn)生數(shù)據(jù)庫(kù)連接的工廠,因此整個(gè)應(yīng)用只需要一個(gè)數(shù)據(jù)源即可。
  2. 當(dāng)數(shù)據(jù)庫(kù)訪問結(jié)束后,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫(kù)連接:conn.close(); 但conn.close()并沒有關(guān)閉數(shù)據(jù)庫(kù)的物理連接,它僅僅把數(shù)據(jù)庫(kù)連接釋放,歸還給了數(shù)據(jù)庫(kù)連接池。

9.4、DBCP連接池

DBCP 是 Apache 軟件基金組織下的開源連接池實(shí)現(xiàn),該連接池依賴該組織下的另一個(gè)開源系統(tǒng):Common-pool。如需使用該連接池實(shí)現(xiàn),應(yīng)在系統(tǒng)中增加如下兩個(gè) jar 文件:

  • Commons-dbcp.jar:連接池的實(shí)現(xiàn)
  • Commons-pool.jar:連接池實(shí)現(xiàn)的依賴庫(kù)

Tomcat 的連接池正是采用該連接池來(lái)實(shí)現(xiàn)的。該數(shù)據(jù)庫(kù)連接池既可以與應(yīng)用服務(wù)器整合使用,也可由應(yīng)用程序獨(dú)立使用。

數(shù)據(jù)源和數(shù)據(jù)庫(kù)連接不同,數(shù)據(jù)源無(wú)需創(chuàng)建多個(gè),它是產(chǎn)生數(shù)據(jù)庫(kù)連接的工廠,因此整個(gè)應(yīng)用只需要一個(gè)數(shù)據(jù)源即可。

當(dāng)數(shù)據(jù)庫(kù)訪問結(jié)束后,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫(kù)連接:conn.close(); 但上面的代碼并沒有關(guān)閉數(shù)據(jù)庫(kù)的物理連接,它僅僅把數(shù)據(jù)庫(kù)連接釋放,歸還給了數(shù)據(jù)庫(kù)連接池。

9.4.1、DBCP屬性說(shuō)明

屬性

默認(rèn)值

說(shuō)明

initialSize

0

連接池啟動(dòng)時(shí)創(chuàng)建的初始化連接數(shù)量

maxActive

8

連接池中可同時(shí)連接的最大的連接數(shù)

maxIdle

8

連接池中最大的空閑的連接數(shù),超過(guò)的空閑連接將被釋放,如果設(shè)置為負(fù)數(shù)表示不限制

minIdle

0

連接池中最小的空閑的連接數(shù),低于這個(gè)數(shù)量會(huì)被創(chuàng)建新的連接。該參數(shù)越接近maxIdle,性能越好,因?yàn)檫B接的創(chuàng)建和銷毀,都是需要消耗資源的;但是不能太大。

maxWait

無(wú)限制

最大等待時(shí)間,當(dāng)沒有可用連接時(shí),連接池等待連接釋放的最大時(shí)間,超過(guò)該時(shí)間限制會(huì)拋出異常,如果設(shè)置-1表示無(wú)限等待

poolPreparedStatements

false

開啟池的Statement是否prepared

maxOpenPreparedStatements

無(wú)限制

開啟池的prepared 后的同時(shí)最大連接數(shù)

minEvictableIdleTimeMillis


連接池中連接,在時(shí)間段內(nèi)一直空閑, 被逐出連接池的時(shí)間

removeAbandonedTimeout

300

超過(guò)時(shí)間限制,回收沒有用(廢棄)的連接

removeAbandoned

false

超過(guò)removeAbandonedTimeout時(shí)間后,是否進(jìn) 行沒用連接(廢棄)的回收

9.4.2、獲取連接的方式

//使用dbcp數(shù)據(jù)庫(kù)連接池的配置文件方式,獲取數(shù)據(jù)庫(kù)的連接:推薦
// 創(chuàng)建一個(gè)DataSource對(duì)象
private static DataSource source = null;
static{
	try {
        //創(chuàng)建一個(gè)Properties,用于讀取配置文件
		Properties pros = new Properties();
        //讀取配置文件
		InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("db.properties");
		//加載配置文件
		pros.load(is);
		//根據(jù)提供的BasicDataSourceFactory創(chuàng)建對(duì)應(yīng)的DataSource對(duì)象
		source = BasicDataSourceFactory.createDataSource(pros);
	} catch (Exception e) {
		e.printStackTrace();
	}
		
}
public static Connection getConnection4() throws Exception {
	Connection conn = source.getConnection();
	return conn;
}
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=123456

initialSize=10
#...

9.4.3、注意事項(xiàng)

由于使用了DBCP,所以配置文件的key我們必須按照他官方給定的要求來(lái)書寫。

9.5、Druid(德魯伊)連接池

Druid是阿里巴巴開源平臺(tái)上一個(gè)數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn),它結(jié)合了C3P0、DBCP、Proxool等DB池的優(yōu)點(diǎn),同時(shí)加入了日志監(jiān)控,可以很好的監(jiān)控DB池連接和SQL的執(zhí)行情況,可以說(shuō)是針對(duì)監(jiān)控而生的DB連接池,可以說(shuō)是目前最好的連接池之一。

9.5.1、Druid參數(shù)詳解

配置

缺省

說(shuō)明

name


配置這個(gè)屬性的意義在于,如果存在多個(gè)數(shù)據(jù)源,監(jiān)控的時(shí)候可以通過(guò)名字來(lái)區(qū)分開來(lái)。 如果沒有配置,將會(huì)生成一個(gè)名字,格式是:”DataSource-” + System.identityHashCode(this)

url


連接數(shù)據(jù)庫(kù)的url,不同數(shù)據(jù)庫(kù)不一樣。例如:mysql :
jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto

username


連接數(shù)據(jù)庫(kù)的用戶名

password


連接數(shù)據(jù)庫(kù)的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。詳細(xì)看這里:github.com/alibaba/dru…

driverClassName


根據(jù)url自動(dòng)識(shí)別 這一項(xiàng)可配可不配,如果不配置druid會(huì)根據(jù)url自動(dòng)識(shí)別dbType,然后選擇相應(yīng)的driverClassName(建議配置下)

initialSize

0

初始化時(shí)建立物理連接的個(gè)數(shù)。初始化發(fā)生在顯示調(diào)用init方法,或者第一次getConnection時(shí)

maxActive

8

最大連接池?cái)?shù)量

maxIdle

8

已經(jīng)不再使用,配置了也沒效果

minIdle


最小連接池?cái)?shù)量

maxWait


獲取連接時(shí)最大等待時(shí)間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會(huì)有所下降,如果需要可以通過(guò)配置useUnfairLock屬性為true使用非公平鎖。

poolPreparedStatements

false

是否緩存preparedStatement,也就是PSCache。PSCache對(duì)支持游標(biāo)的數(shù)據(jù)庫(kù)性能提升巨大,比如說(shuō)oracle。在mysql下建議關(guān)閉。

maxOpenPreparedStatements

-1

要啟用PSCache,必須配置大于0,當(dāng)大于0時(shí),poolPreparedStatements自動(dòng)觸發(fā)修改為true。在Druid中,不會(huì)存在Oracle下PSCache占用內(nèi)存過(guò)多的問題,可以把這個(gè)數(shù)值配置大一些,比如說(shuō)100

validationQuery


用來(lái)檢測(cè)連接是否有效的sql,要求是一個(gè)查詢語(yǔ)句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會(huì)其作用。

testOnBorrow

true

申請(qǐng)連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能。

testOnReturn

false

歸還連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能

testWhileIdle

false

建議配置為true,不影響性能,并且保證安全性。申請(qǐng)連接的時(shí)候檢測(cè),如果空閑時(shí)間大于
timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效。

timeBetweenEvictionRunsMillis


有兩個(gè)含義: 1)Destroy線程會(huì)檢測(cè)連接的間隔時(shí)間2)testWhileIdle的判斷依據(jù),詳細(xì)看testWhileIdle屬性的說(shuō)明

numTestsPerEvictionRun


不再使用,一個(gè)DruidDataSource只支持一個(gè)EvictionRun

minEvictableIdleTimeMillis



connectionInitSqls


物理連接初始化的時(shí)候執(zhí)行的sql

exceptionSorter


根據(jù)dbType自動(dòng)識(shí)別 當(dāng)數(shù)據(jù)庫(kù)拋出一些不可恢復(fù)的異常時(shí),拋棄連接

filters


屬性類型是字符串,通過(guò)別名的方式配置擴(kuò)展插件,常用的插件有: 監(jiān)控統(tǒng)計(jì)用的filter:stat日志用的filter:log4j防御sql注入的filter:wall

proxyFilters


類型是List,如果同時(shí)配置了filters和proxyFilters,是組合關(guān)系,并非替換關(guān)系

9.5.2、獲取連接方式

package com.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;

/**
 * @author Xiao_Lin
 * @date 2021/1/3 19:47
 */
public class DruidUtils {
  static DataSource ds = null;
  private DruidUtils(){

  }

  static {
    InputStream stream = Thread.currentThread().getContextClassLoader()
        .getResourceAsStream("db.properties");
    Properties properties = new Properties();
    try {
      properties.load(stream);
      ds = DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static Connection getConnection(){
    try {
      return ds.getConnection();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return null;
  }
}
DriverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql:///db?characterEncoding=utf-8&useSSL=false
username = root
password = 123456

9.5.3、注意事項(xiàng)

properties配置文件中的 key 一定要和 DruidDataSource 中對(duì)應(yīng)的屬性名一致。

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2020-11-17 07:43:24

互聯(lián)網(wǎng)MVC服務(wù)類

2019-01-30 13:44:34

JVM內(nèi)存服務(wù)器

2019-07-10 15:15:23

JVM虛擬機(jī)Java

2018-07-31 14:03:09

JVM內(nèi)存數(shù)據(jù)

2017-03-07 15:35:26

Android適配 界面

2017-03-10 21:04:04

Android適配

2021-10-14 06:36:38

存儲(chǔ)云存儲(chǔ)本地存儲(chǔ)

2022-02-18 06:56:18

Wi-Fi路由器局域網(wǎng)

2019-05-30 09:32:49

2019-08-01 11:04:10

Linux磁盤I

2015-12-02 18:11:06

百度地圖/地圖軟件

2025-03-28 08:53:51

2020-03-18 21:12:22

Nginx網(wǎng)站Linux

2021-04-27 07:59:11

內(nèi)聯(lián)匯編 C 語(yǔ)言 asm 關(guān)鍵字

2017-06-01 18:55:44

2020-05-15 09:05:46

Service Mes微服務(wù)架構(gòu)

2017-08-09 15:07:08

大數(shù)據(jù)數(shù)據(jù)分析戶畫像

2021-02-24 07:38:50

Redis

2024-11-20 08:09:19

RabbitMQ項(xiàng)目客戶端

2019-11-28 18:36:00

華為MateBook
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)