Java——Web開發之事務與數據庫連接池

事務:指的是一組操作,裏面包含許多個單一的邏輯,只要有一個邏輯沒有執行成功,那麼都算失敗,所有的數據都回到最初的狀態。事務在默認情況下是自動提交的。(事務指針對連接對象)

 

1.事務的簡單使用

1).關閉自動提交的設置 conn.setAutoCommit(false)
2).提交事務 conn.commit
3).回滾事務 conn.rollback

 

package web.stu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import web.stu.util.JDBCUtil;

public class Transaction {
	
public static void main(String[] args){
		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);
		
			ps.setInt(1, 100);
			ps.setInt(2, 1);
			ps.executeUpdate();
			
			//int a = 10 /0 ;
			
			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);
		}
	}
}

2.事務的特性:ACID

  • 原子性:事務中包含的邏輯不可分割。
  • 一致性:事務執行的前後,數據的完整性保持一致。
  • 隔離性:事務在執行期間不受到其他事務的影響。
  • 持久性:事務執行成功,則數據應該持久保存到磁盤上。

 

3.事務的安全隱患:

  • 讀產生的問題:
  1. 讀髒數據:一個事務讀到另外一個事務還未提交的數據。
  2. 不可重複讀:一個事務讀到另外一個事務提交的數據,造成前後兩次查詢結果不一致。
  3. 幻讀:一個事務讀到了另一個事務已提交的插入數據,導致多次查詢結果不一樣。
  • 隔離級別:
  1. 讀未提交:會引起髒讀的問題。
  2. 讀已提交:能解決髒讀的問題,但引發不可重複讀。
  3. 可重複讀:能解決髒讀,不可重複讀的問題,但是不能解決幻讀。
  4. 可串行化:能解決髒讀,不可重複讀,幻讀的問題。

     mysql默認的隔離級別是可重複讀

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

  • 寫產生的問題:丟失更新
  • 解決丟失更新的方法:
  1. 悲觀鎖:可以在查詢的時候加入for update
  2. 樂觀鎖:要求程序員自己控制

 

數據庫連接池:

1)數據庫的連接對象創建工作,比較消耗性能。
2)一開始先在內存中開闢一塊空間(集合),一開始先往裏面放置多個連接對象。後面如果需要連接, 直接從裏面取,不要自己創建連接。使用完畢後,要歸還連接,確保連接對象可以循環利用。
 

1.自己模擬數據庫連接池的簡單創建和使用

  • 先往池子裏面放10個連接
  1. 開始創建10個連接
  2. 來的程序通過getConnection獲取連接
  3. 用完之後,使用back歸還連接
  4. 擴容
  • 使用這種方法(MyDataSource.java)會產生的問題:
  1. 需要額外記住back歸還方法。
  2. 沒法實現單例模式,導致不斷地new對象。
  3. 無法面向接口編程,因爲dataSource接口裏面沒有定義back方法,導致沒法調用。

MyDataSource.java

package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource implements DataSource{

	//先往池子裏面放10個連接
	List<Connection> list =new ArrayList<Connection>();
	public MyDataSource(){
		for (int i = 0; i < 10; i++) {
			Connection conn=JDBCUtil.getConn();
			list.add(conn);
		}
	}
	
	//連接池對外公佈的獲取連接的方法
	@Override
	public Connection getConnection() throws SQLException {
		//來拿連接時,先判斷池子有沒有連接,如果沒有就擴容
		if(list.size()==0){
			for (int i = 0; i < 10; i++) {
				Connection conn=JDBCUtil.getConn();
				list.add(conn);
			}
		}
		//移除的是集合中的第一個元素 
		Connection conn=list.remove(0);
		return conn;
	}
	//用完之後,歸還到池子裏
	public void back(Connection conn){
		list.add(conn);
	}
	@Override
	public PrintWriter getLogWriter() throws SQLException {
		
		return null;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
	}
	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
	}
	@Override
	public int getLoginTimeout() throws SQLException {
		
		return 0;
	}
	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		
		return null;
	}
	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		
		return null;
	}
	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		
		return false;
	}
	@Override
	public Connection getConnection(String username, String password)
			throws SQLException {
		
		return null;
	}
}

TestPool.java

package util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

public class TestPool {
	@Test
	public void testPool(){
		Connection conn=null;
		PreparedStatement ps=null;
		MyDataSource dataSource=new MyDataSource();
		try {
			conn=dataSource.getConnection();
			String sql="insert into account values (null,'qq',2000)";
			ps=conn.prepareStatement(sql);
			ps.executeUpdate();
			
		} catch (SQLException e) {
			
			e.printStackTrace();
		}finally{
			try {
				ps.close();
			} catch (SQLException e) {
				
				e.printStackTrace();
			}
			//歸還連接
			dataSource.back(conn);	
		}
	}	
}

 

  •  解決方法:修改接口中的close方法。原來的Connection對象的close方法是真的關閉連接,改成以後關閉時,是一個歸還連接的方法。
  •  如何擴展一個方法:
  1.  直接改源碼,無法實現。
  2.  使用繼承,無法知道實現Connection接口的類,所以也不行。
  3.  使用裝飾者模式。(在這使用)
  4.  動態代理。

使用裝飾者模式解決後的代碼~

MyDataSource2.java

package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource2 implements DataSource{
	//先往池子裏面放10個連接
	List<Connection> list =new ArrayList<Connection>();
	public MyDataSource2(){
		for (int i = 0; i < 10; i++) {
			Connection conn=JDBCUtil.getConn();
			list.add(conn);
		}
	}
	//連接池對外公佈的獲取連接的方法
	@Override
	public Connection getConnection() throws SQLException {
		//來拿連接時,先判斷池子有沒有連接,如果沒有就擴容
		if(list.size()==0){
			for (int i = 0; i < 10; i++) {
				Connection conn=JDBCUtil.getConn();
				list.add(conn);
			}
		}
		//移除的是集合中的第一個元素 
		Connection conn=list.remove(0);
		//conn.close();	//直接調用自己擴展的,直接可以歸還	
		//在把這個對象拋出去的時候,對這個對象進行包裝
		Connection connection=new ConnectionWrap(conn,list);
		return connection;
	}
	//用完之後,歸還到池子裏
	public void back(Connection conn){
		list.add(conn);
	}
	@Override
	public PrintWriter getLogWriter() throws SQLException {
		
		return null;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
	}
	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
	}
	@Override
	public int getLoginTimeout() throws SQLException {
		return 0;
	}
	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {	
		return null;
	}
	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}
	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}
	@Override
	public Connection getConnection(String username, String password)
			throws SQLException {
		return null;
	}
}

ConnectionWrap.java需要實現Connection接口,重寫close方法,因爲代碼太長,所以只貼出部分代碼截圖

 

測試代碼TestPool2.java

package util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

public class TestPool2 {
	@Test
	public void testPool(){
		Connection conn=null;
		PreparedStatement ps=null;
		MyDataSource dataSource=new MyDataSource();
		try {
			conn=dataSource.getConnection();
			String sql="insert into account values (null,'qq',2000)";
			ps=conn.prepareStatement(sql);
			ps.executeUpdate();
			
		} catch (SQLException e) {
			
			e.printStackTrace();
		}finally{
			try {
				ps.close();
			} catch (SQLException e) {
				
				e.printStackTrace();
			}
			JDBCUtil.release(conn, ps);
		}
	}	
}

使用到之前的JDBCUtil.java代碼

package util;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtil {
	static String driverClass = null;
	static String url = null;
	static String name = null;
	static String password= null;
	static{
		try {
			//1. 創建一個屬性配置對象
			Properties properties = new Properties();
			InputStream is = new FileInputStream("jdbc.properties");	//Java工程裏可以直接new來加載配置文件
			
			//使用類加載器,去讀取src底下的資源文件。 後面在servlet,web工程需要使用類加載器來加載配置文件
			//InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
			//導入輸入流。
			properties.load(is);
			
			//讀取屬性
			driverClass = properties.getProperty("driverClass");
			url = properties.getProperty("url");
			name = properties.getProperty("name");
			password = properties.getProperty("password");
				
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 獲取連接對象
	 * @return
	 */
	public static Connection getConn(){
		Connection conn = null;
		try {
			Class.forName(driverClass);
			//靜態代碼塊 ---> 類加載了,就執行。 java.sql.DriverManager.registerDriver(new Driver());
			//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
			//DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
			//2. 建立連接 參數一: 協議 + 訪問的數據庫 , 參數二: 用戶名 , 參數三: 密碼。
			conn = DriverManager.getConnection(url, name, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * 釋放資源
	 */
	public static void release(Connection conn , Statement st , ResultSet rs){
		closeRs(rs);
		closeSt(st);
		closeConn(conn);
	}
	public static void release(Connection conn , Statement st){
		closeSt(st);
		closeConn(conn);
	}
	private static void closeRs(ResultSet rs){
		try {
			if(rs != null){
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			rs = null;
		}
	}
	private static void closeSt(Statement st){
		try {
			if(st != null){
				st.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			st = null;
		}
	}
	private static void closeConn(Connection conn){
		try {
			if(conn != null){
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			conn = null;
		}
	}
}

注:在上面這個代碼需要注意一下Properties的創建,對於java工程和web工程有不同的加載方式,在下面給出部分代碼~

  • 創建一個屬性配置對象

            Properties properties = new Properties();
            InputStream is = new FileInputStream("jdbc.properties");    //Java工程裏可以直接new來加載配置文件
            
            使用類加載器,去讀取src底下的資源文件。 後面在servlet,web工程需要使用類加載器來加載配置文件
            InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            導入輸入流。
            properties.load(is);

 

 

 

 

 

 

 

 

 

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