1-事務介紹與簡單使用
事務 - Transaction
其實指的一組操作,裏面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麼都算失敗,則所有的數據都回歸到最初的狀態(回滾)
爲什麼要有事務?
爲了確保邏輯的成功 例子:銀行的轉賬
開啓事務命令:
start transaction;
提交或者回滾事務命令:
commit; 提交事務,數據將會寫到磁盤上的數據庫
rollback; 數據回滾,回到最初的狀態
自動提交命令:
set autocommit = off;
autocommit會降低運行效率並且容易發生數據錯誤,若異常則後續代碼無法執行。關閉autocommit後,若要使事務執行則commit,否則就回滾rollback
設置隔離級別命令:
set session transaction isolation level (級別);
事務在Java中的簡單使用:
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);
}
}
注意:若不使用事務(autocommit開着),則執行事務邏輯時某處出現的異常可能導致整個數據出錯
2-事務 特性(ACID)和安全隱患
- 原子性
> 指的是 事務中包含的邏輯,不可分割 - 一致性
> 指的是 事務執行前後.數據完整性 - 隔離性
> 指的是 事務在執行期間不應該受到其他事務的影響 - 持久性
> 指的是 事務執行成功,那麼數據應該持久保存到磁盤上
不考慮隔離級別設置,那麼會出現以下問題
-
髒讀
> 一個事務讀到另外一個事務還未提交的數據
-
不可重複讀
> 一個事務讀到了另外一個事務提交的數據,造成了前後兩次查詢結果不一致 -
幻讀
> 一個事務讀到了另一個事務已提交的插入的數據,導致多次查詢結果不一致
事務的可串行化:
如果有一個連接的隔離級別設置爲了串行化,那麼誰先打開了事務,誰就有了先執行的權利,誰後打開事務,誰就只能得着,等前面的那個事務,提交或者回滾後,才能執行
- 按效率劃分,從高到低
> 讀未提交 > 讀已提交 > 可重複讀 > 可串行化 - 按攔截程度,從高到底
> 可串行化 > 可重複讀 > 讀已提交 > 讀未提交
事務的丟失更新現象:
悲觀鎖和樂觀鎖:
1、可以在查詢的時候加入for update,表示悲觀鎖
2、樂觀鎖要求程序員自己規定標識
事務小結:
1、需要掌握在Java中使用事務
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
2、需要熟悉瞭解事務的安全隱患和隔離級別
3、mySql默認的隔離級別是可重複讀。Oracle是讀已提交
4、需要會在數據出現異常時學會排錯
3-數據庫連接池介紹與簡單使用
前提引入:如果每個程序連接數據庫都要創建一個數據庫對象,那麼會比較消耗性能。所以我們一開始直接在內存中使用一塊集合空間存放多個連接對象,如果有程序需要連接對象則給一個,使用完畢之後回收,這樣的話不僅能夠循環利用而且還能夠提升效率。
數據庫連接池的搭建分析:
1、先創建X個連接
2、要用的程序通過getConnection來獲取連接
3、用完之後,使用addBack來歸還連接
4、如果連接池中不夠了,則擴容
5、list.remove(0)移出list中的第一個,移出後第二個默認到第一個,還是remove(0)
數據庫連接池的簡單搭建:
這裏暫時創建一個(JDBCUtil)對象類,用於聲明連接對象。接着自己手動創建一個連接池對象,具體的主要代碼如下↓
List<Connection> connList = new ArrayList<Connection>();
public List<Connection> getConnList() {
return connList;
}
public void setConnList(List<Connection> connList) {
this.connList = connList;
}
public MyDataSource() {
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil_old.getConn();
connList.add(conn);
}
}
//目前使用到的方法只有getConnection
@Override
public Connection getConnection() throws SQLException {
if(connList.size()==0){
for (int i = 0; i < 5; i++) {//一次加5個
Connection conn = JDBCUtil_old.getConn();
connList.add(conn);
}
}
Connection conn = new ConnectionWrap(connList.remove(0),this.connList);//刪除並返回Connection
System.out.println("分配一個Connection之後的list大小:"+connList.size());
return conn;
}
//使用完畢之後,利用addBack方法,回收連接
public void addBack(Connection conn){
connList.add(conn);
System.out.println("還原一個Connection之後的list大小:"+connList.size());
}
自定義數據庫連接池的問題:
1、需要額外記住addBack方法
2、單例模式的使用,創建多個數據庫連接池,佔用內存高
3、由於addBack方法接口中沒有,無法面向接口編程
解決自定義數據庫連接池問題的重點在於把addBack換成接口已有的方法:
1、使用裝飾者模式
2、使用動態代理
4-數據庫連接池 裝飾者模式
裝飾者模式改進自定義數據庫連接池主要代碼:
Connection conn = null;
List<Connection> connList = null;
public ConnectionWrap(Connection conn,List<Connection> connList) {
this.conn = conn;
this.connList = connList;
}
@Override
public void close() throws SQLException {
connList.add(conn);
System.out.println("還原一個Connection之後的list大小:"+connList.size());
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
conn.setAutoCommit(autoCommit);
}
@Override
public void commit() throws SQLException {
conn.commit();
}
@Override
public void rollback() throws SQLException {
conn.rollback();
}
5-數據庫連接池DBCP
DBCP連接池的apache上的一個Java連接池項目。
1、使用直連的方式獲取連接:
//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();
2、通過配置文件、工廠獲取連接:
首先導入兩個jar包(一個pool,一個dbcp),一個properties文件
其次建立工廠,通過工廠來獲取連接,代碼如下:
BasicDataSourceFactory bdsf = new BasicDataSourceFactory();
//bdsf不能生成自定義的DataSource
//MyDataSource ds = null;
DataSource ds = null;
Connection conn = null;
InputStream in = null;
Properties pro = null;
try {
pro = new Properties();
in = new FileInputStream("src/dbcpconfig.properties");
pro.load(in);
ds = bdsf.createDataSource(pro);
conn = ds.getConnection();
}
6-數據庫連接池C3P0
C3P0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。
1、使用直連的方式獲取連接:
//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");
//3. 得到連接對象
conn = dataSource.getConnection();
2、通過配置文件獲取連接:
ComboPooledDataSource ds = new ComboPooledDataSource(); //後有參數可以選擇數據庫類型
Connection conn = null;
conn = ds.getConnection();
//配置文件
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/bank?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">qq</property>
<property name="automaticTestTable">con_test</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</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>
數據庫連接池總結:
1、配置文件中的url如果就寫到目標數據庫名則會報錯,原因是沒有規定使用的解碼器
2、配置文件中寫了解碼器的代碼但沒有使用轉義字符,會報錯"characterEncoding" must end with the ‘;’ delimiter;原因是xml不能解析’&’,所以把‘&’改成"& ;" 即可
3、Tried to check-in a foreign resource! - 線程無法正常運行,不要手動關閉ds
4、ComboPooledDataSource源代碼中自動會幫助獲取連接信息,所以要寫好配置文件
7-DBUtils以及Handler
利用DBUtils進行增刪改查操作,這裏主要利用到QueryRunner對象
//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);
這裏要單獨把查找功能寫出來,因爲查找的時候有利用到一個Handler集合對象
1、我們直接new接口的匿名實現類(ResultSetHandler)
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());
//ResultSetHandler爲最大的接口,所以要使用必須自行定義接口方法,所以@Override
2、直接使用已經寫好的實現類(BeanListHandler)
ComboPooledDataSource ds = new ComboPooledDataSource(); //後有參數可以選擇數據庫類型
QueryRunner eq = new QueryRunner(ds);
try {
//查找
List<Account> accounts = eq.query("select * from account", new BeanListHandler<Account>(Account.class));
for (Account account : accounts) {
String name = account.getName();
int money = account.getMoney();
System.out.println("Account:"+name+"-"+money);
}
} catch (SQLException e) {
e.printStackTrace();
}
DBUtils與Handler總結:
ResultSetHandler 常用的實現類
以下是使用頻率從高往低排序結果:
------------------------------------------✈
BeanHandler, 查詢到的單個數據封裝成一個對象
BeanListHandler, 查詢到的多個數據封裝 成一個List<對象>
------------------------------------------🚑
ArrayHandler, 查詢到的單個數據封裝成一個數組
ArrayListHandler, 查詢到的多個數據封裝成一個集合 ,集合裏面的元素是數組。
------------------------------------------⛵
MapHandler, 查詢到的單個數據封裝成一個map
MapListHandler,查詢到的多個數據封裝成一個集合 ,集合裏面的元素是map。
------------------------------------------🚲
ColumnListHandler
KeyedHandler
ScalarHandler
HPF-自我總結
這部分的重點就是要學會使用事務,並且會解決關於數據庫連接出現的問題。
其次,我個人用C3P0用得多,學到目前階段感覺C3P0是真的很方便,要好好掌握。
最後,熟練使用BeanListHandler,這個框架很好用
——— 給自己一個恰如其分的自信。