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