JavaWeb學習筆記6:事務&數據庫連接池&DBUtiles

1-事務介紹與簡單使用

事務 - Transaction
其實指的一組操作,裏面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麼都算失敗,則所有的數據都回歸到最初的狀態(回滾)
爲什麼要有事務?
爲了確保邏輯的成功 例子:銀行的轉賬
開啓事務命令:
start transaction;
提交或者回滾事務命令:
commit; 提交事務,數據將會寫到磁盤上的數據庫
rollback; 數據回滾,回到最初的狀態
自動提交命令:
set autocommit = off;
autocommit會降低運行效率並且容易發生數據錯誤,若異常則後續代碼無法執行。關閉autocommit後,若要使事務執行則commit,否則就回滾rollback
設置隔離級別命令:
set session transaction isolation level (級別);

事務在Java中的簡單使用:

public void testTransaction(){
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		conn = JDBCUtil.getConn();
		//連接,事務默認就是自動提交的。 關閉自動提交。
		conn.setAutoCommit(false);
		String sql = "update account set money = money - ? where id = ?";
		ps = conn.prepareStatement(sql);
		//扣錢, 扣ID爲1 的100塊錢
		ps.setInt(1, 100);
		ps.setInt(2, 1);
		ps.executeUpdate();
		int a = 10 /0 ;
		//加錢, 給ID爲2 加100塊錢
		ps.setInt(1, -100);
		ps.setInt(2, 2);
		ps.executeUpdate();
		//成功: 提交事務。
		conn.commit();
	} catch (SQLException e) {
		try {
			//事變: 回滾事務
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
		e.printStackTrace();
	}finally {
		JDBCUtil.release(conn, ps, rs);
	}
}

注意:若不使用事務(autocommit開着),則執行事務邏輯時某處出現的異常可能導致整個數據出錯

2-事務 特性(ACID)和安全隱患
  • 原子性
    > 指的是 事務中包含的邏輯,不可分割
  • 一致性
    > 指的是 事務執行前後.數據完整性
  • 隔離性
    > 指的是 事務在執行期間不應該受到其他事務的影響
  • 持久性
    > 指的是 事務執行成功,那麼數據應該持久保存到磁盤上

不考慮隔離級別設置,那麼會出現以下問題

  • 髒讀
    > 一個事務讀到另外一個事務還未提交的數據
    髒讀/讀未提交

  • 不可重複讀
    > 一個事務讀到了另外一個事務提交的數據,造成了前後兩次查詢結果不一致

  • 幻讀
    > 一個事務讀到了另一個事務已提交的插入的數據,導致多次查詢結果不一致

事務的可串行化:

如果有一個連接的隔離級別設置爲了串行化,那麼誰先打開了事務,誰就有了先執行的權利,誰後打開事務,誰就只能得着,等前面的那個事務,提交或者回滾後,才能執行

  • 按效率劃分,從高到低
    > 讀未提交 > 讀已提交 > 可重複讀 > 可串行化
  • 按攔截程度,從高到底
    > 可串行化 > 可重複讀 > 讀已提交 > 讀未提交

事務的丟失更新現象:
丟失更新
悲觀鎖和樂觀鎖:
1、可以在查詢的時候加入for update,表示悲觀鎖
悲觀鎖
2、樂觀鎖要求程序員自己規定標識
樂觀鎖

事務小結:
1、需要掌握在Java中使用事務

conn.setAutoCommit(false);
conn.commit();
conn.rollback();

2、需要熟悉瞭解事務的安全隱患和隔離級別
3、mySql默認的隔離級別是可重複讀。Oracle是讀已提交
4、需要會在數據出現異常時學會排錯

3-數據庫連接池介紹與簡單使用

前提引入:如果每個程序連接數據庫都要創建一個數據庫對象,那麼會比較消耗性能。所以我們一開始直接在內存中使用一塊集合空間存放多個連接對象,如果有程序需要連接對象則給一個,使用完畢之後回收,這樣的話不僅能夠循環利用而且還能夠提升效率。

數據庫連接池的搭建分析:
1、先創建X個連接
2、要用的程序通過getConnection來獲取連接
3、用完之後,使用addBack來歸還連接
4、如果連接池中不夠了,則擴容
5、list.remove(0)移出list中的第一個,移出後第二個默認到第一個,還是remove(0)

數據庫連接池的簡單搭建:
這裏暫時創建一個(JDBCUtil)對象類,用於聲明連接對象。接着自己手動創建一個連接池對象,具體的主要代碼如下↓

List<Connection> connList = new ArrayList<Connection>();
public List<Connection> getConnList() {
	return connList;
}
public void setConnList(List<Connection> connList) {
	this.connList = connList;
}
public MyDataSource() {
	for (int i = 0; i < 10; i++) {
		Connection conn = JDBCUtil_old.getConn();
		connList.add(conn);
	}
}
//目前使用到的方法只有getConnection
@Override
public Connection getConnection() throws SQLException {
	if(connList.size()==0){
		for (int i = 0; i < 5; i++) {//一次加5個
			Connection conn = JDBCUtil_old.getConn();
			connList.add(conn);
		}
	}
	Connection conn = new ConnectionWrap(connList.remove(0),this.connList);//刪除並返回Connection
	System.out.println("分配一個Connection之後的list大小:"+connList.size());
	return conn;
}
//使用完畢之後,利用addBack方法,回收連接
public void addBack(Connection conn){
	connList.add(conn);
	System.out.println("還原一個Connection之後的list大小:"+connList.size());
}

自定義數據庫連接池的問題:
1、需要額外記住addBack方法
2、單例模式的使用,創建多個數據庫連接池,佔用內存高
3、由於addBack方法接口中沒有,無法面向接口編程
解決自定義數據庫連接池問題的重點在於把addBack換成接口已有的方法:
1、使用裝飾者模式
2、使用動態代理

4-數據庫連接池 裝飾者模式

裝飾者模式改進自定義數據庫連接池主要代碼:

Connection conn = null;
List<Connection> connList = null;
public ConnectionWrap(Connection conn,List<Connection> connList) {
	this.conn = conn;
	this.connList = connList;
}
@Override
public void close() throws SQLException {
	connList.add(conn);
	System.out.println("還原一個Connection之後的list大小:"+connList.size());
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
	return conn.prepareStatement(sql);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
	conn.setAutoCommit(autoCommit);
}
@Override
public void commit() throws SQLException {
	conn.commit();
}
@Override
public void rollback() throws SQLException {
	conn.rollback();
}
5-數據庫連接池DBCP

DBCP連接池的apache上的一個Java連接池項目。
1、使用直連的方式獲取連接:

//1. 構建數據源對象
BasicDataSource dataSource = new BasicDataSource();
//連的是什麼類型的數據庫, 訪問的是哪個數據庫 , 用戶名, 密碼。。
//jdbc:mysql://localhost/bank 主協議:子協議 ://本地/數據庫
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/bank");
dataSource.setUsername("root");
dataSource.setPassword("root");
//2. 得到連接對象
conn = dataSource.getConnection();

2、通過配置文件、工廠獲取連接:
首先導入兩個jar包(一個pool,一個dbcp),一個properties文件
其次建立工廠,通過工廠來獲取連接,代碼如下:

BasicDataSourceFactory bdsf = new BasicDataSourceFactory();
//bdsf不能生成自定義的DataSource
//MyDataSource ds = null;
DataSource ds = null;
Connection conn = null;
InputStream in = null;
Properties pro = null;
try {
	pro = new Properties();
	in = new FileInputStream("src/dbcpconfig.properties");
	pro.load(in);
	ds = bdsf.createDataSource(pro);
	conn = ds.getConnection();
}
6-數據庫連接池C3P0

C3P0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。
1、使用直連的方式獲取連接:

//1. 創建datasource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2. 設置連接數據的信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");

//忘記了---> 去以前的代碼 ---> jdbc的文檔
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
dataSource.setUser("root");
dataSource.setPassword("root");
//3. 得到連接對象
conn = dataSource.getConnection();

2、通過配置文件獲取連接:

ComboPooledDataSource ds = new ComboPooledDataSource();	//後有參數可以選擇數據庫類型
Connection conn = null;
conn = ds.getConnection();

//配置文件
<default-config>
	<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
	<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC</property>
	<property name="user">root</property>
	<property name="password">qq</property>
	<property name="automaticTestTable">con_test</property>
	<property name="checkoutTimeout">30000</property>
	<property name="idleConnectionTestPeriod">30</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>
</default-config>

數據庫連接池總結:
1、配置文件中的url如果就寫到目標數據庫名則會報錯,原因是沒有規定使用的解碼器
2、配置文件中寫了解碼器的代碼但沒有使用轉義字符,會報錯"characterEncoding" must end with the ‘;’ delimiter;原因是xml不能解析’&’,所以把‘&’改成"&amp ;" 即可
3、Tried to check-in a foreign resource! - 線程無法正常運行,不要手動關閉ds
4、ComboPooledDataSource源代碼中自動會幫助獲取連接信息,所以要寫好配置文件

7-DBUtils以及Handler

利用DBUtils進行增刪改查操作,這裏主要利用到QueryRunner對象

//dbutils 只是幫我們簡化了CRUD的代碼,但是連接的創建以及獲取工作不在他的考慮範圍
對象的生成:QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
//增加
queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
//刪除
queryRunner.update("delete from account where id = ?", 5);
//更新
queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);

這裏要單獨把查找功能寫出來,因爲查找的時候有利用到一個Handler集合對象
1、我們直接new接口的匿名實現類(ResultSetHandler)

QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
Account  account =  queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){
	@Override
	public Account handle(ResultSet rs) throws SQLException {
		Account account  =  new Account();
		while(rs.next()){
			String name = rs.getString("name");
			int money = rs.getInt("money");
			
			account.setName(name);
			account.setMoney(money);
		}
		return account;
	}
 }, 6);
System.out.println(account.toString());
//ResultSetHandler爲最大的接口,所以要使用必須自行定義接口方法,所以@Override

2、直接使用已經寫好的實現類(BeanListHandler)

ComboPooledDataSource ds = new ComboPooledDataSource();	//後有參數可以選擇數據庫類型
QueryRunner eq = new QueryRunner(ds);
try {
	//查找
	List<Account> accounts = eq.query("select * from account", new BeanListHandler<Account>(Account.class));
	for (Account account : accounts) {
		String name = account.getName();
		int money = account.getMoney();
		System.out.println("Account:"+name+"-"+money);
	}
} catch (SQLException e) {
	e.printStackTrace();
}

DBUtils與Handler總結:

ResultSetHandler 常用的實現類
以下是使用頻率從高往低排序結果:
------------------------------------------✈
BeanHandler, 查詢到的單個數據封裝成一個對象
BeanListHandler, 查詢到的多個數據封裝 成一個List<對象>
------------------------------------------🚑
ArrayHandler, 查詢到的單個數據封裝成一個數組
ArrayListHandler, 查詢到的多個數據封裝成一個集合 ,集合裏面的元素是數組。
------------------------------------------⛵
MapHandler, 查詢到的單個數據封裝成一個map
MapListHandler,查詢到的多個數據封裝成一個集合 ,集合裏面的元素是map。
------------------------------------------🚲
ColumnListHandler
KeyedHandler
ScalarHandler

HPF-自我總結

  這部分的重點就是要學會使用事務,並且會解決關於數據庫連接出現的問題。
  其次,我個人用C3P0用得多,學到目前階段感覺C3P0是真的很方便,要好好掌握。
  最後,熟練使用BeanListHandler,這個框架很好用
  ——— 給自己一個恰如其分的自信。

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