事務{
定義:指邏輯上的一組操作,組成這組操作的各個單元,要麼全部成功,要麼全部不成功。
數據開啓事務的命令:
start transaction——開啓事務
roolback——回滾事務
commit——提交事務
如果開啓事務,在提交之前出現了異常,就會回滾事務,數據庫中的數據就會回滾到開啓事務之前的狀態
如果開啓了事務,但是沒有提交,數據庫的數據就不會改變。
jdbc控制事務{
}Connection.setAutoCommit(false);
Connection.rollback();
Connection.commit();
例1:
Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { // 獲取連接 conn = JdbcUtils.getConnection(); // 定義sql語句,aaa用戶的錢減100,bbb用戶的錢加100 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; // 開啓事務 conn.setAutoCommit(false); // 預編譯sql,執行sql st = conn.prepareStatement(sql1); st.executeUpdate(); int x = 1/0;//這會拋一個異常,這樣該程序就無法執行commit語句,自然就無法將數據庫中的數據更新。數據還是開啓事務之前的數據。如果沒有這行代碼,程序會正常執行,執行提交方法,修改數據庫中的數據 st = conn.prepareStatement(sql2); st.executeUpdate(); // 提交事務 conn.commit(); }finally{ JdbcUtils.release(conn, st, rs); }
例2:(使用到回滾方法)Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try { // 獲取連接 conn = JdbcUtils.getConnection(); // 定義sql語句,aaa用戶的錢減100,bbb用戶的錢加100 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; // 開啓事務 conn.setAutoCommit(false); // 預編譯sql,執行sql st = conn.prepareStatement(sql1); st.executeUpdate(); // 如果想讓第一條sql語句正常執行就需要定義一個回滾點 sp = conn.setSavepoint(); int x = 1/0; st = conn.prepareStatement(sql2); st.executeUpdate(); // 提交事務 conn.commit(); }catch (Exception e) { e.printStackTrace(); // 進行事務回滾操作,即將數據恢復到回滾點出的數據,就是指執行了第一條sql語句的數據 conn.rollback(sp); // 手動回滾之後一定要提交數據,否則數據庫會再次進行回滾到開啓事務之前 conn.commit(); }finally{ JdbcUtils.release(conn, st, rs); }
事務的特性(ACID){一個數據庫支持事務就必須具有下列特性,如果數據庫具有下列特性,就說明該數據庫支持事務
原子性(Atomicity):指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生
一致性(Consistency):指事務前後數據的完整性必須保持一直。即轉賬事務開啓前,總錢爲1000,轉賬後總錢還應該爲1000,類似物理中的能量守恆
隔離性(Isolation):指多個用戶併發訪問數據庫時,一個用戶的事務不能被其他用戶的事務干擾,多個併發事務之前的數據要相互隔離。意思就是:A/B用戶都在操作數據庫,A用戶修改了數據,B用戶不能使用A用戶修改後的數據持久性(Durability):指一個事務一旦被提交,它對數據庫的改變就是永久性的,接下來就算是數據庫發生了故障也不能對這個事務修改的數據產生影響。
}事務的隔離級別{
多個事務開啓各自的事務操作數據庫時,數據庫系統就要負責進行隔離操作,保證各個線程在獲取數據的準確性;如果不考慮隔離性,會發生以下問題:
髒讀:指一個事務讀取了另外一個事務未提交的數據。
例:假設A向B轉賬:1.update account money=money+100 where name='b'; 2.update account money=money-100 where name='a';當第一個sql執行,第二個sql還未執行(A未提交事務),如果此時B查詢自己的賬戶就會發現自己多了100元,這個時候A回滾數據,B就會損失100元。
不可重複度:在一個事務內讀取表中的某一行數據,多次讀取結果不同(一個事務讀取到了另一個事務提交的數據)
例:A用戶先查詢賬戶,B用戶往A用戶轉賬100元,並且提交了數據。A用戶又查一次賬戶,發現多了100元。
幻讀:指一個事務內讀取到了別的事務插入的數據,導致前後讀取不一致(查表,髒讀是查某一行數據)
例:一個用戶存錢,但沒提交,銀行查表發現總共500元,用戶提交之後,銀行在查表發現多了100元。
}數據庫定義的隔離級別(不同的連接對應不同的隔離級別){
Serializable:可避免髒讀,不可重複讀,幻讀
Repeatable read:可避免髒讀,不可重複讀
Read committed:可避免髒讀的發生
Read uncommitted:最低級別,無法避免數據庫的命令:
set transaction isolation level + 隔離級別;——設置事務的隔離級別select @@tx_isolation;——查詢當前事務的隔離級別
jdbc的使用:
// 獲取連接 conn = JdbcUtils.getConnection(); // 設置連接的隔離級別 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
}
ThreadLocal{
1.在java程序中,爲保證事務的準確性,可以使用ThreadLocal
2.這其實是一個存儲連接的容器
3.get,set,remove等方法都是操作當前使用的線程的連接,即一個線程都是用同一個連接
4.使用完之後要注意remove連接,否則造成該對象存放太多連接,出現問題
private static DataSource ds = null; static{ try { Properties prop = new Properties(); prop.load(JdbcUtils.class.getClassLoader().getResourceAsStream("dbcp.properties")); ds = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new RuntimeException(e); } } // 提供一個ThreadLocal對象,由於需要在靜態方法內使用,定義爲靜態 private static ThreadLocal<Connection> t = new ThreadLocal<Connection>(); public static Connection getConnection(){ try { // 先從容器中獲取連接,如果不存在就通過數據源獲取連接並存在容器中,如果存在就返回容器中的連接 Connection conn = t.get(); if(conn == null){ conn = ds.getConnection(); t.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } public static void startTransaction(){ // 開啓事務,先從容器獲取連接,沒有就創建並存在容器中,然後開啓事務 try { Connection conn = t.get(); if(conn == null){ conn = getConnection(); t.set(conn); } conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } public static void commit(){ // 提交事務,從容器中獲取連接,獲取到了就提交,沒獲取說明容器沒連接,就不需要提交 try { Connection conn = t.get(); if(conn != null){ conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } public static void close(){ // 提供關閉連接的方法,使用完連接之後,必須要關閉連接,同時不管任何情況要保證容器內移除了該鏈接 try { Connection conn = t.get(); if(conn != null){ conn.close(); } } catch (Exception e) { throw new RuntimeException(e); }finally{ t.remove(); } }
}
}
連接池{
通過應用程序獲取連接的缺點:用戶的每次請求都需要向數據庫獲得連接,而數據庫創建連接需要消耗相對比較大的資源,創建的時間也就較長。假設網站一天的訪問量10萬次,那就需要數據庫創建10萬次連接,極大的浪費了數據庫資源,並且極易造成數據庫內存溢出,宕機。
定義:就是寫一個連接池程序,裏面保存了幾個連接,當用戶需要連接的時候直接從連接池中獲取,這樣就代替了數據庫直接創建連接,大大優化了程序性能。
編寫連接池{
步驟:
1.實現java.sql.DataSource接口,實現它內部的兩個抽象方法
2.在構造函數中創建數據庫的連接,並存放在容器對象中
3.在方法中提供獲取容器中連接的方法
4.當用戶使用完連接,進行close()連接的時候,該連接會返回給容器對象
// 先要提供容器對象 private static LinkedList<Connection> list = new LinkedList<Connection>(); // 將連接存放在容器中,爲保證每次都從一個地方獲取連接,可是使用靜態代碼塊 static{ try { // 可以將配置信息放在properties文件中 Class.forName("com.mysql.jdbc.Driver"); for (int i = 0; i < 10; i++) { Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root"); list.add(conn); } } catch (Exception e) { // 獲取連接配置異常 throw new ExceptionInInitializerError(e); } } @Override public Connection getConnection() throws SQLException { // 判斷容器中是否有連接 if(list.size()<=0){ throw new RuntimeException("數據庫內無連接"); } // 不能使用list.get(index)方法,因爲該方法只是取出一個引用,在數據庫中並不會移除 // 使用移除第一個的方法,因爲這是鏈表,移除第一個之後,第二個就會前移到第一個 Connection conn = list.removeFirst(); MyConnection my = new MyConnection(conn); return my;//此時調用的都是增強了close方法的connection對象。 } // 爲保證用戶使用完連接,調用close的方法的時候會返回給連接池,就需要增強close方法: // 可以使用1.寫一個connection的子類,覆蓋close的方法,然後增強----較少使用 // 2.使用包裝設計模式----需要填寫過多方法 // 3.用動態代理----最好,aop編程 // 因爲第一種方法,需要將connection的信息全部封裝到子類中,否則無法實現操作。 // 比如某個方法需要使用連接的信息才能實現,但是子類無法實現該信息,所以無法執行 // 過於麻煩,所以使用包裝模式: // 1.實現與需增強的類相同的接口 // 2.將需要增強的對象傳到增強類中 // 3.填寫增強的方法 // 4.不需要增強的方法,通過調用傳遞進來的對象的相應方法 class MyConnection implements Connection{ private Connection conn = null; public MyConnection(Connection conn) { super(); this.conn = conn; } @Override public void close() throws SQLException { list.add(this.conn); } @Override public void clearWarnings() throws SQLException { conn.clearWarnings(); } ... }
}
開源連接池{
DBCP數據庫連接池{
定義:Apache軟件基金組織下的開源連接池實現,使用DBCP連接池,需要在應用程序導入兩個庫:Commons-dbcp.jar(連接池的實現)和Commons-pool.jar(連接池依賴的數據庫)。
1.其實就是開源組織編寫了一個dbcp的數據源,通過該數據庫源可以獲取連接
2.Tomcat的連接池正是採用該連接池實現的
3.該數據庫連接池既可以與應用服務器整合使用,也可以由應用程序獨立使用
使用{
根據dbcp.properties文件來獲取配置信息:
#連接設置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb username=root password=root #<!-- 初始化連接 --> dataSource.initialSize=10 #<!-- 最大空閒連接 --> dataSource.maxIdle=20 #<!-- 最小空閒連接 --> dataSource.minIdle=5 #最大連接數量 dataSource.maxActive=50 #是否在自動回收超時連接的時候打印連接的超時錯誤 dataSource.logAbandoned=true #是否自動回收超時連接 dataSource.removeAbandoned=true #超時時間(以秒數爲單位) #設置超時時間有一個要注意的地方,超時時間=現在的時間-程序中創建Connection的時間,如果maxActive比較大,比如超過100,那麼removeAbandonedTimeout可以設置長一點比如180,也就是三分鐘無響應的連接進行回收,當然應用的不同設置長度也不同。 dataSource.removeAbandonedTimeout=180 #<!-- 超時等待時間以毫秒爲單位 --> #maxWait代表當Connection用盡了,多久之後進行回收丟失連接 dataSource.maxWait=1000
獲取連接:
// 先將配置文件加載到定義的properties對象中 private static Properties prop = new Properties(); // 定義一個數據源對象,接收創建的dbcp數據源 private static DataSource ds = null; // 由於只需要創建一次dbcp的數據源對象即可,放在靜態代碼塊 static{ //加載配置文件 try { prop.load(OpenPoolDemo.class.getClassLoader().getResourceAsStream("dbcp.properties")); // 獲取dbcp的數據源,定義者要求通過工廠模式獲取 // 通過prop對象獲得數據源 // BasicDataSourceFactory factory = new BasicDataSourceFactory(); // ds = factory.createDataSource(prop); ds = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 獲取連接的方法 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
}C3P0數據庫連接池{
1.Spring內置的連接池
2.使用mysql數據庫需要導入兩個庫:c3p0-0.9.2.1.jar和mchange-commons-java-0.2.3.4.jar
使用{
手動定義配置信息:
private static ComboPooledDataSource ds = null; // 獲取數據源對象,只需要加載一次放在靜態代碼塊/ static{ try { //直接創建數據源對象 ds = new ComboPooledDataSource(); //爲ds註冊獲取數據庫連接的配置信息 //1.驅動類 ds.setDriverClass("com.mysql.jdbc.Driver"); //2.要連接的庫的位置url ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb"); //3.庫的用戶名 ds.setUser("root"); //4.庫的密碼 ds.setPassword("root"); //5.最大允許多少連接 ds.setMaxPoolSize(30); //6.最小允許多少連接 ds.setMinPoolSize(5); //7.初始化連接的個數 ds.setInitialPoolSize(10); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 通過數據源獲取連接 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
使用配置文件:
1.文件名必須爲c3p0-config.xml
2.必須放在應用的classpath文件下,或WEB-INF/classes文件下(查看c3p0文檔的快速入門可知)
配置文件:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/my</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <!-- This app is massive! --> <!-- name就是這個配置的名字,調用的時候根據名字獲取配置信息 --> <named-config name="halm"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/my</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
獲取配置文件信息創建連接:private static ComboPooledDataSource ds = null; // 獲取數據源對象,只需要加載一次放在靜態代碼塊/ static{ try { // 調用默認的配置信息 ds = new ComboPooledDataSource(); // 調用名字爲halm的配置信息 // ds = new ComboPooledDataSource("halm"); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } // 通過數據源獲取連接 public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
}
}
}
配置Tomcat數據源,直接從Tomcat中獲取連接池{
需要先配置連接池信息(在web應用的配置文件中配置):驅動的.jar文件必須放在服務器的lib下,可以通過Tomcat的文檔得知或者(點擊打開鏈接)
說明:<context> <Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" maxActive="100" maxIdle="30" maxWait="10000"/> </context>
1.在啓動web服務器會創建一個jndi的容器,內部存儲着配置的數據源,並且綁定在一個指定的名稱上“jdbc/mydb”
2.在程序中就是根據名稱來從jndi找到該數據源
}
}
PS:今天參加第一次面試了,以後再將過程貼出來吧