1.事務(wù)特性(ACID)
ACID辛蚊,指數(shù)據(jù)庫事務(wù)正確執(zhí)行的四個基本要素的縮寫。包含:原子性(Atomicity)求妹、一致性(Consistency)揩懒、隔離性(Isolation)、持久性(Durability)被辑。一個支持事務(wù)(Transaction)的數(shù)據(jù)庫燎悍,必須要具有這四種,否則在事務(wù)過程當(dāng)中無法保證數(shù)據(jù)的正確性盼理,交易過程極可能達(dá)不到交易方的要求谈山。
原子性(Atomicity):整個事務(wù)中的所有操作,要么全部完成宏怔,要么全部不完成奏路,不可能停滯在中間某個環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯誤臊诊,會被
回滾(Rollback)到事務(wù)開始前的狀態(tài)鸽粉,就像這個事務(wù)從來沒有執(zhí)行過一樣。一致性(Consistency):事務(wù)前數(shù)據(jù)的狀態(tài)和事務(wù)后數(shù)據(jù)的狀態(tài)保持一致
持久性(Durability):在事務(wù)完成以后抓艳,該事務(wù)對數(shù)據(jù)庫所作的更改便持久的保存在數(shù)據(jù)庫之中触机,并不會被回滾。
-
隔離性(Isolation 重點):針對多線程并發(fā)操作數(shù)據(jù)資源的時候,每個事務(wù)應(yīng)該是獨立的儡首,不會受到其他事務(wù)的打擾销斟,如果說數(shù)據(jù)庫軟件不支持事務(wù)的隔離性,多個線程訪問數(shù)據(jù)的時候椒舵,會受到相互的打擾蚂踊。這種情況不是正確的。
1.1隔離級別
1.Serializable (串行化):可避免臟讀笔宿、不可重復(fù)讀犁钟、幻讀的發(fā)生。
2.Repeatable read (可重復(fù)讀):可避免臟讀泼橘、不可重復(fù)讀的發(fā)生(mysql默認(rèn)的隔離級別)涝动,有可能發(fā)生幻讀。
3.Read committed (讀已提交):可避免臟讀的發(fā)生(oralce默認(rèn)的隔離級別)炬灭,不可重復(fù)讀還有幻讀有可能發(fā)生醋粟。
4.Read uncommitted (讀未提交):最低級別,任何情況都無法保證重归。
1.2如果沒有隔離性而言米愿,會出現(xiàn)哪些問題?
1.臟讀:兩個事務(wù)(t1,t2),t1事務(wù)開啟鼻吮,修改了李四的賬戶的錢加上1000育苟,并沒有提交事務(wù),t2,查詢到了李四已經(jīng)修改后的數(shù)據(jù)椎木。這種數(shù)據(jù)就是臟讀违柏。(t2讀到了t1沒有提交的數(shù)據(jù))。
2.不可重復(fù)讀:兩個事務(wù)(t1,t2),t1事務(wù)開啟香椎,t2事務(wù)開啟漱竖,t2查詢一次李四的賬戶的資金,t1修改李四的錢加上1000畜伐,t1提交是事務(wù)馍惹。t2在沒有提交事務(wù)的前提下,執(zhí)行了相同的查詢烤礁,出現(xiàn)了本次查詢的數(shù)據(jù)發(fā)生變化讼积。(t2本次事務(wù)中部可以能重復(fù)讀數(shù)據(jù))
3.幻讀(虛讀):兩個事務(wù)(t1,t2)肥照,一般針對的是數(shù)據(jù)的添加操作脚仔,t1開啟事務(wù),t2開啟事務(wù)舆绎,t1查詢賬戶表有5個用戶鲤脏,t2,在賬戶表中添加了一個用戶,t2提交事務(wù)。t1又查詢賬戶表發(fā)現(xiàn)會6個用戶猎醇。這就出現(xiàn)了幻讀窥突。
2.TCL語句
- SQL語句分類
1. DDL 數(shù)據(jù)定義語句 (create 、drop.....sql語句)
2. DML 數(shù)據(jù)操作語句(updat硫嘶、insert into 阻问、 delete.... sql 語句)
3. DQL 數(shù)據(jù)查詢語句 (select ... 語句 )
4. TCL 事務(wù)控制語句
start transaction 開啟事務(wù)
rollback 事務(wù)回滾,本次事務(wù)就結(jié)束
commit 事務(wù)提交沦疾,本次事務(wù)就結(jié)束
select @@tx_isolation 針對mysql查看隔離級別
set tx_isolation ='隔離級別' 設(shè)置隔離級別
3.編程式事務(wù)管理
編碼方式實現(xiàn)事務(wù)管理(JDBC編程來管理事務(wù))
聲明式事務(wù)管理:可知編程式事務(wù)每次實現(xiàn)都要單獨實現(xiàn)称近,但業(yè)務(wù)量大功能復(fù)雜時,使用編程式事務(wù)無疑是痛苦的哮塞,而聲明式事務(wù)不同刨秆,聲明式事務(wù)屬于無侵入式,不會影響業(yè)務(wù)邏輯的實現(xiàn)(通過配置方案就實現(xiàn)了事務(wù)的管理)
3.1 JDBC編程式管理
public void update(UserModel entity) {
// 1.創(chuàng)建三大對象引用
Connection conn = null;
PreparedStatement pstmt = null;
// 2.初始化引用對象
// 2.1 try-catch-finally
try {
conn = DBUtil.getConn();
// 關(guān)閉了事務(wù)的自動提交忆畅,關(guān)閉自動事務(wù)提交也就意味著手動開啟了事務(wù)衡未。
// 開啟事務(wù)
conn.setAutoCommit(false);
pstmt = conn.prepareStatement(UPDATE_SQL);
pstmt.setObject(1, entity.getName());
pstmt.setObject(2, entity.getPassword());
pstmt.setObject(3, entity.getEmail());
pstmt.setObject(4, entity.getBirthday());
pstmt.setObject(5, entity.getId());
pstmt.executeUpdate();
int i = 1 / 0;
// 提交事務(wù)
conn.commit();
} catch (Exception e) {
try {
// 回滾事務(wù)
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
} finally {
DBUtil.replease(conn, pstmt, null);
}
}
注意:以上的編程事務(wù)管理方式是有問題的,由于把事務(wù)的管理操作加入到數(shù)據(jù)訪問層 Dao層,在實際開發(fā)過程中事務(wù)應(yīng)該加到業(yè)務(wù)層家凯。
4.數(shù)據(jù)源(連接池) JDBC規(guī)范中的DataSource 接口
數(shù)據(jù)源提供了一種簡單獲取數(shù)據(jù)庫連接的方式缓醋,并能在內(nèi)部通過一個池的機制來復(fù)用數(shù)據(jù)庫連接,這樣就大大減少創(chuàng)建數(shù)據(jù)庫連接的次數(shù)绊诲,提高了系統(tǒng)性能改衩。
-
sun公司并沒有對DataSource進(jìn)行實現(xiàn)
-
市面上對數(shù)據(jù)源主要的實現(xiàn)工具類
1.apache組織開發(fā)了一個 DBCP數(shù)據(jù)源
2.c3p0組織開發(fā)的c3p0數(shù)據(jù)源
3.alibaba溫少開發(fā)的druid德魯伊數(shù)據(jù)源(國產(chǎn)貨)
4.1開發(fā)druid數(shù)據(jù)源工具類
1.導(dǎo)入alibaba的druid數(shù)據(jù)源依賴jar包
2.編寫DruidUtil工具類
public class DruidUtil {
private static final DruidDataSource DATASOURCE = new DruidDataSource();
private static final String URL;
private static final String USER;
private static final String PASSWORD;
private static final String DRIVER;
static {
try {
InputStream inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties");
Properties p = new Properties();
p.load(inStream);
URL = p.getProperty("jdbc.url");
USER = p.getProperty("jdbc.user");
PASSWORD = p.getProperty("jdbc.password");
DRIVER = p.getProperty("jdbc.driver");
DATASOURCE.setDriverClassName(DRIVER);
DATASOURCE.setPassword(PASSWORD);
DATASOURCE.setUrl(URL);
DATASOURCE.setUsername(USER);
} catch (Exception e) {
throw new ExceptionInInitializerError("數(shù)據(jù)源配置出錯!");
}
}
private DruidUtil() {
}
/**
* 獲取連接對象
*/
public static Connection getConn() {
try {
return DATASOURCE.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 釋放資源
*
*/
public static void release(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
/**
* 得到數(shù)據(jù)源對象
*
*/
public static DataSource getDataSource() {
return DATASOURCE;
}
}
5.ThreadLocal事務(wù)控制
早在JDK 1.2的版本中就提供java.lang.ThreadLocal驯镊,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路葫督。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序。
當(dāng)使用ThreadLocal維護變量時板惑,ThreadLocal為每個使用該變量的線程提供獨立的變量副本橄镜,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本冯乘。
從線程的角度看洽胶,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思裆馒。
所以姊氓,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發(fā)者中得到很好的普及喷好。
5.1ThreadLocal接口 api介紹:
- get() 從線程中取變量
- remove() 從線程中移除變量
- set(T value) 向線程中設(shè)置變量
5.2 修改DruidUtil工具類
public class DruidUtil {
private static final DruidDataSource DATASOURCE = new DruidDataSource();
// 創(chuàng)建線程局部變量操作對象
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private static final String URL;
private static final String USER;
private static final String PASSWORD;
private static final String DRIVER;
static {
try {
InputStream inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties");
Properties p = new Properties();
p.load(inStream);
URL = p.getProperty("jdbc.url");
USER = p.getProperty("jdbc.user");
PASSWORD = p.getProperty("jdbc.password");
DRIVER = p.getProperty("jdbc.driver");
DATASOURCE.setDriverClassName(DRIVER);
DATASOURCE.setPassword(PASSWORD);
DATASOURCE.setUrl(URL);
DATASOURCE.setUsername(USER);
} catch (Exception e) {
throw new ExceptionInInitializerError("數(shù)據(jù)源配置出錯翔横!");
}
}
private DruidUtil() {
}
/**
* 獲取連接對象
*/
public static Connection getConn() {
try {
Connection conn = null;
// 先從線程中取Connection對象
if (tl.get() == null) {
conn = DATASOURCE.getConnection();
// 放入到當(dāng)前線程中
tl.set(conn);
}
return tl.get();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("連接對象獲取失敗梗搅!");
}
}
/**
* 釋放資源
*
*/
@Deprecated
public static void release(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
// 從線程中移除掉Connection對象
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
/**
* 得到數(shù)據(jù)源對象
*
*/
public static DataSource getDataSource() {
return DATASOURCE;
}
/**
* 開啟事務(wù)
*/
public static void startTransaction() {
Connection conn = getConn();
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事務(wù)
*/
public static void commit() {
try {
Connection conn = getConn();
conn.commit();
conn.close();
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 事務(wù)回滾
*/
public static void rollback() {
try {
Connection conn = getConn();
conn.rollback();
conn.close();
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
5.3 業(yè)務(wù)層代碼添加事務(wù)
public class AccountServiceImpl implements IAccountService {
private IAccoutDao accountDao = new AccountDaoImpl();
public void transferAccount(Account srcAccout, Account targAccount, double cash) {
srcAccout.setMoney(srcAccout.getMoney() - cash);
targAccount.setMoney(targAccount.getMoney() + cash);
try {
DruidUtil.startTransaction();
accountDao.modifyAccountMoney(srcAccout);
// 測試異常禾唁,事務(wù)回滾
int i = 1 / 0;
accountDao.modifyAccountMoney(targAccount);
DruidUtil.commit();
} catch (Exception e) {
e.printStackTrace();
DruidUtil.rollback();
}
}
}
5.4 數(shù)據(jù)訪問層代碼簡化為
public class AccountDaoImpl implements IAccoutDao {
private static final String MODIFY_SQL = "UPDATE ACCOUNT SET MONEY = ? WHERE NAME = ?";
public void modifyAccountMoney(Account srcAccout) throws Exception {
Connection conn = null;
PreparedStatement stmt = null;
conn = DruidUtil.getConn();
stmt = conn.prepareStatement(MODIFY_SQL);
stmt.setObject(1, srcAccout.getMoney());
stmt.setObject(2, srcAccout.getName());
stmt.executeUpdate();
}
}