一 事務
(一)事務的概念
事務指邏輯上一組操作,組成這組操作的各個單元,要么全部成功要么全部不成功。 例如:A--B轉賬,對應如下的兩條sql語句:
Update account set money = money-100 where name=’a’;;
Update account set money = money+100 where name=’b’;
數據庫默認事務時自動提交的,也就是發一條sql它就執行一條,如果想多條sql放在一個事務中,需要如下。
(二)數據庫開啟事務cmd命令
開啟事務:start transaction
回退事務:rollback
提交事務:commit
(三)JDBC開啟事務
1.一般事務
conn.setAutoCommit(false);//設置事務不自動提交。
Conn.commit();//后提價事務。
Conn.rollback();//出現異常,回退事務。
Connection conn=null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = DriverManager.getConnection
("jdbc:mysql://localhost:3306/mytransation", "root", "root");
//設置不自動提交
conn.setAutoCommit(false);
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
//假設異常點
/*int a=1/0;*/
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
//在這里提交事務
conn.commit();
}
catch(Exception e){
try {
//出現異常回退事務
if(conn!=null)
conn.rollback();
System.out.println("出現異常,事務回退");
} catch (SQLExcep tion e2) {
e2.printStackTrace();
}
System.out.println(e.getMessage());
if(conn!=null){
try {
conn.close();
} catch (SQLException e1) {
e1.printStackTrace();
System.out.println("Connection異常");
} finally{
conn = null;
}
2.設置回滾點提交事務,提交異常之前的sql語句
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Savepoint sp = null;
try{
conn = DriverManager.getConnection("jdbc:mysql:///mytransation", "root", "root");
conn.setAutoCommit(false);//設置不自動提交事務
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
sp = conn.setSavepoint();//設置回滾點
ps = conn.prepareStatement("update account set money=money-100 where name=?");
ps.setString(1, "a");
ps.executeUpdate();
int a=1/0;//模擬異常
ps = conn.prepareStatement("update account set money=money+100 where name=?");
ps.setString(1,"b");
ps.executeUpdate();
conn.commit();//提交事務
} catch(Exception e){
try{
if(conn !=null){//如果sp為空,則在回滾點之前回退模式
if(sp == null){
conn.rollback();
}else{//否則回滾到回滾點的數據,讓其提交。
conn.rollback(sp);
conn.commit();
}
}
}
catch(SQLException e1){
}
}
3.事務的四大特性(ACID,很重要)。
原子性(Atomicity):事務時一個不可分割的工作單位,事務中操作 要么發生,要么都不發生。
一致性(Consistency):事務前后數據的完整性必須保持一致。
隔離性(Isolation):多個用戶并發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所干擾。多個并發事務之間數據要相互隔離。
持久性(Durability):事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
如果不考慮事務的隔離性,會出現的問題:
(1)臟讀:一個事務讀取到另外一個事務的未提交的數據
(2)不可重復讀: 同一個數據(表)不可重復讀(針對于update更新數據)
(3)虛讀:同一數據(表)不可重復讀(強調insert)
(4)丟失更新:
4.隔離級別以及隔離級別的設置
(1)隔離級別
Read uncommitted 數據庫不能防止臟讀、不可重復讀、虛讀。
Read committed(oracle) 能防止臟讀,不能防止不可重復讀、虛讀。
Repeatable read(mysql) 能防止臟讀、不可重復讀,不能防止虛讀。
Serializable 單線程數據庫,能防止所有的問題。
性能分析:read uncommitted>read committed>repeatable read>serializable
安全性能:serializable>repeatable read>read committed>read uncommitted
(2)隔離級別的設置
1)cmd操作命令
Select @@tx_isolation 查詢當前事務隔離級別
Set session transaction isolation level 設置的事務級別
2)JDBC中操作隔離級別
Connection的實例方法setTransactionIsolation(int level)
可以的值是(Connection的靜態常量):
TRANSACTION_READ_UNCOMMITTED(不能防止臟讀,不可重復讀,虛讀)
TRANSACTION_READ_COMMITTED(能防止臟讀,不能解決不可重復讀,虛讀)
TRANSACTION_REPEATABLE_READ(能防止臟讀,不可重復讀,不能解決虛讀)
TRANSACTION_SERIALIZABLE(可以解決所有的問題)
5.臟讀、不可重復讀、虛讀案例演示。
(1)臟讀演示
一開始狀態:A賬戶:1000元
B 賬戶:1000元
A賬戶向B賬戶匯款的情況:
start transaction
Update account set money=money-100 where name=’A’;
Update account set money=money+100 where name=’B’;
-------此時B查詢,設置隔離級別為read uncommitted
Select * from account where name=’B’;結果為1100;
Rollback;回滾事務,數據并未修改成功。
(2)不可重復讀的
a 賬戶三個月工資為:1000,1000,1000
Start transaction
Select sum(money) from account where name=’a’;
-------此時 財務b,將第一個月工資修改為1300,并且立即提交,即
Update account set money=1300 where name=’a’;
Select sum(money)/count(*) from account where name=’a’;提交,發現報表不對。
(3)虛讀
6.轉賬小案例,事務控制。
1)邏輯層(service)調用dao層的兩個方法(一個轉入方法,一個轉出方法),解決其中一個方法失敗,因此需要事務控制,事務回滾等。
2)用到事務,那么就應該使用同一個Connection對象,因此,在service層的方法里,應該共用一個Connection對象。
3)mysql,修改不存在的用戶名的賬戶值返回值代表影響的行數,由此可以找到解決該方法的問題。
4)學會使用同一個ThreadLocal,可以理解為同一個線程的容器,只要在同一個線程,就可以取出來使用。示例:
Private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
Public static Connection getCon(){
If(tl.get==null){
Tl.set(DriverManager.getConnection(“”,””,””));
}
Return tl.get();
}
7.數據丟失更新。
1)數據丟失更新示意圖(多個事務對同一行數據進行操作,后提交的事務將覆蓋先提交的事務)。
2)數據更新丟失的解決方法
(1)悲觀鎖(假設丟失更新一定發生,利用數據庫內部鎖機制)
共享鎖:
Select * from account lock in share mode(讀鎖,共享鎖)
排它鎖(被鎖中的可以是一行數據,也可以是整張表):
Select * from account for update(寫鎖,排它鎖)。
Update 默認是排他鎖。
(2)樂觀鎖(加鎖丟失更新不會發生,采用程序中添加版本字段解決丟失更新問題)
create table product (
id int,
name varchar(20),
updatetime timestamp
);
insert into product values(1,'冰箱',null);
update product set name='洗衣機' where id = 1;
解決丟失更新:在數據表添加版本字段,每次修改過記錄后,版本字段都會更新,如果讀取是版本字段,與修改時版本字段不一致,說明別人進行修改過數據 (重改)
8.數據庫連接池
(1)定義:創建一個容器,這個容器來裝多個Connection對象,在使用該對象的時候,從容器獲取一個Connection對象,使用完畢后,再把這個Connection對象重新裝到容器當中。那么這個容器就叫做數據庫連接池。
(2)自定義連接池
步驟:1.創建一個MyDataSource類,在這個類中創建一個LinkedList<Connection>
2.在構造器方法中初始化List集合,并向其中裝入5個Connection對象
3.創建一個public Connection getConnection();從List集合中獲取一個連接對象返回
4.創建一個Public void readdd(Connetion)這個方法是將使用完成后的Connection對象重新裝到List集合中。
代碼問題:
1.連接池的創建時有標準的,在javax.sql包下定義一個接口DataSource,我們必須實現
2.改變Connection的close()方法,不是銷毀它,而是將它重新裝入到連接池中。解決這
個問題本質就是將Connection的close()行為改變。
三種方式改變方法行為:繼承;裝飾者模式;動態代理
(3)使用開源連接池
1.dbcp(DataBase Connection Pool)
Dbcp是Apache下的一個開源連接池,兩個關鍵.jar包(commons-dbcp-1.4.jar和commons-pool-1.5.6.jar)
1)手動配置
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///mytransation");
bds.setUsername("root");
bds.setPassword("root");
Connection con = bds.getConnection();
ResultSet rs = con.prepareStatement("select * from account").executeQuery();
while(rs.next()){
System.out.println("賬戶:"+rs.getString("name")+",余額:"+rs.getDouble("money"));
}
JDBCUtils.closeRS(rs);
con.close();//不是關閉連接,而是放回連接池
2)自動配置
Properties p = new Properties();
p.load(new FileInputStream("D:\\Myeclipse java程序\\AccountTransaction\\src\\dbcp.properties"));2.c3p0 (重點,是一個開源數JDBC連接池,實現數據源和JNDI綁定,支持JDBC3和JDBC2的標準擴展。使用它的開源項目有Hibernate,Spring等。性能更強,擁有自動回收空閑連接功能)
1)手動配置
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///mytransation");
cpds.setUser("root");
cpds.setPassword("root");
Connection con = cpds.getConnection();
ResultSet rs = con.prepareStatement("select * from account").executeQuery();
while(rs.next()){
System.out.println("賬戶:"+rs.getString("name")+",余額:"+rs.getDouble("money")+"c3p0hand");
}
JDBCUtils.closeRS(rs);
con.close();//不是關閉連接,而是放回連接池
2)自動配置
C3p0的配置文件可以是properties也可以是xml,對應的文件名是c3p0.properties或者c3p0-config.xml,要求放在classpath路徑下(也就是web應用的classes目錄),我們放在src目錄下即可。
使用:ComboPooledDataSource cpds = new ComboPooledDataSource();
Xml文件形式:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>Properties形式:
(4)tomcat內置連接池(測試出來!!)
Tomcat連接池用的是dbcp。
1)tomcat怎樣管理連接池
要想將一個dbcp連接池讓tomcat管理,只需要創建一個context.xml配置文件,在配置文件中配置相關信息。
<Context>
<Resource name=”jdbc/EmployeeDB” auth=”Container”
Type=”javax.sql.DataSource” username=”root” password=”root”
driverClassName=”com.mysql.jdbc.Driver” url=”jdbc:mysql:///mytransat”
maxActive=”8” maxIdle=”4”
/>
</Context>
問題:context.xml文件的配置位置:
(1)在tomcat/conf/context.xml 這個連接池是給整個服務器用的(全局)
(2)在tomcat/conf/Catalina/localhost 連接池只給localhost虛擬主機使用(全局)。
(3)將其配置在web應用的META-INF下。
注意:如果是全局設置,那么需要將數據庫驅動放置在tomcat/lib目錄下。
問題:怎么從tomcat獲取鏈接池對象?
Context context = new InitialContext();
Context envCtx = (Context) context.lookup(“java:comp/env”);//固定路徑
DataSource ds = (DataSource)envCtx.lookup(“jdbc/EmployeeDB”);
.......操作Connection對象