天天看點

JDBC

目錄

  • JDBC 介紹
  • JDBC 快速入門
  • JDBC API 詳解
    • DriverManager(驅動管理對象)
    • Connection(資料庫連接配接對象)
    • Statement(SQL 執行對象)
    • ResultSet(結果集對象)
  • SQL 注入與預編譯
    • SQL 注入
    • PreparedStatement 預編譯
  • JDBC 案例:student 表 CRUD
    • 表資料
    • JDBC 配置資訊
    • domain 實體類
    • JDBC 工具類
    • Dao 層
    • Service 層
    • Controller 層
  • 事務管理
  • 連接配接池
    • 連接配接池概述
    • 開源連接配接池:C3P0
    • 開源連接配接池:Druid
  • DBUtils
    • DBUtils 介紹
    • 使用案例
      • QueryRunner 類(執行對象)
      • ResultSetHandler 接口(結果集對象)
      • ThreadLocal(目前線程對象)

JDBC 概念:

JDBC(Java DataBase Connectivity,Java 資料庫連接配接)是一種用于執行 SQL 語句的 Java API,可以為多種關系型資料庫提供統一通路,它是由一組用 Java 語言編寫的類和接口組成的。

JDBC 本質:

其實就是 Java 官方提供的一套規範(接口),用于幫助開發人員快速實作不同關系型資料庫的連接配接。

package com.demo;

import java.sql.*;

public class JdbcDemo {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 導入jar包
        // 2. 注冊驅動
        // Class.forName("com.mysql.jdbc.Driver");  // Mysql5 驅動
        Class.forName("com.mysql.cj.jdbc.Driver");  // Mysql8 驅動

        // 3. 擷取連接配接對象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/world?serverTimezone=UTC", "root", "admin");

        // 4. 擷取執行對象
        Statement statement = connection.createStatement();

        // 5. 執行SQL語句,并接收結果
        String sql = "select * from city limit 5";
        ResultSet resultSet = statement.executeQuery(sql);

        // 6. 處理結果
        while(resultSet.next()) {
            System.out.println(resultSet.getString("Name") + "\t" + resultSet.getString("Population"));
        }

        // 7. 釋放資源
        statement.close();
        connection.close();
    }
}
           

1)注冊驅動(告訴程式該使用哪一個資料庫驅動)

  • 注冊給定的驅動程式:static void registerDriver(Driver driver);
  • 寫代碼時使用:Class.forName("com.mysql.jdbc.Driver");
  • 通過檢視源碼發現:在 com.mysql.jdbc.Driver 類中存在以下靜态代碼塊
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
           
  • 我們不需要通過 DriverManager 調用靜态方法 registerDriver,因為隻要 Driver 類被使用,則會執行其靜态代碼塊完成注冊驅動。
  • Mysql5 之後可以省略注冊驅動的步驟。因為在驅動 jar 包中,存在一個 javasql.Driver 配置檔案,檔案中指定了 com.mysqljdbc.Driver。

2)擷取資料庫連接配接對象

static Connection getConnection(String url, String user, String password);

  • url:指定連接配接的路徑。文法:

    jdbc:mysql://ip位址(域名):端口号/庫名

  • user:資料庫使用者名
  • password:資料庫密碼
  • 傳回值 Connection:資料庫連接配接對象

  • 擷取執行對象
    • 擷取普通執行對象:

      Statement createStatement();

    • 擷取預編譯執行對象:

      PreparedStatement prepareStatement(String sql);

  • 管理事務
    • 開啟事務:setAutoCommit(boolean autoCommit); // 參數為 false,則開啟事務
    • 送出事務:commit();
    • 復原事務:rollback();
  • 釋放資源
    • 立即釋放連接配接對象:void close();

  • 執行 DML 語句:

    int executeUpdate(String sql);

    • 傳回值 int:傳回影響的行數。
    • 參數 sql:可以執行 insert、update、delete 語句
  • 執行 DQL 語句:

    ResultSet executeQuery(String sql);

    • 傳回值 ResultSet:封裝查詢的結果。
    • 參數 sql:可以執行select語句。
    • 立即釋放執行對象:void close();

  • 判斷結果集中是否還有資料:

    boolean next();

    • 有資料則傳回 true,并将索引向下移動一行
    • 沒有資料則傳回 false
  • 擷取結果集中的資料:

    XXX getXxx("列名");

    • XXX 代表要擷取的某列資料的類型
    • 例如:

      String getString("name");

      int getInt("age");

    • 立即釋放結果集對象:void close();

SQL 注入示範:在登入界面,輸入一個錯誤的使用者名或密碼,也可以登入成功

JDBC

SQL 注入的原理:

  • 按照正常道理來說,我們在密碼處輸入的所有内容,都應該作為密碼(這個參數)。
  • 但是現在 Statement 對象在執行 SQL 語句時,将輸入的内容當做查詢條件來執行了。

PreparedStatement 即預編譯 SQL 語句的執行對象,是 SQL 注入的防禦手段之一。其原理是:在執行 SQL 語句之前,将 SQL 語句進行提前編譯,在明确 SQL 語句的格式(執行計劃)後,就不會改變了。是以剩餘的内容都會認為是參數。

參數使用

?

作為占位符:

  • 為參數指派的方法:

    setXxx(參數 1, 參數 2);

    • 參數 1:? 的位置編号(編号從 1 開始)
    • 參數 2:? 的實際參數
  • 執行 SQL 語句的方法
    • 執行 insert、update、delete 語句:

      int executeUpdate();

    • 執行 select 語句:

      ResultSet executeQuery();

-- 建立student表
CREATE TABLE student(
    sid INT PRIMARY KEY AUTO_INCREMENT,  -- 學生id
    NAME VARCHAR(20),  -- 學生姓名
    age INT,  -- 學生年齡
    birthday DATE  -- 學生生日
);

-- 添加資料
INSERT INTO student VALUES (NULL,'張三',23,'1999-09-23'), (NULL,'李四',24,'1998-08-10'), (NULL,'王五',25,'1996-06-06'), (NULL,'趙六',26,'1994-10-20');
           

config.properties:

driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC
username=root
password=admin
           

Student 實體類:

package com.bean;

import java.util.Date;

public class Student {

    private Integer sid;
    private String name;
    private Integer age;
    private Date birthday;

    public Student() {
    }

    public Student(Integer sid, String name, Integer age, Date birthday) {
        this.sid = sid;
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "student{" +
                "sid=" + sid +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}
           

package com.utils;

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

// JDBC 工具類
public class JdbcUtil {

    // 私有構造方法
    private JdbcUtil(){}

    // 聲明所需要的配置變量
    private static String driverClass;
    private static String url;
    private static String username;
    private static String password;
    private static Connection connection;

    // 靜态代碼塊:讀取配置檔案的資訊為變量指派,注冊驅動
    static {
        try{
            // 讀取配置檔案
            InputStream resourceAsStream = JdbcUtil.class.getClassLoader().getResourceAsStream("config.properties");
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            // 指派
            driverClass = properties.getProperty("driverClass");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            // 注冊驅動
            Class.forName(driverClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 擷取資料庫連接配接對象
    public static Connection getConnection() {
        try {
            connection = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    // 釋放資源
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

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

    // 釋放資源
    public static void close(Connection connection, Statement statement) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

StudentDao.java:

package com.dao;

import com.bean.Student;

import java.util.ArrayList;

// Dao層接口
public interface StudentDao {
    // 查詢所有學生資訊
    public abstract ArrayList<Student> findAll();

    // 根據id條件查詢
    public abstract Student findById(Integer id);

    // 新增學生資訊
    public abstract int insert(Student student);

    // 修改學生資訊
    public abstract int update(Student student);

    // 根據id删除學生資訊
    public abstract int delete(Integer id);
}
           

StudentDaoImpl.java:

package com.dao;

import com.bean.Student;
import com.utils.JdbcUtil;

import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;


public class StudentDaoImpl implements StudentDao{

    // 查詢所有學生資訊
    @Override
    public ArrayList<Student> findAll() {
        ArrayList<Student> studentList = new ArrayList<>();
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            connection = JdbcUtil.getConnection();
            String sql = "select * from student";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            // 處理結果集
            while (resultSet.next()) {
                Integer sid = resultSet.getInt("sid");
                String name = resultSet.getString("name");
                Integer age = resultSet.getInt("age");
                Date birthday = resultSet.getDate("birthday");
                // 封裝Student對象
                Student student = new Student(sid, name, age, birthday);
                // 将student對象儲存到集合中
                studentList.add(student);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            JdbcUtil.close(connection, statement, resultSet);
        }
        // 傳回集合對象
        return studentList;
    }

    // 條件查詢,根據id查詢學生資訊
    @Override
    public Student findById(Integer id) {
        Student student = new Student();
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            connection = JdbcUtil.getConnection();
            String sql = "select * from student where sid=?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            resultSet = preparedStatement.executeQuery();
            // 處理結果集
            while (resultSet.next()) {
                Integer sid = resultSet.getInt("sid");
                String name = resultSet.getString("name");
                Integer age = resultSet.getInt("age");
                Date birthday = resultSet.getDate("birthday");
                // 封裝Student對象
                student.setSid(sid);
                student.setName(name);
                student.setAge(age);
                student.setBirthday(birthday);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.close(connection, statement, resultSet);
        }
        return student;
    }

    // 添加學生資訊
    @Override
    public int insert(Student student) {
        Connection connection = null;
        Statement statement = null;
        int result = 0;
        try {
            connection = JdbcUtil.getConnection();
            Date raw_birthday = student.getBirthday();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String birthday = simpleDateFormat.format(raw_birthday);
            String sql = "insert into student (sid, name, age, birthday) values (?, ?, ?, ?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, student.getSid());
            preparedStatement.setString(2, student.getName());
            preparedStatement.setInt(3, student.getAge());
            preparedStatement.setDate(4, (java.sql.Date) student.getBirthday());
            result = preparedStatement.executeUpdate();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.close(connection, statement);
        }
        return result;
    }

    // 修改學生資訊
    @Override
    public int update(Student student) {
        Connection connection = null;
        Statement statement = null;
        int result = 0;
        try {
            connection = JdbcUtil.getConnection();
            Date raw_birthday = student.getBirthday();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String birthday = simpleDateFormat.format(raw_birthday);
            String sql = "UPDATE student SET name=?, age=?, birthday=? WHERE sid=?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, student.getName());
            preparedStatement.setInt(2, student.getAge());
            preparedStatement.setDate(3, (java.sql.Date) student.getBirthday());
            result = preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.close(connection, statement);
        }
        return result;
    }

    @Override
    public int delete(Integer id) {
        Connection connection = null;
        Statement statement = null;
        int result = 0;
        try {
            connection = JdbcUtil.getConnection();
            String sql = "delete from student where sid=?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            result = preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.close(connection, statement);
        }
        return result;
    }
}
           

StudentService.java:

package com.service;

import com.bean.Student;

import java.util.ArrayList;

// service 層接口
public interface StudentService {
    //查詢所有學生資訊
    public abstract ArrayList<Student> findAll();

    //條件查詢,根據id擷取學生資訊
    public abstract Student findById(Integer id);

    //新增學生資訊
    public abstract int insert(Student student);

    //修改學生資訊
    public abstract int update(Student student);

    //删除學生資訊
    public abstract int delete(Integer id);
}
           

StudentServiceImpl.java:

package com.service;

import com.bean.Student;
import com.dao.StudentDao;
import com.dao.StudentDaoImpl;

import java.util.ArrayList;

public class StudentServiceImpl implements StudentService {

    private StudentDao dao = new StudentDaoImpl();

    // 查詢所有學生資訊
    @Override
    public ArrayList<Student> findAll() {
        return dao.findAll();
    }

    // 根據id查詢指定學生資訊
    @Override
    public Student findById(Integer id) {
        return dao.findById(id);
    }

    // 添加學生資訊
    @Override
    public int insert(Student student) {
        return dao.insert(student);
    }

    // 修改學生資訊
    @Override
    public int update(Student student) {
        return dao.update(student);
    }

    // 删除學生資訊
    @Override
    public int delete(Integer id) {
        return dao.delete(id);
    }
}
           

StudentController.java:

package com.controller;

import com.bean.Student;
import com.service.StudentService;
import com.service.StudentServiceImpl;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Date;

public class StudentController {

    private StudentService studentService = new StudentServiceImpl();

    // 查詢所有學生資訊
    @Test
    public void findAll() {
        ArrayList<Student> studentList = studentService.findAll();
        for (Student student: studentList) {
            System.out.println(student);
        }
    }

    // 根據id查詢指定學生資訊
    @Test
    public void findById() {
        Student student = studentService.findById(3);
        System.out.println("查詢成功: "+student);
    }

    // 添加學生資訊
    @Test
    public void insert() {
        Student student = new Student(5, "陳七", 19, new Date());
        int result = studentService.insert(student);
        if (result != 0) {
            System.out.println("學生資訊添加成功!");
        } else {
            System.out.println("學生資訊添加失敗!");
        }
    }

    // 修改學生資訊
    @Test
    public void update() {
        Student student = studentService.findById(1);
        student.setName("xiaoji");
        int result = studentService.update(student);
        if (result != 0) {
            System.out.println("學生資訊修改成功!");
        } else {
            System.out.println("學生資訊修改失敗!");
        }
    }

    // 删除學生資訊
    @Test
    public void delete() {
        int result = studentService.delete(1);
        if (result != 0) {
            System.out.println("學生資訊删除成功!");
        } else {
            System.out.println("學生資訊删除失敗!");
        }
    }
}
           

事務一般在 service 層控制管理,因為事務一般與業務耦合,而不是與通用的 dao 層耦合。

  • Service 接口:
import java.util.List;

public interface UserService {
    /**
     * 批量添加
     * @param users
     */
    void batchAdd(List<User> users);
}
           
  • ServiceImpl 實作類:
@Override
public void batchAdd(List<User> users) {
    // 擷取資料庫連接配接
    Connection connection = JDBCUtils.getConnection();
    try {
        // 開啟事務
        connection.setAutoCommit(false);
        for (User user : users) {
            // 1.建立ID,并把UUID中的-替換
            String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase();
            // 2.給user的uid指派
            user.setUid(uid);
            // 3.生成員工編号
            user.setUcode(uid);

            // 手動模拟異常
            //int n = 1 / 0;

            // 4.儲存
            userDao.save(connection,user);
        }
        // 送出事務
        connection.commit();
    }catch (Exception e){
        try {
            // 若遇到異常,復原事務
            connection.rollback();
        }catch (Exception ex){
            ex.printStackTrace();
        }
        e.printStackTrace();
    } finally {
        JDBCUtils.close(connection,null,null);
    }
}
           

資料庫連接配接背景

資料庫連接配接是一種關鍵的、有限的、昂貴的資源,這一點在多使用者的網頁應用程式中展現得尤為突出。對資料庫連接配接的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的性能名額。

資料庫連接配接池正是針對這種背景提出來的。

資料庫連接配接池

資料庫連接配接池負責配置設定、管理和釋放資料庫連接配接,它允許應用程式重複使用一個現有的資料庫連接配接,而不是再重建立立一個。這項技術能解決建立資料庫連接配接耗費資源和時間的問題,明顯提高對資料庫操作的性能。

資料庫連接配接池原理

JDBC

C3P0 是一個開源的 JDBC 連接配接池,使用它的開源項目有 Hibernate、Spring 等。

使用步驟:

  1. 導入 jar 包
  2. 導入配置檔案(c3p0-config.xml,檔案名不可改)到 src 目錄下
  3. 建立 c3p0 連接配接池對象
  4. 擷取資料庫連接配接進行使用

使用示例:

  • c3p0-config.xml:
<c3p0-config>
  <!-- 使用預設的配置讀取連接配接池對象 -->
  <default-config>
  	<!--  連接配接參數 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/world</property>
    <property name="user">root</property>
    <property name="password">admin</property>
    
    <!-- 連接配接池參數 -->
    <!--初始化的連接配接數量-->
    <property name="initialPoolSize">5</property>
    <!--最大連接配接數量-->
    <property name="maxPoolSize">10</property>
    <!--逾時時間-->
    <property name="checkoutTimeout">3000</property>
  </default-config>

  <!-- 自定義連接配接池對象 -->
  <named-config name="otherc3p0"> 
    <!-- 連接配接參數 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/world</property>
    <property name="user">root</property>
    <property name="password">admin</property>
    
    <!-- 連接配接池參數 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </named-config>
</c3p0-config>
           
  • 測試類:
import com.mchange.v2.c3p0.ComboPooledDataSource;

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

public class C3P0Test {

    public static void main(String[] args) throws SQLException {
        // 建立c3p0連接配接池對象
        DataSource dataSource = new ComboPooledDataSource();

        // 擷取資料庫連接配接進行使用
        Connection con = dataSource.getConnection();

        // 查詢全部學生資訊
        String sql = "SELECT * FROM student";
        PreparedStatement pst = con.prepareStatement(sql);
        ResultSet rs = pst.executeQuery();

        while(rs.next()) {
            System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
        }

        // 釋放資源
        rs.close();
        pst.close();
        con.close();  // 将連接配接對象歸還池中
    }
}

* **優化:抽取工具類**
~~~java
package com.itheima.util;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {
    // 得到一個資料源
    private static DataSource dataSource = new ComboPooledDataSource();
    
    
    public static DataSource getDataSource() {
        return dataSource;
    }

    //從資料源中得到一個連接配接對象
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("伺服器錯誤");
        }
    }
    
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        // 關閉資源
        if(rs!=null){
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(stmt!=null){
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if(conn!=null){
            try {
                conn.close(); 
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}
           

資料庫連接配接池有很多選擇,C3P0、DHCP 等,阿裡巴巴開源的 druid 作為一名後起之秀,憑借其出色的性能,也逐漸印入了大家的眼簾。

Druid 基本概念及架構介紹

  1. 通過 Properties 集合加載配置檔案
  2. 通過 Druid 連接配接池工廠類擷取資料庫連接配接池對象
  3. 擷取資料庫連接配接,進行使用

示例:

  • druid.properties:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/world
username=root
password=itheima
# 初始化連接配接數量
initialSize=5
# 最大連接配接數量
maxActive=10
# 逾時時間
maxWait=3000
           
import com.alibaba.druid.pool.DruidDataSourceFactory;

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

public class DruidTest {
    public static void main(String[] args) throws Exception {
        // 通過Properties集合加載配置檔案
        InputStream is = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
        Properties prop = new Properties();
        prop.load(is);

        // 通過Druid連接配接池工廠類擷取資料庫連接配接池對象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

        // 擷取資料庫連接配接,進行使用
        Connection con = dataSource.getConnection();

        // 查詢全部學生資訊
        String sql = "SELECT * FROM student";
        PreparedStatement pst = con.prepareStatement(sql);
        ResultSet rs = pst.executeQuery();

        while(rs.next()) {
            System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday"));
        }

        // 釋放資源
        rs.close();
        pst.close();
        con.close();    // 将連接配接對象歸還池中
    }
}
           
  • 優化:抽取工具類
public class DataSourceUtils {

    // 1.私有構造方法
    private DataSourceUtils(){}

    // 2.定義DataSource資料源變量
    private static DataSource dataSource;

    // 3.提供靜态代碼塊,完成配置檔案的加載和擷取連接配接池對象
    static {
        try{
            // 加載配置檔案
            InputStream is = DruidDemo1.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties prop = new Properties();
            prop.load(is);

            // 擷取資料庫連接配接池對象
            dataSource = DruidDataSourceFactory.createDataSource(prop);

        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    // 4.提供擷取資料庫連接配接的方法
    public static Connection getConnection() {
        Connection con = null;
        try {
            con = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }

    // 5.提供擷取資料庫連接配接池的方法
    public static DataSource getDataSource() {
        return dataSource;
    }

    // 6.提供DQL釋放資源的方法
    public static void close(Connection con, Statement stat, ResultSet rs) {
        if(con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

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

    // 提供DML釋放資源的方法
    public static void close(Connection con, Statement stat) {
        close(con, stat, null);
    }
}
           

什麼是 DBUtils ?其作用?

DBUtils 是 Apache 開源的 Java 程式設計中的資料庫操作實用工具,小巧簡單實用。

DBUtils 封裝了對 JDBC 的操作,簡化了 JDBC 操作,可以少寫代碼。

  1. 對于資料表的讀操作,他可以把結果轉換成 List、Array、Set 等 Java 集合,便于程式員操作。
  2. 對于資料表的寫操作,也變得很簡單(隻需寫 SQL 語句)。
  3. 可以使用資料源,使用 JNDI,資料庫連接配接池等技術來優化性能--重用已經建構好的資料庫連接配接對象。

DBUtils 的三個核心對象:

  1. QueryRunner 類

    :提供對 SQL 語句操作的 API,它主要有三個方法:
    • `query():用于執行 select
    • update()

      :用于執行 insert、update、delete
    • batch()

      :批處理
  2. ResultSetHandler 接口

    :用于定義 select 操作後怎樣封裝結果集。
  3. DbUtils 類

    :一個工具類,定義了關閉資源與事務處理的方法。

DBUtils 使用步驟:

    • c3p0-0.9.1.2.jar(其他連接配接對象均可)
    • commons-logging-1.1.1.jar
    • commons-beanutils-1.8.3.jar
    • commons-dbutils-1.4.jar
  1. 建立 QueryRunner 對象
  2. 使用 query 方法執行 select 語句
  3. 使用 ResultSetHandler 封裝結果集
  4. 使用 DbUtils 類釋放資源

構造函數:

  • new QueryRunner();

    • 其事務可以手動控制。
    • 此對象調用的方法(如 query、update、batrch)參數中要有 Connection 對象。
  • new QueryRunner(DataSource ds);

    • 其事務是自動控制的(一個 SQL 一個事務)。
    • 此對象調用的方法(如 query、update、batrch)參數中無需 Connection 對象。
  • C3P0Util.java
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {
    // 得到一個資料源
    private static DataSource dataSource = new ComboPooledDataSource();


    public static DataSource getDataSource() {
        return dataSource;
    }

    //從資料源中得到一個連接配接對象
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("伺服器錯誤");
        }
    }

    public static void close(Connection conn, Statement stmt, ResultSet rs){
        // 關閉資源
        if(rs!=null){
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(stmt!=null){
            try {
                stmt.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }

}
           
  • DBUtil
import com.bean.Student;
import org.apache.commons.dbutils.QueryRunner;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.jupiter.api.Test;
import org.apache.commons.dbutils.handlers.BeanListHandler;

public class DBUtil {

    @Test
    public void testDQL1() throws SQLException{
        //建立一個QueryRunner對象
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        //執行查詢語句,并傳回結果
        List<Student> list = qr.query("select * from student where sid=? and name=?", new BeanListHandler<Student>(Student.class), 3, "王五");
        for (Student student : list) {
            System.out.println(student);
        }
    }

    @Test
    public void testDQL2() throws SQLException{
        // 建立一個QueryRunner對象
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        List<Student> list = qr.query("select * from student", new ResultSetHandler<List<Student>>(){
            // 當query方法執行完select語句,就會将結果集以參數的形式傳遞進來
            public List<Student> handle(ResultSet rs) throws SQLException {
                List<Student> list  = new ArrayList<Student>();
                while(rs.next()){
                    Student student = new Student();
                    student.setSid(rs.getInt(1));
                    student.setName(rs.getString(2));
                    student.setAge(rs.getInt(3));
                    student.setBirthday(rs.getDate(4));
                    list.add(student);
                }
                return list;
            }
        });
        for (Student student : list) {
            System.out.println(student);
        }
    }

    @Test
    public void testDML() throws SQLException{
        //建立一個QueryRunner對象
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        // 傳回影響行數
        qr.update("insert into student (sid,name,age,birthday) values(?,?,?,?)", "6", "王八", "4", new Date());
    }

    @Test
    public void testBatchDQL() throws SQLException{
        //建立一個QueryRunner對象
        QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
        Object[][] params = new Object[10][];  // 高維代表執行次數;低維代表
        for (int i=0; i<10; i++) {
            params[i] = new Object[]{10+i, "菜"+i, 10+i, new Date()};
        }
        // 傳回影響行數
        qr.batch("insert into student (sid,name,age,birthday) values(?,?,?,?)", params);
    }

}
           

ResultSetHandler 下的所有結果處理器:

對象名 說明
ArrayHandler

适合取 1 條記錄。

把該條記錄的每列值封裝到一個 Object[] 中

ArrayListHandler

适合取多條記錄。

把每條記錄的每列值封裝到一個 Object[] 中,再把數組封裝到一個 List 中

ColumnListHandler

取某一列的資料。

把該條記錄的每列值封裝到 List 中

KeyedHandler

取多條記錄。

每一條記錄封裝到一個 Map 中,再把這個 Map 封裝到另外一個 Map 中,key 為指定的字段值

MapHandler

适合取1條記錄。

把目前記錄的列名和列值放到一個 Map 中

MapListHandler 把每條記錄封裝到一個 Map 中,再把 Map 封裝到 List 中
ScalarHandler 适合取單行單列資料
BeanHandler 取第一行資料
BeanListHandler 将每個資料封裝到 List 集合中
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import org.junit.Test;

public class ResultSetHandler {
     //ArrayHandler:适合取1條記錄。把該條記錄的每列值封裝到一個數組中Object[]
     @Test
     public void test1() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          Object[] arr = qr.query("select * from users where id =?",new ArrayHandler(),5);
          for (Object o : arr) {
              System.out.println(o);
          }
     }

     //ArrayListHandler:适合取多條記錄。把每條記錄的每列值封裝到一個數組中Object[],把數組封裝到一個List中
     @Test
     public void test2() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          List<Object[]> list = qr.query("select * from users",new ArrayListHandler());
          for (Object[] o : list) {
              for(Object os : o){
                   System.out.println(os);
              }
              System.out.println("---------");
          }
     }
     
     //ColumnListHandler:取某一列的資料,封裝到List中。
     @Test
     public void test3() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          //參數的列數是指select語句中列的數
          List<Object> list = qr.query("select * from users",new ColumnListHandler(3));
          for (Object o : list) {
              System.out.println(o);
          }
     }
     
     //KeyedHandler:取多條記錄,每一條記錄封裝到一個Map中,再把這個Map封裝到另外一個Map中,Key為指定的字段值。
     @Test
     public void test4() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          //參數指大Map的Key,是表中的某列資料,若不重複,則作為輸出的記錄數量。
          //小Map中的Key是列名
          Map<Object,Map<String,Object>> map = qr.query("select * from users",new KeyedHandler(1));
          for (Map.Entry<Object, Map<String, Object>> m : map.entrySet()) {
              System.out.println(m.getKey());
              for(Map.Entry<String, Object> mm : m.getValue().entrySet()){
                   System.out.println(mm.getKey()+":"+mm.getValue());
              }
              System.out.println("---------");
          }
     }
     
     //MapHandler:适合取1條記錄。把目前記錄的列名和列值放到一個Map中
     @Test
     public void test5() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          Map<String, Object> map = qr.query("select * from users where id=?",new MapHandler(),6);
          for (Map.Entry<String, Object> m : map.entrySet()) {
               System.out.println(m.getKey()+":"+m.getValue());
          }
          System.out.println("---------");
     }
     
     //MapListHandler:适合取多條記錄。把每條記錄封裝到一個Map中,再把Map封裝到List中
     @Test
     public void test6() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          List<Map<String, Object>> list = qr.query("select * from users",new MapListHandler());
          for(Map<String, Object> map : list){
              for (Map.Entry<String, Object> m : map.entrySet()) {
                   System.out.println(m.getKey()+":"+m.getValue());
              }
              System.out.println("---------");
          }
     }
     
     //ScalarHandler:适合取單行單列資料
     @Test
     public void test7() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          Object o = qr.query("select username from users",new ScalarHandler(1)); //username列第1個
          Object o2 = qr.query("select * from users",new ScalarHandler(2)); //第1行第2列
          Object o3 = qr.query("select count(*) from users",new ScalarHandler());
          System.out.println(o3);
          System.out.println(o.getClass().getName());//檢視傳回的Object變量是什麼類型
     }
     
     //BeanHandler:取第一行資料
     @Test
     public void test8() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
          User u = qr.query("select * from users",new BeanHandler<User>(User.class));
          System.out.println(u);
     }
     
     //BeanListHandler:将每個資料封裝到List集合中
     @Test
     public void test9() throws SQLException {
          QueryRunner qr = new QueryRunner(c3p0.getDataSource());
        List<User> list = qr.query("select * from users where id=?", new BeanListHandler<User>(User.class),2);
        //list若取不到值則傳回0,不會有空指針異常的問題
        for(User user:list){
            System.out.println(user);
        }
    }   

}
           

作用:調用該類的 get 方法,永遠傳回目前線程放入的資料(線程局部變量)。

// 模拟 ThreadLocal 的設計,明白其作用
public class ThreadLocal {

    private Map<Runnable, Object> container = new HashMap<Runnable, Object>();

    public void set(Object value){
        container.put(Thread.currentThread(), value);  // 用目前線程作為key
    }

    public Object get(){
        return container.get(Thread.currentThread());
    }

    public void remove(){
        container.remove(Thread.currentThread());
    }

}
           

案例:

  • ThreadLocal 工具類
import java.sql.Connection;
import java.sql.SQLException;

public class ManageThreadLocal {

     private static ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
     
     // 得到目前線程中的一個連接配接
     public static Connection getConnection(){
          Connection conn = t1.get();  // 從目前線程取出一個連接配接
          if(conn==null){
              conn = C3P0Util.getConnection();  // 從池中取出一個
              t1.set(conn);  // 把conn對象放入到目前線程對象中
          }
          return conn;
     }
     
     // 開始事務
     public static void startTransaction(){
          try {
              getConnection().setAutoCommit(false);  // 從目前線程對象中取出的連接配接,并開始事務
          } catch (SQLException e) {
              e.printStackTrace();
          }
     }
     
     // 送出事務
     public static void commit(){
          try {
              getConnection().commit();
          } catch (SQLException e) {
              e.printStackTrace();
          }
     }
     
     // 復原事務
     public static void rollback(){
          try {
              getConnection().rollback();
          } catch (SQLException e) {
              e.printStackTrace();
          }
     }
     
     public static void close(){
          try {
              getConnection().close();  // 把連接配接放回池中
              t1.remove();  // 把目前線程對象中的conn移除
          } catch (SQLException e) {
              e.printStackTrace();
          }
     }

}
           
  • AccountDaoImpl
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.dao.AccountDao;
import com.domain.Account;
import com.util.C3P0Util;
import com.util.ManagerThreadLocal;

public class AccountDaoImpl implements AccountDao {

     public void updateAccount(String fromname, String toname, double money) throws Exception {
         // 建立一個QueryRunner對象
         QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
         qr.update("update account set money=money-? where name=?",money,fromname);
         qr.update("update account set money=money+? where name=?",money,toname);

     }

     public void updateAccout(Account account) throws Exception {
         QueryRunner qr = new QueryRunner();

         return qr.update(ManagerThreadLocal.getConnection(),"update account set money=? where name=?",account.getMoney(),account.getName());

     }

     public Account findAccountByName(String name) throws Exception {
         QueryRunner qr = new QueryRunner();
         return qr.query(ManagerThreadLocal.getConnection(),"select * from account where name=?", new BeanHandler<Account>(Account.class),name);
     }

}
           
  • AccountServiceImpl
import java.sql.Connection;
import java.sql.SQLException;
import com.dao.AccountDao;
import com.dao.impl.AccountDaoImpl;
import com.domain.Account;
import com.service.AccountService;
import com.util.C3P0Util;
import com.util.ManagerThreadLocal;

public class AccountServiceImpl implements AccountService {
     public void transfer(String fromname, String toname, double money) {
     //   ad.updateAccount(fromname, toname, money);
          AccountDao ad = new AccountDaoImpl();
          
          try {
               ManagerThreadLocal.startTransacation();  // begin
              // 分别得到轉出和轉入賬戶對象
              Account fromAccount = ad.findAccountByName(fromname);
              Account toAccount = ad.findAccountByName(toname);
              
              // 修改賬戶各自的金額
               fromAccount.setMoney(fromAccount.getMoney()-money);
               toAccount.setMoney(toAccount.getMoney()+money);
              
              //完成轉賬操作
              ad.updateAccout(fromAccount);
//            int i = 10/0;
              ad.updateAccout(toAccount);
              
              ManagerThreadLocal.commit();  // 送出事務
          } catch (Exception e) {
              try {
                   ManagerThreadLocal.rollback();  // 復原事務
              } catch (Exception e1) {
                   e1.printStackTrace();
              }
          }finally{
              try {
                   ManagerThreadLocal.close();
              } catch (Exception e) {
                   e.printStackTrace();
              }  // 關閉
          }
     }

}