一、事務
事務就是一個事情凿宾,組成這個事情可能有多個單元矾屯,要求這些單元,要么全都成功菌湃,要么全都不成功问拘。
在開發(fā)中,有事務的存在惧所,可以保證數(shù)據(jù)完整性骤坐。
事務的操作
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);
- mysql下怎樣操作
方式1:
start transaction
開啟事務
rollback
事務回滾(回滾到最開始位置)
commit
事務提交(沒有commit就不會修改數(shù)據(jù))
方式2:
show variables like '%commit%';
可以查看當前autocommit值.在mysql數(shù)據(jù)庫中它的默認值是"on",代表自動事務(執(zhí)行任何一條mysql語句都會自動提交事務).
測試:將autocommit的值設置為off
1.set autocommit=off 關閉自動事務。
2.必須手動commit才可以將事務提交下愈。
注意:mysql默認autocommit=on oracle默認的autocommit=off; - jdbc下怎樣操作
java.sql.Connection
接口中有幾個方法是用于可以操作事務
1.setAutocommit(boolean flag)
如果flag=false;它就相當于start transaction;
2.rollBack()
事務回滾
3.commit()
事務提交
// 隨便拋異常版纽绍,僅限演示,開發(fā)中不這么寫
public class TransactionTest1 {
public static void main(String[] args) throws SQLException {
// 修改id=2這個人的money=500;
String sql = "update account set money=500 where id=2";
Connection con = JdbcUtils.getConnection();
con.setAutoCommit(false); //開啟事務势似,相當于 start transaction;
Statement st = con.createStatement();
st.executeUpdate(sql);
//事務回滾
//con.rollback();
con.commit(); //事務提交
st.close();
con.close();
}
}
// 開發(fā)中應該這么寫
public class TransactionTest2 {
public static void main(String[] args) {
// 修改id=2這個人的money=500;
String sql = "update account set money=500 where id=2";
Connection con = null;
Statement st = null;
try {
con = JdbcUtils.getConnection();
con.setAutoCommit(false); // 開啟事務拌夏,相當于 start transaction;
st = con.createStatement();
st.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
// 事務回滾
try {
con.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
con.commit(); // 事務提交
st.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
二、事務特性(重點)
- 原子性:事務是一個不可分割的工作單位履因,事務中的操作要么都發(fā)生障簿,要么都不發(fā)生。
- 一致性:事務前后數(shù)據(jù)的完整性必須保持一致栅迄。
- 隔離性:多個用戶并發(fā)訪問數(shù)據(jù)庫時站故,一個用戶的事務不能被其它用戶的事務所干擾,多個并發(fā)事務之間數(shù)據(jù)要相互隔離。
- 持久性:一個事務一旦被提交西篓,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的愈腾,接下來即使數(shù)據(jù)庫發(fā)生故障也不應該對其有任何影響。
如果不考慮事務的隔離性岂津,會出現(xiàn)什么問題虱黄?
- 臟讀:一個事務讀取到另一個事務的未提交數(shù)據(jù)
- 不可重復讀:兩次讀取的數(shù)據(jù)不一致(強調(diào)update)
- 虛讀(幻讀):兩次讀取的數(shù)據(jù)不一致(強調(diào)insert)
- 丟失更新:兩個事務對同一條記錄進行操作,后提交的事務吮成,將先提交的事務的修改覆蓋了橱乱。
解決方案
- 事務的隔離級別有哪些?
- Serializable:可避免臟讀、不可重復讀赁豆、虛讀情況的發(fā)生仅醇。(串行化)
- Repeatable read:可避免臟讀、不可重復讀情況的發(fā)生魔种。(可重復讀)不可以避免虛讀
- Read committed:可避免臟讀情況發(fā)生(讀已提交)
- Read uncommitted:最低級別析二,以上情況均無法保證。(讀未提交)
- 怎樣設置事務的隔離級別?
1.mysql中設置
1.查看事務隔離級別
select @@tx_isolation 查詢當前事務隔離級別(默認為Repeatable read).
擴展:oracle中默認是Read committed
2.mysql中怎樣設置事務隔離級別
set session transaction isolation level 事務隔離級別
2.jdbc中設置
使用java.sql.Connection接口中提供的方法
void setTransactionIsolation(int level) throws SQLException
參數(shù)level可以取以下值:
level - 以下 Connection 常量之一:
Connection.TRANSACTION_READ_UNCOMMITTED节预、
Connection.TRANSACTION_READ_COMMITTED叶摄、
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE。
(注意安拟,不能使用 Connection.TRANSACTION_NONE蛤吓,因為它指定了不受支持的事務。)
- 演示
1.臟讀
一個事務讀取到另一個事務的為提交數(shù)據(jù)
設置A,B事務隔離級別為 Read uncommitted
set session transaction isolation level read uncommitted;
1.在A事務中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事務中
start transaction;
select * from account;
這時糠赦,B事務讀取時会傲,會發(fā)現(xiàn),錢已經(jīng)匯完拙泽。那么就出現(xiàn)了臟讀淌山。
當A事務提交前,執(zhí)行rollback顾瞻,在commit泼疑, B事務在查詢,就會發(fā)現(xiàn)荷荤,錢恢復成原樣
也出現(xiàn)了兩次查詢結果不一致問題退渗,出現(xiàn)了不可重復讀.
2.解決臟讀問題
將事務的隔離級別設置為 read committed來解決臟讀
設置A,B事務隔離級別為 Read committed
set session transaction isolation level read committed;
1.在A事務中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事務中
start transaction;
select * from account;
這時B事務中,讀取信息時蕴纳,是不能讀到A事務未提交的數(shù)據(jù)的会油,也就解決了臟讀。
讓A事務古毛,提交數(shù)據(jù) commit;
這時翻翩,在查詢,這次結果與上一次查詢結果又不一樣了,還存在不可重復讀体斩。
3.解決不可重復讀
將事務的隔離級別設置為Repeatable read來解決不可重復讀。
設置A,B事務隔離級別為 Repeatable read;
set session transaction isolation level Repeatable read;
1.在A事務中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事務中
start transaction;
select * from account;
當A事務提交后commit;B事務在查詢颖低,與上次查詢結果一致絮吵,解決了不可重復讀。
4.設置事務隔離級別Serializable ,它可以解決所有問題
set session transaction isolation level Serializable;
如果設置成這種隔離級別忱屑,那么會出現(xiàn)鎖表蹬敲。也就是說,一個事務在對表進行操作時莺戒,
其它事務操作不了伴嗡。
- 總結
臟讀:一個事務讀取到另一個事務為提交數(shù)據(jù)
不可重復讀:兩次讀取數(shù)據(jù)不一致(讀提交數(shù)據(jù))---update
虛讀:兩次讀取數(shù)據(jù)不一致(讀提交數(shù)據(jù))----insert
事務隔離級別:
read uncommitted 什么問題也解決不了.
read committed 可以解決臟讀,其它解決不了.
Repeatable read 可以解決臟讀从铲,可以解決不可重復讀,不能解決虛讀.
Serializable 它會鎖表瘪校,可以解決所有問題.
安全性:serializable > repeatable read > read committed > read uncommitted
性能 :serializable < repeatable read < read committed < read uncommitted
結論: 實際開發(fā)中,通常不會選擇 serializable 和 read uncommitted 名段,
mysql默認隔離級別 repeatable read 阱扬,oracle默認隔離級別 read committed
三、丟失更新
多個事務對同一條記錄進行了操作伸辟,后提交的事務將先提交的事務操作覆蓋了麻惶。
解決辦法:
- 悲觀鎖:(假設丟失更新一定會發(fā)生 ) ----- 利用數(shù)據(jù)庫內(nèi)部鎖機制,管理事務
提供的鎖機制
1.共享鎖
select * from table lock in share mode(讀鎖信夫、共享鎖)
2.排它鎖
select * from table for update (寫鎖窃蹋、排它鎖)
update語句默認添加排它鎖 - 樂觀鎖:(假設丟失更新不會發(fā)生) ----- 采用程序中添加版本字段解決丟失更新問題
解決丟失更新:在數(shù)據(jù)表添加版本字段,每次修改過記錄后静稻,版本字段都會更新警没,如果讀取是版本字段,與修改時版本字段不一致姊扔,說明別人進行修改過數(shù)據(jù) (重改)
四惠奸、連接池
就是創(chuàng)建一個容器,用于裝入多個Connection對象恰梢,在使用連接對象時佛南,從容器中獲取一個Connection,使用完成后嵌言,在將這個Connection重新裝入到容器中嗅回。這個容器就是連接池。(DataSource)也叫做數(shù)據(jù)源.
我們可以通過連接池獲取連接對象.
優(yōu)點:節(jié)省創(chuàng)建連接與釋放連接 性能消耗 ---- 連接池中連接起到復用的作用 摧茴,提高程序性能
自定義連接池
- 創(chuàng)建一個
MyDataSource
類绵载,在這個類中創(chuàng)建一個LinkedList<Connection>
private LinkedList<Connection> ll;
ll = new LinkedList<Connection>();
- 在其構造方法中初始化List集合,并向其中裝入5個Connection對象
for (int i = 0; i < 5; i++) {
Connection con = JdbcUtils.getConnection();
ll.add(con);
}
創(chuàng)建一個
public Connection getConnection()
從List集合中獲取一個連接對象返回.創(chuàng)建一個
public void readd(Connection)
這個方法是將使用完成后的Connection對象重新裝入到List集合中.
代碼問題
1.連接池的創(chuàng)建是有標準的.
在javax.sql包下定義了一個接口 DataSource
簡單說,所有的連接池必須實現(xiàn)javax.sql.DataSource接口娃豹,
我們的自定義連接池必須實現(xiàn)DataSource接口焚虱。
2.我們操作時,要使用標準懂版,怎樣可以讓 con.close()它不是銷毀鹃栽,而是將其重新裝入到連接池.
要解決這個問題,其本質(zhì)就是將Connection中的close()方法的行為改變躯畴。
怎樣可以改變一個方法的行為(對方法功能進行增強)
1.繼承
2.裝飾模式
1.裝飾類與被裝飾類要實現(xiàn)同一個接口或繼承同一個父類
2.在裝飾類中持有一個被裝飾類引用
3.對方法進行功能增強民鼓。
3.動態(tài)代理
可以對行為增強
Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);
結論:Connection對象如果是從連接池中獲取到的,那么它的close方法的行為已經(jīng)改變了蓬抄,不在是銷毀丰嘉,而是重新裝入到連接池。
方法增強
-
繼承增強(不好)
public class Demo1 { public static void main(String[] args) { Person1 p=new Student1(); p.eat(); } } class Person1 { public void eat(){ System.out.println("吃兩個饅頭"); } } class Student1 extends Person1 { public void eat(){ super.eat(); System.out.println("加兩個雞腿"); } }
裝飾模式(不好)
-
動態(tài)代理
import javax.sql.DataSource; public class MyDataSource implements DataSource { private LinkedList<Connection> ll; // 用于裝Connection對象的容器嚷缭。 public MyDataSource() throws SQLException { ll = new LinkedList<Connection>(); // 當創(chuàng)建MyDateSource對象時饮亏,會向ll中裝入5個Connection對象。 for (int i = 0; i < 5; i++) { Connection con = JdbcUtils.getConnection(); ll.add(con); } } public Connection getConnection() throws SQLException { if (ll.isEmpty()) { for (int i = 0; i < 3; i++) { Connection con = JdbcUtils.getConnection(); ll.add(con); } } final Connection con = ll.removeFirst(); Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), con.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("close".equals(method.getName())) { // 這代表是close方法阅爽,它要做的事情是將con對象重新裝入到集合中. ll.add(con); System.out.println("重新將連接對象裝入到集合中"); return null; } else { return method.invoke(con, args);// 其它方法執(zhí)行原來操作 } } }); return proxyCon; } }
五克滴、dbcp連接池(了解)
導入兩個jar包:commons-dbcp-1.4.jar
和commons-pool-1.5.6.jar
- 手動配置(手動編碼)
BasicDataSource bds = new BasicDataSource(); // 需要設置連接數(shù)據(jù)庫最基本四個條件 bds.setDriverClassName("com.mysql.jdbc.Driver"); bds.setUrl("jdbc:mysql:///day18"); bds.setUsername("root"); bds.setPassword("abc"); // 得到一個Connection Connection con = bds.getConnection();
示例:
```java
public class JdbcDemo{
public void test() throws Exception {
BasicDataSource bds = new BasicDataSource();
// 需要設置連接數(shù)據(jù)庫最基本四個條件
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql:///day18");
bds.setUsername("root");
bds.setPassword("123");
Connection con = bds.getConnection();
ResultSet rs = con.createStatement().executeQuery("select * from account");
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
rs.close();
con.close();
}
public static void main(String[] args) throws Exception {
JdbcDemo jd = new JdbcDemo();
jd.test();
}
}
```
- 自動配置(使用配置文件)
示例:Properties props = new Properties(); FileInputStream fis = new FileInputStream("D:\\java1110\\workspace\\day18_2\\src\\dbcp.properties"); props.load(fis); DataSource ds = BasicDataSourceFactory.createDataSource(props);
dbcp.properties
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///day18 username=root password=123
JdbcDemo.java
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; import java.sql.*; import javax.sql.DataSource; import java.io.FileInputStream; import java.util.Properties; public class JdbcDemo { public void test2() throws Exception { Properties props = new Properties(); // props.setProperty("driverClassName","com.mysql.jdbc.Driver"); // props.setProperty("url","jdbc:mysql:///day18"); // props.setProperty("username","root"); // props.setProperty("password","123"); FileInputStream fis = new FileInputStream("D:\\code\\java\\JDBC\\src\\dbcp.properties"); props.load(fis); DataSource ds = BasicDataSourceFactory.createDataSource(props); Connection con = ds.getConnection(); ResultSet rs = con.createStatement().executeQuery("select * from account"); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")); } rs.close(); con.close(); } public static void main(String[] args) throws SQLException { JdbcDemo jd = new JdbcDemo(); jd.test2(); } }
六、c3p0連接池(必須掌握)
C3P0是一個開源的JDBC連接池优床,它實現(xiàn)了數(shù)據(jù)源和JNDI綁定劝赔,支持JDBC3規(guī)范和JDBC2的標準擴展。目前使用它的開源項目有Hibernate胆敞,Spring等着帽。
dbcp沒有自動回收空閑連接的功能,c3p0有自動回收空閑連接功能移层,它的性能更強大仍翰。
導入包:c3p0-0.9.5.2.jar
- 手動
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("abc");
事例:
public void test() throws Exception {
BasicDataSource bds = new BasicDataSource();
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("123");
Connection con = cpds.getConnection();
ResultSet rs = con.createStatement().executeQuery("select * from account");
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
rs.close();
con.close();
}
-
自動(使用配置文件)
c3p0的配置文件可以是properties也可以是xml.
c3p0的配置文件如果名稱叫做c3p0.properties
orc3p0-config.xml
并且放置在classpath路徑下(對于web應用就是classes目錄),那么c3p0會自動查找观话。
注意:我們其時只需要將配置文件放置在src下就可以予借。使用:
ComboPooledDataSource cpds = new ComboPooledDataSource();
它會在指定的目錄下查找指定名稱的配置文件,并將其中內(nèi)容加載频蛔。c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///day18</property> <property name="user">root</property> <property name="password">123</property> </default-config> </c3p0-config>