<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java JDBC 編程指北

          共 45857字,需瀏覽 92分鐘

           ·

          2021-04-26 21:05

          微信搜一搜
          村雨遙

          前言

          在我們?nèi)粘J褂玫?APP 或網(wǎng)站中,往往需要存取數(shù)據(jù),比如在微信中,需要存儲我們的用戶名、手機號、用戶密碼…… 等一系列信息。依靠之前所學(xué)習(xí)的 Java 相關(guān)知識已經(jīng)無法滿足這一需求。現(xiàn)在的應(yīng)用程序中最基本、應(yīng)用最廣的也就是關(guān)系型數(shù)據(jù)庫,如 MySQL。Java 語言中為了實現(xiàn)與關(guān)系型數(shù)據(jù)庫的通信,制定了標準的訪問捷克,即 JDBC(Java Database Connectivity)。本文主要介紹在 Java 中使用 JDBC 的相關(guān)知識,主要內(nèi)容如下:

          • JDBC 簡介
          • 數(shù)據(jù)的增刪改查
          • 事務(wù)
          • 連接池

          JDBC 簡介

          JDBC(Java Database Connectivity),即 Java 數(shù)據(jù)庫連接。是 Java 語言中用于規(guī)范客戶端程序如何來訪問數(shù)據(jù)庫的應(yīng)用程序接口,它是面向關(guān)系型數(shù)據(jù)庫的,提供了查詢和更新數(shù)據(jù)庫中數(shù)據(jù)的方法。

          本文以 MySQL 來演示如何使用 JDBC,所以需要事先在你的機器上準備好 MySQL,而且最好是懂一些 MySQL 的使用。

          首先我們需要建立 MySQL 與 Java 程序間的聯(lián)系,所以需要事先好 mysql-connector-java 這個第三方包,下載地址:https://downloads.mysql.com/archives/c-j/

          導(dǎo)入驅(qū)動包

          以在 IDEA 中導(dǎo)入 jar 包為例,當(dāng)我們建立好項目后,導(dǎo)包過程如下:

          1. 首先依次打開 File -> Project Structure -> Modules -> Dependencies;
          1. 然后點擊 + 號,選擇 1 JARs or Directories,找到你下載好的 jar 包導(dǎo)入;
          1. 導(dǎo)入成功,點擊 OK 即可;

          初始化并建立連接

          導(dǎo)入我們的 jar 包之后,就需要進行初始化工作。新建一個類,用于初始化并連接。先將驅(qū)動類加載到 JVM 中,加載過程中會執(zhí)行其中的靜態(tài)初始化塊,從而完成驅(qū)動的初始化工作。然后建立數(shù)據(jù)庫與程序之間的連接,此時需要提供數(shù)據(jù)庫的 IP 地址、端口號、數(shù)據(jù)庫名、編碼方式、用戶名、用戶密碼等信息。

          首先,我們在數(shù)據(jù)庫中建立一個表 student,建表語句如下,用于后續(xù)實踐。

          -- 創(chuàng)建數(shù)據(jù)庫 javalearning
          CREATE DATABASE if not exists javalearning;
          -- 創(chuàng)建表 students
          USE javalearning;
          CREATE TABLE students (
            id BIGINT AUTO_INCREMENT NOT NULL-- 學(xué)號
            name VARCHAR(50NOT NULL-- 姓名
            gender TINYINT(1NOT NULL-- 性別
            grade INT NOT NULL-- 年級
            score INT NOT NULL-- 分數(shù)
            PRIMARY KEY(id-- 主鍵
          Engine=INNODB DEFAULT CHARSET=UTF8;

          -- 插入部分數(shù)據(jù)
          INSERT INTO students (idname, gender, grade, score) VALUES (101,'小紅'01100);
          INSERT INTO students (idname, gender, grade, score) VALUES (102,'小橙'0189);
          INSERT INTO students (idname, gender, grade, score) VALUES (201,'小黃'1297);
          INSERT INTO students (idname, gender, grade, score) VALUES (301,'小綠'1399);

          創(chuàng)建好數(shù)據(jù)庫及表之后,我們就可以進行初始化和連接工作了,這里的步驟主要分為如下幾步:

          1. 首先需要加載驅(qū)動,主要是利用 Class.forName() 將驅(qū)動類加載到 JVM;
          2. 建立程序和數(shù)據(jù)庫之間的連接,主要是創(chuàng)建 Connection 對象;
          3. 接著是創(chuàng)建用于執(zhí)行 SQL 語句的 Statement 對象;
          4. 最后則是關(guān)閉連接從而釋放資源,先關(guān)閉 Statement ,再關(guān)閉 Connection
          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.SQLException;
          import java.sql.Statement;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : InitJDBC
           * @date : 2021/4/23 10:56
           * @description : 初始化并建立連接
           */


          public class InitJDBC {
              public static void main(String[] args) {
                  Connection connection = null;
                  Statement statement = null;
                  try {
          //            初始化,注冊驅(qū)動
                      Class.forName("com.mysql.cj.jdbc.Driver");
          //            建立連接
                      connection = DriverManager.getConnection("jdbc:mysql://localhost/javalearning?characterEncoding=UTF-8""root""12345");
                      System.out.println("連接成功!");
          //            創(chuàng)建 Statement 用于執(zhí)行 SQL 語句
                      statement = connection.createStatement();
                      System.out.println("Statement 對象:" + statement);
                  } catch (ClassNotFoundException | SQLException e) {
                      e.printStackTrace();
                  } finally {
                      try {
                          if (statement != null) {
                              statement.close();
                          }
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                      try {

                          if (connection != null) {
                              connection.close();
                          }
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }
              }
          }

          對于上述關(guān)閉 ConnectionStatement 的方式,可能略顯繁瑣,為了進一步簡化,可以使用 try-with-source 的方式自動關(guān)閉,簡化后的代碼如下;

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.SQLException;
          import java.sql.Statement;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : InitJDBC2
           * @date : 2021/4/23 13:53
           * @description : 初始化與連接
           */


          public class InitJDBC2 {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8""root""12345"); Statement statement = connection.createStatement();) {
                      System.out.println("連接成功");
                      System.out.println("State 對象:" + statement);
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          JDBC 增刪改查

          當(dāng)我們初始化并建立 JDBC 連接之后,我們就可以對數(shù)據(jù)庫進行 CRUD (增加、查詢、更新、刪除)等操作。

          在正式開始 CRUD 前,我們最好先了解下 MySQL 中的數(shù)據(jù)類型在 Java 中所對應(yīng)的數(shù)據(jù)類型,以便后續(xù)操作數(shù)據(jù)。一般來講,兩者中的數(shù)據(jù)類型對應(yīng)關(guān)系如下表所示。

          SQL 中的數(shù)據(jù)類型對應(yīng)的 Java 數(shù)據(jù)類型
          BITBOOLboolean
          INTEGERint
          BIGINTlong
          REALfloat
          FLOAT、 DOUBLEdouble
          CHAR、 VARCHARString
          DECIMALBigDecimal
          DATEjava.sql.DateLocalDate
          TIMEjava.sql.Time、 LocalTime

          此外,雖然我們在 JDBC 的簡介部分在初始化和建立連接時使用的是用 Statement 來創(chuàng)建一個對象并用于后續(xù)操作,但是在實際使用過程中時,SQL 參數(shù)基本都是從方法參數(shù)傳入的,這時使用 Statement 就十分容易引起 SQL 注入,為了解決這一問題,大牛們提出了如下兩個辦法:

          1. 對字符串中的參數(shù)進行轉(zhuǎn)義,然后利用轉(zhuǎn)義后的參數(shù)來進行操作。但是轉(zhuǎn)義十分麻煩,而且一使用 SQL,我們就必須增加轉(zhuǎn)義代碼。
          2. 利用 PreparedStatement,它利用 ? 作為占位符,將數(shù)據(jù)聯(lián)通 SQL 本身傳遞給數(shù)據(jù)庫,從而保證每次傳給數(shù)據(jù)庫的 SQL 語句都是保持一致的,每次變動的只是占位符中的數(shù)據(jù)不同。通過使用 PreparedStatement,我們就能夠 完全避免 SQL 注入 問題。

          針對后續(xù)利用 JDBC 操作數(shù)據(jù)庫的過程,為了盡量避免 SQL 注入問題,我們優(yōu)先采用 PreparedStatement 而非 Statement.

          查詢數(shù)據(jù)

          首先,我們來進行查詢操作。進行查詢時,可以總結(jié)為如下幾個步驟:

          1. 通過創(chuàng)建一個 Connection 對象從而建立連接;
          2. 然后利用 prepareStatement() 方法創(chuàng)建一個 PreparedStatement 對象并傳入 SQL 語句,用于執(zhí)行查詢操作;
          3. 接著執(zhí)行 PreparedStatement 對象所提供的 executeQuery() 方法,獲取查詢結(jié)果并返回到一個 ResultSet 結(jié)果集中;
          4. 最后則是利用 ResultSet 對象的 next() 方法去讀取我們所查詢返回的結(jié)果;

          需要注意的地方:

          1. 如果你不是利用 try-with-source 的方式,那么一定要記得在使用完連接之后記得釋放資源;
          2. 結(jié)果集 ResultSet 中,索引位置是從 1 開始的,而不是從 0 開始,這一點要特別注意!
          import java.sql.*;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : QueryTest
           * @date : 2021/4/23 14:01
           * @description : 查詢
           */


          public class QueryTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8";
                  String username = "root";
                  String password = "0908";
                  String queryString = "SELECT * FROM students";
                  try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(queryString); ResultSet resultSet = preparedStatement.executeQuery();) {
                      System.out.println("連接成功");

                      System.out.println("查詢到的信息如下:");
                      while (resultSet.next()) {
          //                查詢到的結(jié)果索引從 1 開始
                          System.out.println("id:" + resultSet.getLong(1) + "\tname:" + resultSet.getString(2) + "\tgender:" + resultSet.getInt(3) + "\tgrade:" + resultSet.getLong(4) + "\tscore:" + resultSet.getLong(5));
                      }
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          增加數(shù)據(jù)

          即插入一條新記錄,和查詢語句很像,但是區(qū)別在于最后 PreparedStatement 對象執(zhí)行的不是 executeQuery(),而是 executeUpdate(). 插入記錄的步驟總結(jié)如下:

          1. 創(chuàng)建 Connection 對象從而建立連接;
          2. 利用 prepareStatement() 方法創(chuàng)建一個 PreparedStatement 對象并傳入 SQL 語句,用于執(zhí)行插入操作;
          3. 然后依次設(shè)置占位符所代表的值;
          4. 執(zhí)行 PreparedStatement 對象所提供的 executeUpdate() 方法,此時返回的是一個 int 類型的數(shù),表示插入記錄的條數(shù);
          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : InsertTest
           * @date : 2021/4/23 15:04
           * @description : 新增數(shù)據(jù)
           */


          public class InsertTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8";
                  String username = "root";
                  String password = "110120";
                  String insertString = "INSERT INTO students VALUES (?,?,?,?,?)";

                  try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(insertString);) {
                      System.out.println("連接成功");
                      //            依次插入數(shù)據(jù)
                      preparedStatement.setLong(1302);
                      preparedStatement.setString(2"小藍");
                      preparedStatement.setInt(30);
                      preparedStatement.setLong(43);
                      preparedStatement.setLong(5100);
                      System.out.println("插入數(shù)據(jù)成功");
                      preparedStatement.executeUpdate();
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          新增數(shù)據(jù)后,接著查詢數(shù)據(jù),得到如下結(jié)果,可以看到我們新插入的數(shù)據(jù)成功加入到了數(shù)據(jù)庫中!

          刪除數(shù)據(jù)

          刪除數(shù)據(jù)和新增數(shù)據(jù)的方式基本一樣,兩者最大的區(qū)別在于 SQL 語句的不同,刪除操作利用的是 DELETE 語句,能一次刪除若干列。

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : DeleteTest
           * @date : 2021/4/23 15:23
           * @description : 刪除數(shù)據(jù)
           */


          public class DeleteTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?charactersetEncoding=UTF-8";
                  String username = "root";
                  String password = "0908";
                  String deleteString = "DELETE FROM students WHERE id = ?";
                  try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(deleteString);) {
                      System.out.println("連接成功");
                      preparedStatement.setLong(1101);
                      preparedStatement.executeUpdate();
                      System.out.println("刪除成功");
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          刪除數(shù)據(jù)后,接著查詢數(shù)據(jù),得到如下結(jié)果,可以看到 id = 101 的數(shù)據(jù)列已經(jīng)被刪除了,說明我們刪除數(shù)據(jù)成功了!

          修改數(shù)據(jù)

          修改數(shù)據(jù)的方式同刪除數(shù)據(jù)和新增數(shù)據(jù)基本一致,最大的區(qū)別在于 SQL 語句的不同,修改操作利用的是 UPDATE 語句,能一次更新若干列。

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : UpdateTest
           * @date : 2021/4/23 15:23
           * @description : 更新數(shù)據(jù)
           */


          public class UpdateTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?charactersetEncoding=UTF-8";
                  String username = "root";
                  String password = "0908";
                  String updateString = "UPDATE students SET name = ? WHERE id = ?";
                  try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(updateString);) {
                      System.out.println("連接成功");
                      preparedStatement.setString(1"村雨遙");
                      preparedStatement.setLong(2201);
                      preparedStatement.executeUpdate();
                      System.out.println("更新成功");
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          修改數(shù)據(jù)后,接著查詢數(shù)據(jù),得到如下結(jié)果,可以看到 id = 201 對應(yīng)的數(shù)據(jù)列中,name 從小黃變成了村雨遙,說明數(shù)據(jù)更新成功。

          注意

          當(dāng)我們的數(shù)據(jù)庫表設(shè)置自增主鍵后,在新增數(shù)據(jù)時無需指定主鍵也會自動更新。但是在獲取自增主鍵的值時,不能先插入再查詢,否則可能會導(dǎo)致沖突。要正確獲取自增主鍵,需要在創(chuàng)建 PreparedStatement 時,指定一個標志位 RETURN_GENERATED_KEYS,用于表示 JDBC 驅(qū)動必須返回插入的自增主鍵。

          假設(shè)我們創(chuàng)建表時,設(shè)置了自增長的鍵:

          CREATE TABLE students(
           id int(11) AUTO_INCREMENT,
              …
          );

          此時無論是 executeQuery() 還是 execureUpdate() 都不會返回這個自增長的 id,所以需要在創(chuàng)建 PreparedStatement 對象時加入 Statement.RETURN_GENERATED_KEYS 參數(shù)以確保會返回自增長 ID,然后通過 getGeneratedKeys 獲取該字段;

          import java.sql.*;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : QueryTest
           * @date : 2021/4/23 18:01
           * @description : 自增主鍵查詢
           */


          public class QueryTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8";
                  String username = "root";
                  String password = "12345";
                  String queryString = "INSET INTO students VALUES(null,?,……)";
                  try (Connection connection = DriverManager.getConnection(url, username, password); PreparedStatement preparedStatement = connection.prepareStatement(queryString, Statement.RETURN_GENERATED_KEYS); ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
                      System.out.println("連接成功");
                      preparedStatement.setString(1"村雨遙");
                      ……
                      preparedStatement.executeUpdate();
                      System.out.println("查詢到的信息如下:");
                      while (resultSet.next()) {
          //                查詢到的結(jié)果索引從 1 開始
                          System.out.println("id:" + resultSet.getLong(1));
                      }
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          JDBC 工具類

          觀察上面的代碼,我們可以注意到每次都需要注冊驅(qū)動、傳遞參數(shù),關(guān)閉連接等操作,為了提高工具通用性,我們利用配置文件來配置數(shù)據(jù)庫相關(guān)信息,然后創(chuàng)建一個 JDBC 工具類來簡化上述操作。

          1. 首先在 src 目錄下創(chuàng)建一個配置文件 jdbc.properties,并且填入數(shù)據(jù)庫的相關(guān)信息;
          url=jdbc:mysql://localhost/demo?characterEncoding=UTF-8
          user=root
          password="12345"
          driver=com.mysql.jdbc.cj.Driver
          1. 創(chuàng)建工具類
          import java.io.FileReader;
          import java.io.IOException;
          import java.net.URL;
          import java.sql.*;
          import java.util.Properties;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : JDBCUtils
           * @date : 2021/4/24 15:10
           * @description : JDBC 工具類
           */


          public class JDBCUtils {
              //    配置文件中的各個參數(shù)
              private static String url;
              private static String user;
              private static String password;
              private static String driver;

              //    靜態(tài)代碼塊
              static {
                  try {
          //        讀取配置文件并獲取參數(shù)值
          //        創(chuàng)建集合類
                      Properties properties = new Properties();

          //        獲取配置文件所在位置
                      ClassLoader classLoader = JDBCUtils.class.getClassLoader();
                      URL resource = classLoader.getResource("jdbc.properties");
                      String path = resource.getPath();
                      System.out.println("配置文件所在位置");
          //        加載配置文件
                      properties.load(new FileReader(path));

          //            獲取參數(shù)的值并賦值
                      url = properties.getProperty("url");
                      user = properties.getProperty("user");
                      password = properties.getProperty("password");
                      driver = properties.getProperty("driver");

          //            注冊驅(qū)動
                      Class.forName(driver);
                  } catch (IOException | ClassNotFoundException e) {
                      e.printStackTrace();
                  }
              }

              /**
               * @param
               * @return 連接對象
               * @description 獲取連接
               * @date 2021/4/24 15:24
               * @author cunyu1943
               * @version 1.0
               */

              public static Connection getConnection() {
                  try {
                      return DriverManager.getConnection(url, user, password);
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
                  return null;
              }

              /**
               * @param preparedStatement 預(yù)聲明
               * @param connection        連接對象
               * @return
               * @description 關(guān)閉連接
               * @date 2021/4/24 15:27
               * @author cunyu1943
               * @version 1.0
               */

              public static void close(PreparedStatement preparedStatement, Connection connection) {
                  if (preparedStatement != null) {
                      try {
                          preparedStatement.close();
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }

                  if (connection != null) {
                      try {
                          connection.close();
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }
              }


              /**
               * @param resultSet         結(jié)果集
               * @param preparedStatement 預(yù)聲明對象
               * @param connection        連接對象
               * @return
               * @description 關(guān)閉連接
               * @date 2021/4/24 15:28
               * @author cunyu1943
               * @version 1.0
               */

              public static void close(ResultSet resultSet, PreparedStatement preparedStatement, Connection connection) {
                  if (resultSet != null) {
                      try {
                          resultSet.close();
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }

                  if (preparedStatement != null) {
                      try {
                          preparedStatement.close();
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }

                  if (connection != null) {
                      try {
                          connection.close();
                      } catch (SQLException throwables) {
                          throwables.printStackTrace();
                      }
                  }
              }
          }

          JDBC 事務(wù)

          事務(wù) 4 大特性

          事務(wù)是一個不可分割的數(shù)據(jù)庫操作序列,也是數(shù)據(jù)庫并發(fā)控制的基本單位,其執(zhí)行結(jié)果必須使數(shù)據(jù)庫從一種一致性狀態(tài)切換到另一中一致性狀態(tài)。事務(wù)是邏輯上的一組操作,要么都執(zhí)行,要么都不執(zhí)行。事務(wù)能夠在數(shù)據(jù)庫提交工作時確保要么所有修改都保存,要么所有修改都不保存。即事務(wù)是邏輯上的一組操作,要么都執(zhí)行,要么都不執(zhí)行。

          1. 原子性(Atomicity)

          原子性是整個數(shù)據(jù)庫事務(wù)中不可分割的工作單位,只有事務(wù)中的所有的數(shù)據(jù)庫操作都執(zhí)行成功,才代表整個事務(wù)成功,如果其中任一環(huán)節(jié)執(zhí)行失敗,那么就算已經(jīng)執(zhí)行成功的 SQL 語句也必須撤銷,回滾到事務(wù)執(zhí)行前的狀態(tài)。即原子性能夠保證 動作要么全部完成,要么完全不起作用。 即事務(wù)是最小的執(zhí)行單位,不允許分割。

          1. 一致性(Consistency)

          指事務(wù)將數(shù)據(jù)庫從一種一致性狀態(tài)變?yōu)榱硪环N一致性狀態(tài)。在事務(wù)開始前后,數(shù)據(jù)庫的完整性約束未被破壞。在事務(wù)執(zhí)行前后,數(shù)據(jù)能夠保持一致,多個事務(wù)對統(tǒng)一數(shù)據(jù)讀取的結(jié)果相同

          1. 隔離性(Isolation)

          并發(fā)訪問數(shù)據(jù)庫時,隔離性要求每個讀寫事務(wù)對其他事務(wù)的操作對象能夠相互分離,即一個用戶的事務(wù)不被其他事務(wù)所干擾,各并發(fā)事務(wù)間數(shù)據(jù)庫是獨立的;

          1. 持久性(Durability)

          表示事務(wù)一旦被提交,其結(jié)果就是永久性的,它對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即便數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其產(chǎn)生影響;

          臟讀、幻讀 & 不可重復(fù)讀

          了解事務(wù)隔離級別之前,先來看看這幾個讀的概念:

          1. 臟讀(Dirty Read)

          表示某一事務(wù)已經(jīng)更新了一份數(shù)據(jù),另一個事務(wù)在此時讀取了同一份數(shù)據(jù)。當(dāng)前一個事務(wù)撤銷操作后,就會導(dǎo)致后一個事務(wù)所讀取的數(shù)據(jù)不正確。

          1. 幻讀(Phantom Read)

          在一個事務(wù)的兩次查詢中數(shù)據(jù)量不一致,假如有一個事務(wù)查詢了幾列數(shù)據(jù),同時另一個事務(wù)中在此時查詢了新的數(shù)據(jù),則查詢事務(wù)在后續(xù)查詢中,就會發(fā)現(xiàn)數(shù)據(jù)比最開始的查詢數(shù)據(jù)更豐富。

          1. 不可重復(fù)讀(Non-repeatable Read)

          一個事務(wù)中兩次查詢數(shù)據(jù)不一致,有可能是因為兩次查詢過程中插入了一個更新原有數(shù)據(jù)的事務(wù)。

          注意:不可重復(fù)讀和幻讀的區(qū)別在于:

          不可重復(fù)讀的重點在于修改, 比如多次讀取一條記錄發(fā)現(xiàn)其中某些列的值被修改,而 幻讀的重點在于新增或刪除,比如多次讀取一條記錄發(fā)現(xiàn)記錄增多或減少了。

          隔離級別

          SQL 標準定義了 4 個隔離級別,隔離級別從低到高分別是:

          1. READ-UNCOMMITTED(讀取未提交)

          最低的隔離級別,允許讀取尚未提交的數(shù)據(jù)變更,可能導(dǎo)致臟讀、幻讀或不可重復(fù)讀。

          1. READ-COMMITTED(讀取已提交)

          允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),能夠阻止臟讀,但可能導(dǎo)致幻讀或不可重復(fù)讀

          1. REPEATABLE-READ(可重復(fù)讀)

          對同一字段的多次讀取結(jié)果時一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,能夠阻止臟讀和不可重復(fù)讀,但可能導(dǎo)致幻讀

          1. SERIALIZABLE(可串行化)

          最高的隔離級別,完全服從 ACID 的隔離級別,所有事務(wù)依次逐個執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,能夠防止臟讀、幻讀以及不可重復(fù)讀。

          以下是 SQL 隔離級別和各種讀之間的關(guān)系:

          隔離級別臟讀不可重復(fù)讀幻讀
          READ-UNCOMMITTED???
          READ-COMMITTED???
          REPEATABLE-READ???
          SERIALIZABLE???

          實例

          關(guān)于回滾,主要涉及 Connection 對象,常用的三個方法如下:

          返回值方法描述
          voidsetAutoCommit(boolean autoCommit)設(shè)定連接的自動提交模式,true 表示自動提交,false 表示手動提交
          voidcommit()使上次提交/回滾以來所做的所有更改成為永久更改,并釋放此 Connection 對象當(dāng)前持有的所有數(shù)據(jù)庫鎖
          voidrollback()撤銷當(dāng)前十五中所做的所有更改,并釋放此 Connection 對象當(dāng)前持有的所有數(shù)據(jù)庫鎖

          以下是一個回滾實例,我們當(dāng)我們第一次插入一條數(shù)據(jù)時,由于是新數(shù)據(jù),所以不會報錯,但是如果我們執(zhí)行一次程序之后再次執(zhí)行,此時按理來說就會報錯,因為插入的數(shù)據(jù)重復(fù),這時候利用事務(wù)就可以十分方便的解決這個問題,我們設(shè)置插入出錯就回滾到未出錯之前的狀態(tài),這樣就能保證插入數(shù)據(jù)不會報錯了。

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : AffairTest
           * @date : 2021/4/23 22:35
           * @description : 事務(wù)
           */


          public class AffairTest {
              public static void main(String[] args) {
                  try {
                      Class.forName("com.mysql.cj.jdbc.Driver");
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }

                  String url = "jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8";
                  String username = "root";
                  String password = "12345";
                  String insertString = "INSERT INTO students VALUES (?,?,?,?,?)";
                  Connection connection = null;
                  PreparedStatement preparedStatement = null;

                  try {
                      connection = DriverManager.getConnection(url, username, password);
          //            關(guān)閉自動提交
                      connection.setAutoCommit(false);
                      preparedStatement = connection.prepareStatement(insertString);
                      System.out.println("連接成功");
          //            依次插入數(shù)據(jù)
                      preparedStatement.setLong(1401);
                      preparedStatement.setString(2"小紫");
                      preparedStatement.setInt(30);
                      preparedStatement.setLong(44);
                      preparedStatement.setLong(588);
                      preparedStatement.executeUpdate();
          //            如果沒有出錯,則提交事務(wù)
                      connection.commit();
                      System.out.println("插入數(shù)據(jù)成功");

                  } catch (SQLException throwables) {
          //            一旦出錯,則回滾事務(wù)
                      try {
                          connection.rollback();
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  } finally {
          //            最后關(guān)閉連接
                      if (connection != null) {
                          try {
                              connection.close();
                          } catch (SQLException throwables) {
                              throwables.printStackTrace();
                          }
                      }
                      if (preparedStatement != null) {
                          try {
                              preparedStatement.close();
                          } catch (SQLException throwables) {
                              throwables.printStackTrace();
                          }
                      }
                  }
              }
          }

          除了上述回滾的方式外,JDBC 還支持設(shè)置保存點的方式,我們可以使用事務(wù)回滾到指定的保存點,主要涉及的方法如下:

          • setSavepoint(String savePointName):創(chuàng)建新的保存點,返回一個 SavePoint 對象;
          • rollback(String savePointName):回滾到指定保存點;

          連接池

          簡介

          當(dāng)我們使用多線程時,每個線程如果都需要連接數(shù)據(jù)庫來執(zhí)行 SQL 語句,那么每個線程都得創(chuàng)建一個連接,然后在使用之后關(guān)閉。這個創(chuàng)建和關(guān)閉連接的過程是十分耗時的,一旦多線程并發(fā)時,就容易導(dǎo)致系統(tǒng)卡頓。針對這一問題,提出使用數(shù)據(jù)庫連接池。數(shù)據(jù)庫連接池,其實就相當(dāng)于一個集合,是一個存放數(shù)據(jù)庫連接的容器。當(dāng)我們的系統(tǒng)初始化好之后,集合就被創(chuàng)建,集合中會申請一些連接對象,當(dāng)用戶來訪問數(shù)據(jù)庫時,從集合中獲取連接對象,一旦用戶訪問完畢,就將連接對象返還給容器。

          使用數(shù)據(jù)庫連接池的優(yōu)點:一來是節(jié)約資源,二來提高了用戶訪問的效率。

          常用數(shù)據(jù)庫連接池

          C3P0

          1. 導(dǎo)包

          首先需要導(dǎo)包,先去下載 C3P0 對象的 jar 包,下載地址:https://sourceforge.net/projects/c3p0/,然后將其中的如下兩個包導(dǎo)入;

          1. 定義配置文件

          創(chuàng)建 C3P0 對應(yīng)的配置文件,注意:配置文件一般放在 src 路徑下,而且文件的名稱要必須為以下其中的一個:

          • c3p0.properties
          • c3p0-config.xml
          <c3p0-config>
              <!-- 使用默認的配置讀取連接池對象 -->
              <default-config>
                  <!--  連接參數(shù) -->
                  <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
                  <property name="jdbcUrl">jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8</property>
                  <property name="user">root</property>
                  <property name="password">0908</property>

                  <!-- 連接池參數(shù) -->
                  <!--    初始化申請的連接數(shù)-->
                  <property name="initialPoolSize">5</property>
                  <!--        最大連接數(shù)-->
                  <property name="maxPoolSize">10</property>
                  <!--      超時時間-->
                  <property name="checkoutTimeout">3000</property>
              </default-config>
          </c3p0-config>
          1. 創(chuàng)建連接池對象

          2. 獲取連接對象

          import com.mchange.v2.c3p0.ComboPooledDataSource;

          import javax.sql.DataSource;
          import java.sql.Connection;
          import java.sql.SQLException;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : C3POTest
           * @date : 2021/4/24 16:01
           * @description : C3PO 連接池
           */


          public class C3POTest {
              public static void main(String[] args) {
          //        創(chuàng)建數(shù)據(jù)庫連接池對象
                  DataSource dataSource = new ComboPooledDataSource();
          //        獲取連接對象
                  try {
                      Connection connection = dataSource.getConnection();
                      System.out.println(connection);
                  } catch (SQLException throwables) {
                      throwables.printStackTrace();
                  }
              }
          }

          Druid

          1. 導(dǎo)包

          導(dǎo)入 Druid 的 jar 包,下載地址:https://repo1.maven.org/maven2/com/alibaba/druid/

          1. 定義配置文件

          配置文件名稱無要求,但是后綴名為 .properties,而且可以存放在任意目錄下;

          driver=com.mysql.cj.jdbc.Driver
          url=jdbc:mysql://localhost:3306/javalearning?characterEncoding=UTF-8
          username=root
          password=12345
          initialSize=5
          maxActive=10
          maxWait=3000
          1. 加載配置文件
          2. 創(chuàng)建連接池對象
          3. 獲取連接對象
          import com.alibaba.druid.pool.DruidDataSourceFactory;

          import javax.sql.DataSource;
          import java.io.InputStream;
          import java.sql.Connection;
          import java.util.Properties;

          /**
           * @author : cunyu
           * @version : 1.0
           * @className : DruidTest
           * @date : 2021/4/24 19:56
           * @description : Druid 連接池
           */


          public class DruidTest {
              public static void main(String[] args) {
                  try {
          //            加載配置文件
                      Properties properties = new Properties();
                      InputStream resourceAsStream = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
                      properties.load(resourceAsStream);
          //            獲取連接池對象
                      DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
          //            獲取連接
                      Connection connection = dataSource.getConnection();
                      System.out.println(connection);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          總結(jié)

          今天的內(nèi)容到此就結(jié)束了,老規(guī)矩,點贊關(guān)注走一波 ??。

          對于文中有錯或遺漏的地方,還煩請各位大佬在評論區(qū)指出來。我是村雨遙,一個技術(shù)棧主要為 Java 的菜鳥程序員,關(guān)注我,一起學(xué)習(xí)成長吧!



          瀏覽 131
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品成人99一区无码 | 无码看电影 | 天堂中文在线观看 | 亚洲A∨无码无在线观看 | 久久国产精品色综合 |