很久沒用原生連線池,最近想寫個小功能,結果發現很多地方不太懂,然後網上搜了半天的 c3p0 相關內容,全不符合我想要的。相同內容太多 而且沒什麼,所以我自己來總結下吧。
01 總結全文
從以下來總結
- 連線池的作用,為什麼要使用連線池
- 書寫自己的連線池,用於理解框架 c3p0 等連線池
- 連線池框架 c3p0 使用
- 連線池框架 c3p0 和 DBUtils 的配合使用
- 配合事務的使用(重點,這塊很多人都說不明白)
02 分析
0201 連線池的作用,為什麼要使用連線池
首先我們運算元據庫都需要連線,平時獲取連線、關閉連線如果頻繁,就會浪費資源,佔用CPU。所以這裡我們用一個池子來存放連線。 先自定義一個自己的連線池,這些內容太簡單,我直接上程式碼。相信大家很容易看懂。
public class CustomConnectionUtils {
private static LinkedList<Connection> pool = new LinkedList<Connection>();
/**
* 初始化連線池 新增3個連線到池子中去
*/
static {
try {
Class.forName("com.mysql.jdbc.Driver");
for(int i=0;i<3;i++){
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1", "root", "root");
pool.add(connection);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取連線
* @return
*/
public static Connection getConnection(){
try {
if(!pool.isEmpty()){
Connection connection = pool.removeFirst();
return connection;
}
//如果沒有連線 等待 100 毫秒 然後繼續
Thread.sleep(100);
return getConnection();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
/**
* 歸還連線 其實就是重新將連線新增到池子中去
* @param connection
*/
public static void release(Connection connection){
if(connection != null){
pool.add(connection);
}
}
}
複製程式碼
0202 c3p0連線池使用
免費的連線池有很多,如果不配合 spring 。單獨使用,我個人還是喜歡使用 c3p0。
第一步 新增依賴
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
複製程式碼
第二步 配置檔案
配置檔案命名只能 c3p0-config.xml
,位置放在 idea-->src-->resources-->c3p0-config.xml
然後就是它的配置,有兩種情況
- 預設不設定名字
- 設定名字 首先來看預設不設定名字,比較簡單,配置比較簡單。程式碼中使用也可以直接使用。
<!-- This is default config! -->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mylink2mv?characterEncoding=utf8</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property>
</default-config>
複製程式碼
還有種方式就是設定配置名字,如下 test1
<!-- This is my config for mysql-->
<named-config name="test1">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test1</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</named-config>
複製程式碼
剩下就是程式碼中的事情了。
第三步程式碼中初始化配置檔案
//載入預設配置檔案
//private static final ComboPooledDataSource DATA_SOURCE_DEFAULT =new ComboPooledDataSource();
//載入命名配置檔案
private static final ComboPooledDataSource DATA_SOURCE_TEST1 =new ComboPooledDataSource("test1");
複製程式碼
兩種載入方式,都比較簡單。稍微注意,就是這一行程式碼就已經讀取配置檔案了,不用做其他操作。這裡已經得到的就是連線池了。這裡我們寫個方法來獲取,顯得專業點,不然直接通過類名獲取也沒事。一般大部分舉例都是採用讀取預設名字的配置,這裡我讀取自定義的,將上面的程式碼註釋掉。
第四步 獲取連線
這裡我寫個簡單的從連線池獲取連線的方法。
public static ComboPooledDataSource getDataSource(){
return pooledDataSource;
}
public static Connection getCoonection(){
try {
Connection connection = getDataSource().getConnection();
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
複製程式碼
第五步 測試
這裡因為還沒開始用 DBUtils ,所以都是原生的 jdbc 方法。寫了個簡單的查詢語句演示。
public static void main(String[] args) {
try {
Connection connection = C3P0Utils.getCoonection();
PreparedStatement preparedStatement = null;
ResultSet rs = null;
String sql = "select * from my_movie;";
preparedStatement = connection.prepareStatement(sql);
rs = preparedStatement.executeQuery();
while(rs.next()){
String mName = rs.getString("mName");
System.out.println(mName);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
複製程式碼
0203 DBUtils
現在開始使用 DBUtils ,注意它並不是連線池 ,最開始學的時候兩者總是分不清,它是結合連線池 C3P0 使用的工具類。如上面程式碼,發現沒,就算不使用 DBUtils 也是可以的,只不過運算元據庫那一塊,增刪改查太過於複雜,特別對於「查」來說。對於返回的資料,處理很原生。但是有了 DBUtils 就不一樣了。它有三個核心功能,剛好用於解決專案實踐中很容易碰到的問題。
- QueryRunner 中提供對 sql 語句操作的 api
- ResultSetHandler 介面,用於定義 select 操作後,怎樣封裝結果集
- 定義了關閉資源與事務處理的方法
如上所說,這三個核心功能都是解決「痛點」問題的。
**QueryRunner **
一個個來看。首先 QueryRunner 類的構造方法有好三個,但是常用的可以分成兩類
- 帶 connection 的
- 不帶 connection
首先來看不帶 connection 的,也就是說直接傳入連線池的。DBUtils 底層自動維護連線 connection 。
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
複製程式碼
這種是大家用的最多的,還有種就是預設的不帶引數。
QueryRunner queryRunner = new QueryRunner();
複製程式碼
這兩種有什麼區別呢? 為什麼涉及 connection
呢? 主要還是事務!
這裡先將結果丟出來,如果涉及事務,不能使用第一種構造。
再來看 QueryRunner 的方法 api 。兩個運算元據庫的 api 。
- update(String sql, Object ... params) ,用於執行 增刪改 sql 語句
- query(String sql , ResutlSetHandler rsh , Object ... params) 執行 查詢 sql 語句。
**ResultSetHandler **
第二部分中的查詢語句中可以看出,對於查詢語句要解析到實體類,特別麻煩,特別結果集是集合時,更加麻煩。但是實踐中查詢用的最多。所以 DBUtils 有專門的類來處理結果集。非常多,但是常用三個。
- 結果集是單 bean ----> BeanHanler
- 結果集是集合 ---- BeanListHandler
- 結果集是個單資料。(我一般專用來獲取 主鍵 id) ---- ScalarHandler
增刪改操作都比較簡單,比較特殊一點就是 **獲取插入成功後的主鍵值 **,這點後再說,先將簡單的寫完。
/**
* 新增
*/
public void A() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
Object params[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
int i = queryRunner.update(sql,params);
System.out.println(i);
}
複製程式碼
/**
* 刪除
*/
public void B() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "delete from my_movie where mId = ?";
Object params[] = {7};
int i = queryRunner.update(sql,params);
System.out.println(i);
}
複製程式碼
/**
* 改
*/
public void C() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "update my_movie set mName = ? where mId = ?";
Object params[] = {"綠箭俠",1};
int i = queryRunner.update(sql,params);
System.out.println(i);
}
複製程式碼
上面三個方法我都試過,執行是沒問題的。接下來就是 獲取插入成功後的主鍵值。因為我碰到過這個需求,結果搜尋 c3p0 dbutils 插入成功主鍵值
找到的相關文章都沒解決我的問題。查了很久 踩了很多坑,這裡總結下。
/**
* 獲取插入成功後的主鍵值
*/
public void D() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
Object params[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
//這裡不能使用 update 而是 insert 使用如下引數
Long i = (Long) queryRunner.insert(sql,new ScalarHandler(),params);
System.out.println(i);
}
複製程式碼
接下來是查詢
@Test
/**
* 查詢 獲取單個結果集
*/
public void E() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "select * from my_movie where mId = ? ";
Object params[] = {1};
Movie movie = queryRunner.query(sql, new BeanHandler<Movie>(Movie.class), params);
System.out.println(movie);
}
@Test
/**
* 查詢 獲取單個結果集
*/
public void F() throws SQLException {
QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
String sql = "select * from my_movie ";
Object params[] = {};
List<Movie> movieList = queryRunner.query(sql, new BeanListHandler<Movie>(Movie.class), params);
System.out.println(movieList.toString());
}
複製程式碼
0204 配合事務
到這裡為止,一切都很美好,沒啥問題。但是如果涉及 事務 呢。
如果一個方法中不止一個 sql 操作,而都在一個事務中。採用如上辦法是不行的。因為這些 sql 語句的操作前提需要保證使用的是同一個連線
但是使用 QueryRunner
如上的構造方法是不行的。每一個操作queryRunner.query
可能都使用的不是同一個連線。所以我們的做法是自己獲取連線,然後作為引數傳入。
/**
* 事務
*/
@Test
public void G() {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = C3P0Utils.getCoonection();
//開啟事務
connection.setAutoCommit(false);
//所有的操作都帶上引數 connection
String sql01 = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
Object params01[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
int i = queryRunner.update(connection,sql01,params01);
String sql02 = "select * from my_movie ";
Object params02[] = {};
List<Movie> movieList = queryRunner.query(connection,sql02, new BeanListHandler<Movie>(Movie.class), params02);
System.out.println(movieList);
} catch (SQLException e) {
//如果報錯 回滾
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
try {
//不管成功 失敗 都提交
connection.commit();
//關閉連線
DbUtils.closeQuietly(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
複製程式碼
上面程式碼我只是舉例了插入和查詢。其他都一樣,帶了 connection 引數即可。
03 尾聲
基本 c3p0 dbutils 事務 所有問題都搞清楚了。以後可能也用不著這些框架,但是希望能幫到一些用到了朋友吧。