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