1. 程式人生 > >Java Web(五) -- 事務 & 數據庫連接池 & DBUtiles

Java Web(五) -- 事務 & 數據庫連接池 & DBUtiles

ML run count() lis into 銀行 定義數據 dao util

#事務&數據庫連接池&DBUtils

##事務

> Transaction  其實指的一組操作,裏面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麽都算失敗。 所有的數據都回歸到最初的狀態(回滾)

* 為什麽要有事務?

> 為了確保邏輯的成功。 例子: 銀行的轉賬。 

###使用命令行方式演示事務。

* 開啟事務

	start transaction;

* 提交或者回滾事務

	commit; 提交事務, 數據將會寫到磁盤上的數據庫
	rollback ;  數據回滾,回到最初的狀態。

1. 關閉自動提交功能。

![icon](img/img01.png)

2. 演示事務

![icon](img/img02.png)


###使用代碼方式演示事務
> 代碼裏面的事務,主要是針對連接來的。 
>
>
>	1.  通過conn.setAutoCommit(false )來關閉自動提交的設置。

>	2. 提交事務  conn.commit();

>	3. 回滾事務 conn.rollback();

	@Test
	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);
		}
	}

###事務的特性

* 原子性

> 指的是 事務中包含的邏輯,不可分割。 

* 一致性

> 指的是 事務執行前後。數據完整性

* 隔離性

> 指的是 事務在執行期間不應該受到其他事務的影響

* 持久性

> 指的是 事務執行成功,那麽數據應該持久保存到磁盤上。


###事務的安全隱患 

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

* 讀
> 臟讀 不可重讀讀  幻讀.


	* 臟讀

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

	* 不可重復讀 

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


####讀未提交 演示

1. 設置A窗口的隔離級別為 讀未提交 
![icon](img/img03.png)

2. 兩個窗口都分別開啟事務

![icon](img/img04.png)

* 寫

> 丟失更新



#### 讀已提交演示 

1. 設置A窗口的隔離級別為 讀已提交

![icon](img/img05.png)

2. A B 兩個窗口都開啟事務, 在B窗口執行更新操作。

![icon](img/img06.png)

3. 在A窗口執行的查詢結果不一致。 一次是在B窗口提交事務之前,一次是在B窗口提交事務之後。

![icon](img/img07.png)

> 這個隔離級別能夠屏蔽 臟讀的現象, 但是引發了另一個問題  ,不可重復讀。


###可串行化

> 如果有一個連接的隔離級別設置為了串行化 ,那麽誰先打開了事務, 誰就有了先執行的權利, 誰後打開事務,誰就只能得著,等前面的那個事務,提交或者回滾後,才能執行。  但是這種隔離級別一般比較少用。 容易造成性能上的問題。 效率比較低。


* 按效率劃分,從高到低

> 讀未提交  > 讀已提交  > 可重復讀  > 可串行化

* 按攔截程度 ,從高到底

> 可串行化 > 可重復讀 > 讀已提交 >  讀未提交


##事務總結

###需要掌握的

1. 在代碼裏面會使用事務 

		conn.setAutoCommit(false);


		conn.commit();

		conn.rollback();

2. 事務只是針對連接連接對象,如果再開一個連接對象,那麽那是默認的提交。

3. 事務是會自動提交的。 


###需要了解的

####安全隱患

	讀
		臟讀
			一個事務讀到了另一個事務未提交的數據
		不可重復讀
			一個事務讀到了另一個事務已提交的數據,造成前後兩次查詢結果不一致
		幻讀
			一個事務讀到了另一個事務insert的數據 ,造成前後查詢結果不一致 。

	寫

		丟失更新。

####隔離級別

讀未提交

> 引發問題: 臟讀 

讀已提交

> 解決: 臟讀 , 引發: 不可重復讀

可重復讀

> 解決: 臟讀 、 不可重復讀 , 未解決: 幻讀

可串行化

> 解決: 臟讀、 不可重復讀 、 幻讀。

mySql 默認的隔離級別是 可重復讀

Oracle 默認的隔離級別是  讀已提交


###丟失更新

![icon](img/img08.png)


###解決丟失更新

* 悲觀鎖

> 可以在查詢的時候,加入 for update

![icon](img/img09.png)

* 樂觀鎖

> 要求程序員自己控制。 

![icon](img/img10.png)



##數據庫連接池

>1. 數據庫的連接對象創建工作,比較消耗性能。 

>2.一開始現在內存中開辟一塊空間(集合) , 一開先往池子裏面放置 多個連接對象。  後面需要連接的話,直接從池子裏面去。不要去自己創建連接了。  使用完畢, 要記得歸還連接。確保連接對象能循環利用。

![icon](img/img11.png)


###自定義數據庫連接池 


* 代碼實現

* 出現的問題:

	1. 需要額外記住 addBack方法
 
 	2. 單例。
 
 	3. 無法面向接口編程。 
 
 		UserDao dao = new UserDaoImpl();
 		dao.insert();
 
 
 		DataSource dataSource = new MyDataSource();
 
 		因為接口裏面沒有定義addBack方法。 
 
 	4. 怎麽解決?   以addBack 為切入點。

###解決自定義數據庫連接池出現的問題。 

> 由於多了一個addBack 方法,所以使用這個連接池的地方,需要額外記住這個方法,並且還不能面向接口編程。

> 我們打算修改接口中的那個close方法。  原來的Connection對象的close方法,是真的關閉連接。 
> 打算修改這個close方法,以後在調用close, 並不是真的關閉,而是歸還連接對象。


###如何擴展某一個方法?

> 原有的方法邏輯,不是我們想要的。 想修改自己的邏輯

1. 直接改源碼  無法實現。

2. 繼承, 必須得知道這個接口的具體實現是誰。 

3. 使用裝飾者模式。 



##開源連接池

#### DBCP


1. 導入jar文件

2. 不使用配置文件:


		public void testDBCP01(){
		
			
			Connection conn = null;
			PreparedStatement ps = null;
			try {
				
				//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();
				String sql = "insert into account values(null , ? , ?)";
				ps = conn.prepareStatement(sql);
				ps.setString(1, "admin");
				ps.setInt(2, 1000);
				
				ps.executeUpdate();
				
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				JDBCUtil.release(conn, ps);
			}
			
		}

2. 使用配置文件方式:


		Connection conn = null;
		PreparedStatement ps = null;
		try {
			BasicDataSourceFactory factory = new BasicDataSourceFactory();
			Properties properties = new Properties();
			InputStream is = new FileInputStream("src//dbcpconfig.properties");
			properties.load(is);
			DataSource dataSource = factory.createDataSource(properties);
			
			//2. 得到連接對象
			conn = dataSource.getConnection();
			String sql = "insert into account values(null , ? , ?)";
			ps = conn.prepareStatement(sql);
			ps.setString(1, "liangchaowei");
			ps.setInt(2, 100);
			
			ps.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, ps);
		}


		

* C3P0

> 拷貝jar文件 到 lib目錄

###不使用配置文件方式


		Connection conn = null;
		PreparedStatement ps = null;
		try {
			//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");
			
			//2. 得到連接對象
			conn = dataSource.getConnection();
			String sql = "insert into account values(null , ? , ?)";
			ps = conn.prepareStatement(sql);
			ps.setString(1, "admi234n");
			ps.setInt(2, 103200);
			
			ps.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.release(conn, ps);
		}


###使用配置文件方式


			//默認會找 xml 中的 default-config 分支。 
			ComboPooledDataSource dataSource = new ComboPooledDataSource();
			//2. 設置連接數據的信息
			dataSource.setDriverClass("com.mysql.jdbc.Driver");
			

			//忘記了---> 去以前的代碼 ---> jdbc的文檔
			dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
			dataSource.setUser("root");
			dataSource.setPassword("root");
			
			//2. 得到連接對象
			conn = dataSource.getConnection();
			String sql = "insert into account values(null , ? , ?)";
			ps = conn.prepareStatement(sql);
			ps.setString(1, "admi234n");
			ps.setInt(2, 103200);



##DBUtils

###增刪改


				//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);

###查詢

1. 直接new接口的匿名實現類


		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());

2. 直接使用框架已經寫好的實現類。


	* 查詢單個對象

		QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
		//查詢單個對象
		Account account = queryRunner.query("select * from account where id = ?", 
				new BeanHandler<Account>(Account.class), 8);
	
	
	* 查詢多個對象

		QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
		List<Account> list = queryRunner.query("select * from account ",
				new BeanListHandler<Account>(Account.class));

###ResultSetHandler 常用的實現類
以下兩個是使用頻率最高的

	BeanHandler,  查詢到的單個數據封裝成一個對象
	BeanListHandler, 查詢到的多個數據封裝 成一個List<對象>

------------------------------------------

	ArrayHandler,  查詢到的單個數據封裝成一個數組
	ArrayListHandler,  查詢到的多個數據封裝成一個集合 ,集合裏面的元素是數組。 
	
	
	
	MapHandler,  查詢到的單個數據封裝成一個map
	MapListHandler,查詢到的多個數據封裝成一個集合 ,集合裏面的元素是map。 




ColumnListHandler
KeyedHandler
ScalarHandler


#總結

##事務

	使用命令行演示
	
	使用代碼演示

臟讀、

不可重復讀、

幻讀
丟失更新

	悲觀鎖
	樂觀鎖

	4個隔離級別
		讀未提交
		讀已提交
		可重復讀
		可串行化
	

##數據連接池

* DBCP
	
	不使用配置

	使用配置

* C3P0

	不使用配置

	使用配置 (必須掌握)

* 自定義連接池 

	裝飾者模式

##DBUtils

> 簡化了我們的CRUD , 裏面定義了通用的CRUD方法。 

	queryRunner.update();
	queryRunner.query

Java Web(五) -- 事務 & 數據庫連接池 & DBUtiles