事務(wù)
Transaction 其實(shí)指的一組操作,里面包含許多個單一的邏輯馁痴。只要有一個邏輯沒有執(zhí)行成功翼岁,那么都算失敗开财。 所有的數(shù)據(jù)都回歸到最初的狀態(tài)(回滾)
事務(wù)的作用:為了確保邏輯的成功。 例子: 銀行的轉(zhuǎn)賬惹悄。
-
使用命令行方式演示事務(wù)春叫。
-
關(guān)閉自動提交功能。
關(guān)閉自動提交 -
開啟事務(wù)
start transaction;
開啟事務(wù) -
提交或者回滾事務(wù):事務(wù)提交后或者回滾就結(jié)束了
-
commit; 提交事務(wù)泣港, 數(shù)據(jù)將會寫到磁盤上的數(shù)據(jù)庫
commit -
rollback ; 數(shù)據(jù)回滾暂殖,回到最初的狀態(tài)
rollback
-
-
使用代碼方式演示事務(wù)
public void testTransaction() throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConn();
String sql = "update bank set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
// 關(guān)閉提交 事務(wù)只是針對連接連接對象,如果再開一個連接對象当纱,那么還是默認(rèn)的提交呛每。
conn.setAutoCommit(false);
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
int a = 10 / 0;
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
// 兩段代碼都執(zhí)行成功 提交事務(wù)
conn.commit();
} catch (SQLException e) {
// 出現(xiàn)異常,回滾事務(wù)
conn.rollback();
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}
- 事務(wù)的特性
- 原子性:指的是 事務(wù)中包含的邏輯坡氯,不可分割晨横。
- 一致性:指的是 事務(wù)執(zhí)行前后。數(shù)據(jù)完整性
- 隔離性:指的是 事務(wù)在執(zhí)行期間不應(yīng)該受到其他事務(wù)的影響
- 持久性:指的是 事務(wù)執(zhí)行成功箫柳,那么數(shù)據(jù)應(yīng)該持久保存到磁盤上手形。
- 事務(wù)的安全隱患 :不考慮隔離級別設(shè)置,那么會出現(xiàn)以下問題
- 讀
- 臟讀:一個事務(wù)讀到另外一個事務(wù)還未提交的數(shù)據(jù)
- 不可重復(fù)讀 :一個事務(wù)讀到了另外一個事務(wù)提交的數(shù)據(jù) 悯恍,造成了前后兩次查詢結(jié)果不一致库糠。
- 幻讀:一個事務(wù)讀到了另一個事務(wù)insert的數(shù)據(jù) ,造成前后查詢結(jié)果不一致 涮毫。
-
寫:丟失更新
丟失更新-
悲觀鎖(認(rèn)為一定會丟失更新):可以在查詢的時候瞬欧,加入 for update(數(shù)據(jù)庫鎖機(jī)制贷屎,排他鎖),有點(diǎn)類似序列化
悲觀鎖 -
樂觀鎖(認(rèn)為一定不會丟失更新):要求程序員自己控制艘虎。
樂觀鎖
-
- 讀
- 事務(wù)的隔離級別:mySql 默認(rèn)的隔離級別是 可重復(fù)讀唉侄,Oracle 默認(rèn)的隔離級別是 讀已提交
- 讀未提交(Read Uncommitted):可以讀到其他事務(wù)未提交的數(shù)據(jù)。引發(fā)問題: 臟讀
- 讀已提交(Read Committed):只能讀到已提交的數(shù)據(jù)顷帖。解決: 臟讀 美旧, 引發(fā): 不可重復(fù)讀
- 可重復(fù)讀(Repeatable Read):事務(wù)中讀取的數(shù)據(jù)不受其他事務(wù)提交數(shù)據(jù)的影響,前后讀取的數(shù)據(jù)一致贬墩。解決: 臟讀 榴嗅、 不可重復(fù)讀 , 未解決: 幻讀
- 可串行化(Serializable) :如果有一個連接的隔離級別設(shè)置為了串行化 陶舞,那么誰先打開了事務(wù)嗽测, 誰就有了先執(zhí)行的權(quán)利, 誰后打開事務(wù)肿孵,誰就只能得著唠粥,等前面的那個事務(wù),提交或者回滾后停做,才能執(zhí)行晤愧。 但是這種隔離級別一般比較少用。 容易造成性能上的問題蛉腌。 效率比較低官份。解決: 臟讀、 不可重復(fù)讀 烙丛、 幻讀舅巷。
- 按效率劃分,從高到低
讀未提交 > 讀已提交 > 可重復(fù)讀 > 可串行化
- 按攔截程度 河咽,從高到底
可串行化 > 可重復(fù)讀 > 讀已提交 > 讀未提交
數(shù)據(jù)庫連接池
- 數(shù)據(jù)庫的連接對象創(chuàng)建工作钠右,比較消耗性能。 一開始先在內(nèi)存中開辟一塊空間(集合) 忘蟹, 先往池子里面放置 多個連接對象飒房。 后面需要連接的話,直接從池子里面去寒瓦。不要去自己創(chuàng)建連接了情屹。 使用完畢, 要記得歸還連接杂腰,確保連接對象能循環(huán)利用垃你。Sun公司定義了一個連接池接口
DataSource
- 自定義連接池
- 連接池類
** * 實(shí)現(xiàn)連接池,一開始在連接池中有十個連接對象 */ public class MyDataSource implements DataSource { List<Connection> list = new ArrayList<>(); // 連接池集合 public MyDataSource() { // 構(gòu)造函數(shù) 在連接池中初始化十個連接對象 for (int i = 0; i < 10; i++) { list.add(JDBCUtil.getConn()); } } /** * 該連接池對外公布獲取連接池的方法 * @return * @throws SQLException */ @Override public Connection getConnection() throws SQLException { if(list.size() == 0 ) { // 連接池中沒有連接對象 擴(kuò)容 for (int i = 0; i < 5; i++) { list.add(JDBCUtil.getConn()); } } // 移除連接池中第一個連接對象,包裝過后并將它返回 Connection conn = list.remove(0); Connection connWrap = new ConnectionWrap(conn, list); return connWrap; }
- 使用裝飾者模式解決連接池的歸還問題惜颇,符合面向接口編程
public class ConnectionWrap implements Connection { Connection conn = null; List<Connection> list = null; public ConnectionWrap(Connection conn, List<Connection> list) { super(); this.conn = conn; this.list = list; } @Override public void close() throws SQLException { // 在這里寫歸還操作 System.out.println("歸還前" + list.size()); list.add(conn); System.out.println("歸還后" + list.size()); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); }
- 連接池的使用
@Test public void testPool() throws SQLException { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; MyDataSource dataSource = new MyDataSource(); try { // 其實(shí)在這里得到的是 ConnectionWrap 對象 connection = dataSource.getConnection(); String sql = "select * from bank"; ps = connection.prepareStatement(sql); rs = ps.executeQuery(); while(rs.next()) { String name = rs.getString("name"); int id = rs.getInt("id"); int money = rs.getInt("money"); System.out.println(id + "---" + name + "---" + money); } } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtil.release(connection, ps, rs); } }
- 開源連接池
- DBCP
- 導(dǎo)入jar包
- 不使用配置文件
public class DBCPDemo { @Test public void testDBCP01() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1. 得到連接池對象 BasicDataSource dataSource = new BasicDataSource(); // 2. 設(shè)置連接屬性 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3307/hgzdata"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 3. 獲取連接對象 conn = dataSource.getConnection(); // 4. 數(shù)據(jù)庫操作 String sql = "select * from bank"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while(rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); int money = rs.getInt("money"); System.out.println(id + "---" + name + "---" + money); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtil.release(conn, ps, rs); } } }
- 使用配置文件 將配置文件
dbcpconfig.properties
放置在 src 目錄下
BasicDataSourceFactory factory = new BasicDataSourceFactory(); Properties properties = new Properties(); InputStream is = new FileInputStream("src/dbcpconfig.properties"); properties.load(is); DataSource dataSource = factory.createDataSource(properties);
- C3P0
- 導(dǎo)入 jar 包
- 不使用配置文件
// 1. 得到連接池對象 ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 2. 設(shè)置連接屬性 dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3307/hgzdata"); dataSource.setUser("root"); dataSource.setPassword("root");
- 使用配置文件 將配置文件
c3p0-config.xml
放在 src 目錄下
// 1. 得到連接池對象 默認(rèn)讀取配置文件 獲取連接信息 ComboPooledDataSource dataSource = new ComboPooledDataSource("configname");
- DBCP
DBUtils
- dbutils 只是幫我們簡化了CRUD 的代碼皆刺, 但是連接的創(chuàng)建以及獲取工作。 不在他的考慮范圍凌摄,導(dǎo)入 jar 包
-
update
操作
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);
-
query
操作new接口的匿名實(shí)現(xiàn)類
public void queryTest() {
// 獲取查詢對象
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
try {
User user = queryRunner.query("select * from bank where id = ?", new ResultSetHandler<User>() {
@Override
public User handle(ResultSet resultSet) throws SQLException {
User user = new User();
while (resultSet.next()) {
user.setName(resultSet.getString("name"));
user.setMoney(resultSet.getInt("money"));
}
return user;
}
}, 1);
System.out.println(user.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
-
query
操作 使用框架的 接口實(shí)現(xiàn)類 查詢一行數(shù)據(jù)
public void queryTest1() {
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查詢單行數(shù)據(jù)
try {
User user = queryRunner.query("select * from bank where id = ?",
new BeanHandler<User>(User.class)
, 2);
System.out.println(user.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
-
query
操作 使用框架的 接口實(shí)現(xiàn)類 查詢多行數(shù)據(jù)
public void queryTest2() {
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查詢單行數(shù)據(jù)
try {
List<User> users = queryRunner.query("select * from bank",
new BeanListHandler<User>(User.class));
for (User user: users
) {
System.out.println(user.toString());
}
} catch (SQLException e) {
e.printStackTrace();
}
}
-
ResultSetHandler
(queryRunner.query()
第二個參數(shù)) 常用的實(shí)現(xiàn)類- 最常用
BeanHandler, 查詢到的單個數(shù)據(jù)封裝成一個對象 BeanListHandler, 查詢到的多個數(shù)據(jù)封裝 成一個List<對象>
ArrayHandler, 查詢到的單個數(shù)據(jù)封裝成一個數(shù)組 ArrayListHandler, 查詢到的多個數(shù)據(jù)封裝成一個集合 羡蛾,集合里面的元素是數(shù)組。
MapHandler, 查詢到的單個數(shù)據(jù)封裝成一個map MapListHandler,查詢到的多個數(shù)據(jù)封裝成一個集合 锨亏,集合里面的元素是map痴怨。
- 不常用
ColumnListHandler KeyedHandler ScalarHandler
- 模擬DBUtils功能代碼
- update
public void update(String sql, Object ...args) { Connection conn = null; PreparedStatement ps = null; try { ComboPooledDataSource dataSource = new ComboPooledDataSource(); conn = dataSource.getConnection(); ps = conn.prepareStatement(sql); // 根據(jù)元數(shù)據(jù)參數(shù) 獲取問號個數(shù) ParameterMetaData parameterMetaData = ps.getParameterMetaData(); int paramerterCount = parameterMetaData.getParameterCount(); for (int i = 0; i < paramerterCount ; i++) { ps.setObject(i + 1, args[i]); } ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.release(conn,ps); } }
- query
結(jié)果集處理接口public <T> T query(String sql,ResultHandler<T> handler ,Object ...args) { Connection conn = null; PreparedStatement ps= null; ResultSet rs = null; // 1. 獲取連接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); try { // 2. 獲取連接對象 conn = dataSource.getConnection(); ps = conn.prepareStatement(sql); // 3. 根據(jù)問號個數(shù)填充參數(shù) ParameterMetaData parameterMetaData = ps.getParameterMetaData(); int paramterCount = parameterMetaData.getParameterCount(); for (int i = 0; i < paramterCount ; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); // 4. 讓傳入的 結(jié)果處理對象來處理 結(jié)果集 T t = (T) handler.handle(rs); return t; } catch (Exception e) { e.printStackTrace(); return null; } finally { JDBCUtil.release(conn, ps, rs); } }
query 方法的調(diào)用public interface ResultHandler<T>{ T handle(ResultSet rs); }
public void testQuery(){ User user = query("select * from bank where id = ?", new ResultHandler<User>() { @Override public User handle(ResultSet rs) { User user = new User(); try { if (rs.next()) { user.setName(rs.getString("name")); user.setMoney(rs.getInt("money")); } } catch (SQLException e) { e.printStackTrace(); } return user; } }, 3); System.out.println(user.toString()); }