1. 程式人生 > >Java——Web開發之事務與資料庫連線池

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