一、類庫介紹
首先簡單介紹一下以上三種類庫的區別與聯繫:
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;
配置類代碼
配置類代碼分爲 pom
和 druid
。
首先來看一下 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 操作數據庫的簡單模板就完成了。