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.事務的安全隱患:
- 讀產生的問題:
- 讀髒資料:一個事務讀到另外一個事務還未提交的資料。
- 不可重複讀:一個事務讀到另外一個事務提交的資料,造成前後兩次查詢結果不一致。
- 幻讀:一個事務讀到了另一個事務已提交的插入資料,導致多次查詢結果不一樣。
- 隔離級別:
- 讀未提交:會引起髒讀的問題。
- 讀已提交:能解決髒讀的問題,但引發不可重複讀。
- 可重複讀:能解決髒讀,不可重複讀的問題,但是不能解決幻讀。
- 可序列化:能解決髒讀,不可重複讀,幻讀的問題。
mysql預設的隔離級別是可重複讀
Oracle預設的隔離級別是讀已提交
- 寫產生的問題:丟失更新
- 解決丟失更新的方法:
- 悲觀鎖:可以在查詢的時候加入for update
- 樂觀鎖:要求程式設計師自己控制
資料庫連線池:
1)資料庫的連線物件建立工作,比較消耗效能。
2)一開始先在記憶體中開闢一塊空間(集合),一開始先往裡面放置多個連線物件。後面如果需要連線, 直接從裡面取,不要自己建立連線。使用完畢後,要歸還連線,確保連線物件可以迴圈利用。
1.自己模擬資料庫連線池的簡單建立和使用
- 先往池子裡面放10個連線
- 開始建立10個連線
- 來的程式通過getConnection獲取連線
- 用完之後,使用back歸還連線
- 擴容
- 使用這種方法(MyDataSource.java)會產生的問題:
- 需要額外記住back歸還方法。
- 沒法實現單例模式,導致不斷地new物件。
- 無法面向介面程式設計,因為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方法是真的關閉連線,改成以後關閉時,是一個歸還連線的方法。
- 如何擴充套件一個方法:
- 直接改原始碼,無法實現。
- 使用繼承,無法知道實現Connection介面的類,所以也不行。
- 使用裝飾者模式。(在這使用)
- 動態代理。
使用裝飾者模式解決後的程式碼~
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);