apache-commons-dbutils + Druid + JDBC 簡單實現 CRUD

一、類庫介紹

首先簡單介紹一下以上三種類庫的區別與聯繫:

JDBC

JDBC 是 sun 公司定義的一套使用 java 連接數據庫的規範,是一套接口加部分實現類,他規定了各大數據庫廠商要想使用 java 語言操作他們的數據庫就必須實現這些接口,可以說 JDBC 是一套規範。比如 MySQL 就實現了這一套接口,在com.mysql.jdbc包下。

DBUtils

Apache-commons 是 Apache 的一個工具類庫,相信大家都聽說過這些工具類,非常實用,我們使用的Apache-commons-dbutils就是其中一個類庫,它對 JDBC 進行了簡單的封裝(其實也不是很簡單的封裝,只是相對於框架來說是簡單封裝),簡化了 JDBC 操作。當然我們也可以不使用他提供的類庫而自己實現,但是這是非常麻煩的,而且沒有必要浪費時間在這上面,就好像你着急上班,別人有汽車你不坐,說跑步可以鍛鍊身體,所以走過去。但是這太浪費時間了,所以我們要學會開車,然後研究他的原理,這樣我們也會進步。

Druid

Druid 是阿里巴巴研發的一套數據庫連接池技術,這個類庫並不是必須使用的,我們在這裏使用的目的是因爲它可以增強性能,因爲我們如果使用原生的 JDBC,不使用數據庫連接池,那麼每進行一次數據庫查詢操作就會建立一條連接,而數據庫連接池是首先創建很多連接,當你需要用的時候就拿走,用完了之後歸還,這樣可以提高資源的利用率。常用的數據庫連接池有 C3P0 和 Druid,我選擇 Druid的原因是因爲這是 web 項目模板,而且 Druid 提供強大的數據庫監控技術和統計技術。詳情可以看這篇文章:JavaWeb 使用 Druid 連接池查詢數據庫

二、功能分析

我們主要實現的功能有以下幾個:

  • 首先建立數據庫連接池類,用於和 Druid 交互;
  • 然後建立連接數據庫操作的 BaseDao 基類,用於和 DBUtils 交互;
  • 由於 DBUtils 已經實現了和 JDBC 交互,所以我們只需要封裝以上兩層即可簡化 JDBC 操作;
  • 爲了簡化業務層操作,我們繼續封裝 DBUtils ,實現 CRUD 接口,這樣業務層只需要調用接口即可實現與數據庫交互。

三、代碼實現

這裏是一個 JavaWeb 項目
在這裏插入圖片描述

建表

本項目使用的數據庫爲 school,如果想和我一起操作請建表,並注意編碼設置爲 UTF-8

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(100) NOT NULL AUTO_INCREMENT,
  `username` varchar(25) DEFAULT NULL,
  `password` varchar(25) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

配置類代碼

配置類代碼分爲 pomdruid

首先來看一下 pom文件:

<dependencies>
        <!--Junit 單元測試-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!--servlet api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!--mysql驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
            <scope>compile</scope>
        </dependency>

        <!--Druid 數據庫連接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.22</version>
        </dependency>

        <!--apache dbUtils : 簡化 JDBC 操作-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>

        <!--lombok : 自動生成 get set 方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.8</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>jdbc-demo</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <!--maven插件-->
        <plugins>
            <!--jdk編譯插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!--tomcat插件-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <!-- tomcat7的插件, 不同tomcat版本這個也不一樣 -->
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <!-- 通過maven tomcat7:run運行項目時,訪問項目的端口號 -->
                    <port>8080</port>
                    <!-- 項目訪問路徑  本例:localhost:9090,  如果配置的aa, 則訪問路徑爲localhost:9090/aa-->
                    <path>/</path>
                </configuration>
            </plugin>

        </plugins>
    </build>

然後是druid 配置文件:

driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql:///school?characterEncoding=utf-8
username = root
password = root
initialSize=5
maxActive=10
maxWait=3000

封裝 Druid

我們使用 DruidUtils 類對 Druid 進行簡單封裝。

package top.wsuo.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

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

/**
 * Druid數據庫連接池工具類
 *
 * @Author shuo wang
 * @Date 2020/4/29 0029 14:46
 * @Version 1.0
 */
public class DruidUtils {

    // 數據源
    private static DataSource dataSource;

    // 註冊驅動,使用靜態代碼塊,類一旦加載就會執行
    static {
        try {
            // 獲取類對象,讀取配置文件
            InputStream resource = DruidUtils
                    .class
                    .getClassLoader()
                    .getResourceAsStream("druid.properties");
            Properties properties = new Properties();
            properties.load(resource);
            dataSource = DruidDataSourceFactory
                    .createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnect() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取連接池對象供commons使用
     *
     * @return 返回Druid連接池對象
     */
    public static DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 釋放2個資源
     *
     * @param conn      連接對象
     * @param statement statement對象
     */
    public static void close(Connection conn,
                             PreparedStatement statement) {
        assert conn != null;
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }

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

    /**
     * 釋放3個資源
     *
     * @param conn      連接對象
     * @param statement statement對象
     * @param resultSet 返回結果集
     */
    public static void close(Connection conn,
                             PreparedStatement statement,
                             ResultSet resultSet) {

        close(conn, statement);
        assert resultSet != null;
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

封裝 DBUtils

我們使用 BaseDao 對 DBUtils 進行簡單封裝。

  • 這裏爲什麼要定義成一個抽象類呢 ?
    • 對於一個父類,如果它的某個方法在父類中實現出來沒有任何意義
    • 必須根據子類的實際需求來進行不同的實現,就要定義爲抽象類
    • 像我們的 BaseDao ,我們需要它根據自己的業務需求靈活的變化
    • 比如有時候需要查詢 User 類,有時候需要 Student 類。

可以看到這個抽象類沒有抽象方法:

  • 因爲我們知道抽象類是不能創建實例的,所以我們定義爲抽象類就隱含的限制了其它用戶的行爲,即:你不可以直接使用此類,必須使用其它給你提供好的實現類。該類的靈感來自於org.apache.commons.dbutils.AbstractQueryRunner類,他也是沒有一個抽象方法的抽象類,但是他有兩個子類,這就限制了我們必須使用其子類完成操作。

另外,這裏考慮到事務的操作,所以我在這裏定義了一個 updateCommit 方法,它使用獨立的 Connect 對象,而 DBUtils 使用默認的 Connect 對象,他默認每一條 SQL 語句就是一次事務,但是我們有時候業務需求比如銀行轉賬,肯定是要多條 SQL 語句合成一次事務。這樣定義一個專門的 updateCommit 方法之後當需要事務的時候就直接調用即可,然後調用 commit 方法完成一次事務操作。

這裏使用了反射的思想來獲取子類的類型。

package top.wsuo.dao;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import top.wsuo.util.DruidUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * 定義一個用來被繼承的對數據庫進行基本操作的 Dao
 * 這裏爲什麼要定義成一個抽象類呢 ?
 * - 對於一個父類,如果它的某個方法在父類中實現出來沒有任何意義,
 * - 必須根據子類的實際需求來進行不同的實現,就要定義爲抽象類
 * - 像我們的BaseDao,我們需要它根據自己的業務需求靈活的變化
 * - 比如有時候需要查詢User類,有時候需要Student類
 *
 * @param <T>
 */
public abstract class BaseDao<T> {

    /*
     * 注意這裏可以使用 QueryRunner 的兩個構造方法來獲取該對象
     *  - 一個是無參構造: 默認自己管理事務,因爲框架沒有連接池無法獲得數據庫連接
     *  - 另外一個是有參構造: 傳入一個連接池對象,
     *    數據庫事務交給DBUtils框架進行管理 ---- 默認情況下每條SQL語句單獨一個事務。
     *
     * */

    // 使用 Druid 的連接池
    private QueryRunner queryRunner =
            new QueryRunner(DruidUtils.getDataSource());

    // 定義一個變量來接收泛型的類型
    private Class<T> type;

    // 獲取T的Class對象,獲取泛型的類型,泛型是在被子類繼承時才確定
    public BaseDao() {

        // 獲取子類的類型
        Class clazz = this.getClass();

        // 獲取父類的類型,ParameterizedType表示的是帶泛型的類型,
        // getGenericSuperclass()用來獲取當前類的父類的類型
        ParameterizedType parameterizedType =
                (ParameterizedType) clazz.getGenericSuperclass();

        // 獲取具體的泛型類型 getActualTypeArguments獲取具體的泛型的類型,
        // 這個方法會返回一個Type的數組
        Type[] types =
                parameterizedType.getActualTypeArguments();

        // 獲取具體的泛型的類型
        // noinspection unchecked
        this.type = (Class<T>) types[0];
    }

    /**
     * 手動提交事務
     * 通用的-增刪改-操作, 但是一般只用於修改
     * 這裏使用的是用戶自定義的 Connection 對象,這樣的話用戶可以自己控制事務
     * 其他的查詢方法不提供 Connection 對象,因爲查詢不涉及事務的操作.
     */
    public int updateCommit(Connection conn, String sql, Object... params) {
        int count = 0;
        try {
            count = queryRunner.update(conn, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }


    /**
     * 數據的增刪改
     * 默認一個SQL語句爲一個事務,數據庫事務交給DBUtils框架進行管理
     *
     * @param sql    SQL語句
     * @param params 執行參數
     * @return 返回受影響的行數
     */
    public int update(String sql, Object... params) {
        int count = 0;
        try {
            count = queryRunner.update(sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 自動提交事務的查詢方法
     *
     * @param sql    SQL 語句
     * @param params 查詢參數
     * @return 返回泛型
     */
    public T queryBean(String sql, Object... params) {
        T t = null;
        try {
            t = queryRunner
                    .query(sql, new BeanHandler<>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return t;
    }

    /**
     * 自動提交事務的查詢所有方法
     *
     * @param sql    SQL 語句
     * @param params 查詢參數
     * @return 返回泛型集合
     */
    public List<T> queryBeanList(String sql, Object... params) {
        List<T> list = null;
        try {
            list = queryRunner
                    .query(sql, new BeanListHandler<>(type), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 自動提交事務的值查詢
     *
     * @param sql    SQL 語句
     * @param params 參數
     * @return 返回數值如 count(*) sum(total) ...
     */
    public Object queryValue(String sql, Object... params) {
        Object count = null;
        try {
            count = queryRunner
                    .query(sql, new ScalarHandler<>(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 處理事務提交與回滾
     *
     * @param connection 連接的對象
     */
    public void commit(Connection connection) {
        try {
            DbUtils.commitAndClose(connection);
        } catch (SQLException e) {
            System.out.println("事務提交失敗!");
            DbUtils.rollbackAndCloseQuietly(connection);
            e.printStackTrace();
        }
    }
}

封裝 Dao

首先定義一個接口UserDao

使用了 Page類,所以我們先介紹 Page 類:

package top.wsuo.pojo;

import java.util.List;

/**
 * 分頁類
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:07
 * @Version 1.0
 */
public class Page<T> {
    private static final int PAGE_CURR = 1; // 當前頁碼
    private static final int PAGE_SIZE = 5; // 每頁的數量

    private List<T> list;   // 實體類
    private Integer current;    // 當前頁碼
    private Integer size;   // 每頁的條數
    private int totalRecord;    // 總記錄數

    public Page(Integer current, Integer size) {
        this.current = current;
        this.size = size;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public Integer getCurrent() {
        return this.current;
    }

    public void setCurrent(Integer current) {
        this.current = current;
    }

    public Integer getSize() {
        return this.size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }

    public int getTotalRecord() {
        return totalRecord;
    }

    public void setTotalRecord(int totalRecord) {
        this.totalRecord = totalRecord;
    }

    @Override
    public String toString() {
        return "Page{\n" +
                "\tlist=" + list +
                ",\n \t當前頁碼=" + current +
                ",\n \t每頁的數量=" + size +
                ",\n \t總記錄數=" + totalRecord +
                "\n}";
    }
}

Page 類很簡單,只定義了幾個分頁必須的屬性。

package top.wsuo.dao;

import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;

import java.util.List;

/**
 * 用戶接口類
 *
 * @author shuo wang
 * @version 1.0
 * @date 2020/4/30 0030 20:27
 */
public interface UserDao {
    /**
     * 根據User對象中的用戶名和密碼從數據庫中獲取一條記錄
     */
    User queryUser(User user);

    /**
     * 根據User對象中的條件從數據庫中獲取多條記錄
     */
    List<User> queryAll();

    /**
     * 分頁從數據庫中獲取多條記錄
     */
    Page<User> queryPageList(Page<User> page);

    /**
     * 根據User對象中的用戶名從數據庫中獲取一條記錄
     */
    boolean checkUsername(User user);

    /**
     * 向數據庫中插入User對象
     */
    int saveUser(User user);

    /**
     * 向數據庫中修改User對象
     */
    int updateUser(User user);

    /**
     * 向數據庫中刪除User對象
     */
    int deleteUser(int id);
}

再定義其實現類。

該類中定義 SQL 語句,如果我們又業務上的改變,可以直接修改此類而不用關心其餘底層的 JDBC 操作,所以這是不使用框架比較好的實現,而如果使用反射就和框架更像了。

這裏使用 limit 進行分頁查詢,在頁數和頁碼處進行了相關操作。

package top.wsuo.dao.impl;

import top.wsuo.dao.BaseDao;
import top.wsuo.dao.UserDao;
import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;
import top.wsuo.util.DruidUtils;

import java.sql.Connection;
import java.util.List;

/**
 * 用戶操作的DAO實現類
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:02
 * @Version 1.0
 */
public class UserDaoImpl extends BaseDao<User> implements UserDao {

    /**
     * 根據姓名和密碼查詢用戶,登陸時用
     *
     * @param user 用戶
     * @return 返回用戶
     */
    @Override
    public User queryUser(User user) {
        String sql = "select * from user where username = ? and password = ?";
        return queryBean(sql, user.getUsername(), user.getPassword());
    }

    /**
     * 查詢所有
     *
     * @return 返回集合
     */
    @Override
    public List<User> queryAll() {
        String sql = "select * from user";
        return queryBeanList(sql);
    }

    /**
     * 分頁查詢
     *
     * @param page 分頁
     * @return 返回分頁對象
     */
    @Override
    public Page<User> queryPageList(Page<User> page) {
        //  where username like concat('%', ? '%')
        String sql2 = "select * from user limit ? offset ?";
        int size = page.getSize();
        int curr = page.getCurrent();
        List<User> userList = queryBeanList(sql2, size, (curr - 1) * size);
        page.setTotalRecord(queryAll().size());
        page.setList(userList);
        return page;
    }

    /**
     * 檢查用戶名是否存在
     *
     * @param user 用戶對象
     * @return 返回布爾值
     */
    @Override
    public boolean checkUsername(User user) {
        return queryUser(user) != null;
    }

    /**
     * 保存用戶到數據庫
     *
     * @param user 用戶
     * @return 返回受影響的行數
     */
    @Override
    public int saveUser(User user) {
        String sql = "insert into user(username, password, address, phone) " +
                "values(?,?,?,?);";
        return update(sql, user.getUsername(), user.getPassword(),
                user.getAddress(), user.getPhone());
    }


    /**
     * 處理事務的修改方法
     *
     * @param user 實體類
     * @return 受影響的行數
     */
    @Override
    public int updateUser(User user) {
        Connection connection = DruidUtils.getConnect();
        int i = updateCommit(connection, "update user set username = ?, password = ?, " +
                        "address = ?, phone = ?", user.getUsername(),
                user.getPassword(), user.getAddress(), user.getPhone());
//        updateCommit(connection, "語句二", user);
        commit(connection); // 提交事務
        return i;
    }

    /**
     * 根據id刪除
     *
     * @return 返回受影響的行數
     */
    @Override
    public int deleteUser(int id) {
        String sql = "delete from user where id = ?";
        return update(sql, id);
    }
}

至此,所有持久層代碼就都寫完了。

測試

我們使用 Junit 進行測試:

package top.wsuo.dao;

import org.junit.Test;
import top.wsuo.dao.impl.UserDaoImpl;
import top.wsuo.pojo.Page;
import top.wsuo.pojo.User;

/**
 * 測試類
 *
 * @Author shuo wang
 * @Date 2020/4/30 0030 21:53
 * @Version 1.0
 */
public class DaoTest {

    private UserDaoImpl dao = new UserDaoImpl();

    /**
     * 保存數據測試方法
     */
    @Test
    public void saveTest() {
        User user = new User("test1",
                "123",
                "東北",
                "123456789");
        int i = dao.saveUser(user);
        System.out.println("插入成功: " + i);
    }

    /**
     * 查詢用戶
     */
    @Test
    public void queryUser() {
        User user = new User("test2",
                "123",
                "東北",
                "123456789");
        User user1 = dao.queryUser(user);
        System.out.println(user1);
    }

    /**
     * 分頁查詢測試方法
     */
    @Test
    public void pageTest() {
        Page<User> page = new Page<>(2,3);
        Page<User> pageList = dao.queryPageList(page);
        System.out.println(pageList);
    }

    /**
     * 修改測試方法
     */
    @Test
    public void updateTest() {
        User user = new User(
                "test6",
                "456",
                "吉林",
                "34234234"
        );
    }

    /**
     * 刪除測試方法
     */
    @Test
    public void deleteTest() {
        int i = dao.deleteUser(1);
        System.out.println("刪除成功: " + i);
    }
}

我們致力僅展示一下分頁查詢的查詢結果。
在這裏插入圖片描述
查詢結果:

Page{
	list=[User(id=5, username=test5, password=123, address=東北, phone=123456789), User(id=6, username=test6, password=123, address=東北, phone=123456789), User(id=7, username=test1, password=123, address=東北, phone=123456789)],
 	當前頁碼=2,
 	每頁的數量=3,
 	總記錄數=7
}

再去看數據庫,查詢正確:
在這裏插入圖片描述
至此一個使用 JDBC 操作數據庫的簡單模板就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章