Java Web(五) -- 事務 & 數據庫連接池 & DBUtiles
阿新 • • 發佈:2018-06-26
ML run count() lis into 銀行 定義數據 dao util
#事務&數據庫連接池&DBUtils ##事務 > Transaction 其實指的一組操作,裏面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麽都算失敗。 所有的數據都回歸到最初的狀態(回滾) * 為什麽要有事務? > 為了確保邏輯的成功。 例子: 銀行的轉賬。 ###使用命令行方式演示事務。 * 開啟事務 start transaction; * 提交或者回滾事務 commit; 提交事務, 數據將會寫到磁盤上的數據庫 rollback ; 數據回滾,回到最初的狀態。 1. 關閉自動提交功能。 ![icon](img/img01.png) 2. 演示事務 ![icon](img/img02.png) ###使用代碼方式演示事務 > 代碼裏面的事務,主要是針對連接來的。 > > > 1. 通過conn.setAutoCommit(false )來關閉自動提交的設置。 > 2. 提交事務 conn.commit(); > 3. 回滾事務 conn.rollback(); @Test public void testTransaction(){ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtil.getConn(); //連接,事務默認就是自動提交的。 關閉自動提交。 conn.setAutoCommit(false); String sql = "update account set money = money - ? where id = ?"; ps = conn.prepareStatement(sql); //扣錢, 扣ID為1 的100塊錢 ps.setInt(1, 100); ps.setInt(2, 1); ps.executeUpdate(); int a = 10 /0 ; //加錢, 給ID為2 加100塊錢 ps.setInt(1, -100); ps.setInt(2, 2); ps.executeUpdate(); //成功: 提交事務。 conn.commit(); } catch (SQLException e) { try { //事變: 回滾事務 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtil.release(conn, ps, rs); } } ###事務的特性 * 原子性 > 指的是 事務中包含的邏輯,不可分割。 * 一致性 > 指的是 事務執行前後。數據完整性 * 隔離性 > 指的是 事務在執行期間不應該受到其他事務的影響 * 持久性 > 指的是 事務執行成功,那麽數據應該持久保存到磁盤上。 ###事務的安全隱患 > 不考慮隔離級別設置,那麽會出現以下問題。 * 讀 > 臟讀 不可重讀讀 幻讀. * 臟讀 > 一個事務讀到另外一個事務還未提交的數據 * 不可重復讀 > 一個事務讀到了另外一個事務提交的數據 ,造成了前後兩次查詢結果不一致。 ####讀未提交 演示 1. 設置A窗口的隔離級別為 讀未提交 ![icon](img/img03.png) 2. 兩個窗口都分別開啟事務 ![icon](img/img04.png) * 寫 > 丟失更新 #### 讀已提交演示 1. 設置A窗口的隔離級別為 讀已提交 ![icon](img/img05.png) 2. A B 兩個窗口都開啟事務, 在B窗口執行更新操作。 ![icon](img/img06.png) 3. 在A窗口執行的查詢結果不一致。 一次是在B窗口提交事務之前,一次是在B窗口提交事務之後。 ![icon](img/img07.png) > 這個隔離級別能夠屏蔽 臟讀的現象, 但是引發了另一個問題 ,不可重復讀。 ###可串行化 > 如果有一個連接的隔離級別設置為了串行化 ,那麽誰先打開了事務, 誰就有了先執行的權利, 誰後打開事務,誰就只能得著,等前面的那個事務,提交或者回滾後,才能執行。 但是這種隔離級別一般比較少用。 容易造成性能上的問題。 效率比較低。 * 按效率劃分,從高到低 > 讀未提交 > 讀已提交 > 可重復讀 > 可串行化 * 按攔截程度 ,從高到底 > 可串行化 > 可重復讀 > 讀已提交 > 讀未提交 ##事務總結 ###需要掌握的 1. 在代碼裏面會使用事務 conn.setAutoCommit(false); conn.commit(); conn.rollback(); 2. 事務只是針對連接連接對象,如果再開一個連接對象,那麽那是默認的提交。 3. 事務是會自動提交的。 ###需要了解的 ####安全隱患 讀 臟讀 一個事務讀到了另一個事務未提交的數據 不可重復讀 一個事務讀到了另一個事務已提交的數據,造成前後兩次查詢結果不一致 幻讀 一個事務讀到了另一個事務insert的數據 ,造成前後查詢結果不一致 。 寫 丟失更新。 ####隔離級別 讀未提交 > 引發問題: 臟讀 讀已提交 > 解決: 臟讀 , 引發: 不可重復讀 可重復讀 > 解決: 臟讀 、 不可重復讀 , 未解決: 幻讀 可串行化 > 解決: 臟讀、 不可重復讀 、 幻讀。 mySql 默認的隔離級別是 可重復讀 Oracle 默認的隔離級別是 讀已提交 ###丟失更新 ![icon](img/img08.png) ###解決丟失更新 * 悲觀鎖 > 可以在查詢的時候,加入 for update ![icon](img/img09.png) * 樂觀鎖 > 要求程序員自己控制。 ![icon](img/img10.png) ##數據庫連接池 >1. 數據庫的連接對象創建工作,比較消耗性能。 >2.一開始現在內存中開辟一塊空間(集合) , 一開先往池子裏面放置 多個連接對象。 後面需要連接的話,直接從池子裏面去。不要去自己創建連接了。 使用完畢, 要記得歸還連接。確保連接對象能循環利用。 ![icon](img/img11.png) ###自定義數據庫連接池 * 代碼實現 * 出現的問題: 1. 需要額外記住 addBack方法 2. 單例。 3. 無法面向接口編程。 UserDao dao = new UserDaoImpl(); dao.insert(); DataSource dataSource = new MyDataSource(); 因為接口裏面沒有定義addBack方法。 4. 怎麽解決? 以addBack 為切入點。 ###解決自定義數據庫連接池出現的問題。 > 由於多了一個addBack 方法,所以使用這個連接池的地方,需要額外記住這個方法,並且還不能面向接口編程。 > 我們打算修改接口中的那個close方法。 原來的Connection對象的close方法,是真的關閉連接。 > 打算修改這個close方法,以後在調用close, 並不是真的關閉,而是歸還連接對象。 ###如何擴展某一個方法? > 原有的方法邏輯,不是我們想要的。 想修改自己的邏輯 1. 直接改源碼 無法實現。 2. 繼承, 必須得知道這個接口的具體實現是誰。 3. 使用裝飾者模式。 ##開源連接池 #### DBCP 1. 導入jar文件 2. 不使用配置文件: public void testDBCP01(){ Connection conn = null; PreparedStatement ps = null; try { //1. 構建數據源對象 BasicDataSource dataSource = new BasicDataSource(); //連的是什麽類型的數據庫, 訪問的是哪個數據庫 , 用戶名, 密碼。。 //jdbc:mysql://localhost/bank 主協議:子協議 ://本地/數據庫 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost/bank"); dataSource.setUsername("root"); dataSource.setPassword("root"); //2. 得到連接對象 conn = dataSource.getConnection(); String sql = "insert into account values(null , ? , ?)"; ps = conn.prepareStatement(sql); ps.setString(1, "admin"); ps.setInt(2, 1000); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtil.release(conn, ps); } } 2. 使用配置文件方式: Connection conn = null; PreparedStatement ps = null; try { BasicDataSourceFactory factory = new BasicDataSourceFactory(); Properties properties = new Properties(); InputStream is = new FileInputStream("src//dbcpconfig.properties"); properties.load(is); DataSource dataSource = factory.createDataSource(properties); //2. 得到連接對象 conn = dataSource.getConnection(); String sql = "insert into account values(null , ? , ?)"; ps = conn.prepareStatement(sql); ps.setString(1, "liangchaowei"); ps.setInt(2, 100); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtil.release(conn, ps); } * C3P0 > 拷貝jar文件 到 lib目錄 ###不使用配置文件方式 Connection conn = null; PreparedStatement ps = null; try { //1. 創建datasource ComboPooledDataSource dataSource = new ComboPooledDataSource(); //2. 設置連接數據的信息 dataSource.setDriverClass("com.mysql.jdbc.Driver"); //忘記了---> 去以前的代碼 ---> jdbc的文檔 dataSource.setJdbcUrl("jdbc:mysql://localhost/bank"); dataSource.setUser("root"); dataSource.setPassword("root"); //2. 得到連接對象 conn = dataSource.getConnection(); String sql = "insert into account values(null , ? , ?)"; ps = conn.prepareStatement(sql); ps.setString(1, "admi234n"); ps.setInt(2, 103200); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { JDBCUtil.release(conn, ps); } ###使用配置文件方式 //默認會找 xml 中的 default-config 分支。 ComboPooledDataSource dataSource = new ComboPooledDataSource(); //2. 設置連接數據的信息 dataSource.setDriverClass("com.mysql.jdbc.Driver"); //忘記了---> 去以前的代碼 ---> jdbc的文檔 dataSource.setJdbcUrl("jdbc:mysql://localhost/bank"); dataSource.setUser("root"); dataSource.setPassword("root"); //2. 得到連接對象 conn = dataSource.getConnection(); String sql = "insert into account values(null , ? , ?)"; ps = conn.prepareStatement(sql); ps.setString(1, "admi234n"); ps.setInt(2, 103200); ##DBUtils ###增刪改 //dbutils 只是幫我們簡化了CRUD 的代碼, 但是連接的創建以及獲取工作。 不在他的考慮範圍 QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource()); //增加 //queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000); //刪除 //queryRunner.update("delete from account where id = ?", 5); //更新 //queryRunner.update("update account set money = ? where id = ?", 10000000 , 6); ###查詢 1. 直接new接口的匿名實現類 QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource()); Account account = queryRunner.query("select * from account where id = ?", new ResultSetHandler<Account>(){ @Override public Account handle(ResultSet rs) throws SQLException { Account account = new Account(); while(rs.next()){ String name = rs.getString("name"); int money = rs.getInt("money"); account.setName(name); account.setMoney(money); } return account; } }, 6); System.out.println(account.toString()); 2. 直接使用框架已經寫好的實現類。 * 查詢單個對象 QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource()); //查詢單個對象 Account account = queryRunner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), 8); * 查詢多個對象 QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource()); List<Account> list = queryRunner.query("select * from account ", new BeanListHandler<Account>(Account.class)); ###ResultSetHandler 常用的實現類 以下兩個是使用頻率最高的 BeanHandler, 查詢到的單個數據封裝成一個對象 BeanListHandler, 查詢到的多個數據封裝 成一個List<對象> ------------------------------------------ ArrayHandler, 查詢到的單個數據封裝成一個數組 ArrayListHandler, 查詢到的多個數據封裝成一個集合 ,集合裏面的元素是數組。 MapHandler, 查詢到的單個數據封裝成一個map MapListHandler,查詢到的多個數據封裝成一個集合 ,集合裏面的元素是map。 ColumnListHandler KeyedHandler ScalarHandler #總結 ##事務 使用命令行演示 使用代碼演示 臟讀、 不可重復讀、 幻讀 丟失更新 悲觀鎖 樂觀鎖 4個隔離級別 讀未提交 讀已提交 可重復讀 可串行化 ##數據連接池 * DBCP 不使用配置 使用配置 * C3P0 不使用配置 使用配置 (必須掌握) * 自定義連接池 裝飾者模式 ##DBUtils > 簡化了我們的CRUD , 裏面定義了通用的CRUD方法。 queryRunner.update(); queryRunner.query
Java Web(五) -- 事務 & 數據庫連接池 & DBUtiles