1 事務的用法
事務的ACID屬性:
通俗的說事務:指一組操作,要麼都成功執行,要麼都不執行-->原子性
在所有的操作沒有執行完畢之前,其他會話不能夠看到中間改變的過程-->隔離性
事務發生前,和發生後,數據的總額依然匹配-->一致性
當事務完成後,其影響應該保留下來,不能夠撤銷-->持久性
事務的操作:先定義開始一個事務,然後對數據做修改操作,這時如果提交(commit),這些修改就永遠保存下來,如果回退(rollback),數據庫管理系統將放棄您所做的所有修改而回到開始事務的狀態。
事務:指構成單個邏輯工作單元的操作集合
事務處理:保證所有事務都作爲一個工作單元來執行,即使出現了故障,都不能改變這種執行方式。當在一個事務中執行多個操作時,要麼所有的事務都被提交(commit),要麼整個事務回滾(rollback)到最初狀態。
處理事務的兩個動作:
提交:commit:當整個事務中,所有的邏輯單元都正常執行成功,--->提交事務,--數據已經提交不能更改。
回滾:rollback:當整個事務中,有一個邏輯單元執行失敗,--->回滾事務。
撤銷該事務中的所有操作--->恢復到最初狀態。
JDBC事務的細節:
1.在JDBC中事務是默認自動提交的,在執行DML語句的時候就已經提交事務了。
2.事務只對DML語句有效,對DQL(查詢)沒效果,查詢不會涉及到修改數據,但是以後再Spring設置的時候,往往把查詢也放到事務中,
注意:以後做開發,方式代碼沒有錯,但是數據插入/修改/刪除不成功,首先判斷事務是否提交問題,
3.回滾事務在釋放資源,釋放鎖機制(InnoDB:行鎖)。
4.在MySQL中,MyISAM不支持外鍵,不支持事務,InnoDB都支持。
5.事務還有很多很多的細節操作,留給Hibernate再講講。
在Spring中有專門的事務管理器(TransactionManager):
1 銀行轉賬案例-不使用事務:
@Test
public void testTX1() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
//1.查找張無忌的賬戶餘額是否大於等於1000塊
String sql = "SELECT * FROM account WHERE name = ? AND balance >= ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "張無忌");
ps.setInt(2, 1000);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
throw new RuntimeException("親,你的餘額不足!!");//有個特點結束代碼
}
//2.從張無忌的賬戶上減少1000塊錢。
sql = "UPDATE account SET balance = balance - ? WHERE name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 1000);
ps.setString(2, "張無忌");
ps.executeUpdate();
//模擬停電
//System.out.print(3/0);
//3.從趙敏的賬戶上增加1000塊錢。
sql = "UPDATE account SET balance = balance + ? WHERE name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 1000);
ps.setString(2, "趙敏");
ps.executeUpdate();
System.out.println("轉賬成功");
JdbcUtil.INSTANCE.close(conn, ps, rs);
}
2 銀行轉賬案例-使用事務:
@Test
public void testTX() {
Connection conn = JdbcUtil.INSTANCE.getConn();
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.查找張無忌的賬戶餘額是否大於等於1000塊
String sql = "SELECT * FROM account WHERE name = ? AND balance >= ?";
ps = conn.prepareStatement(sql);
ps.setString(1, "張無忌");
ps.setInt(2, 1000);
rs = ps.executeQuery();
if (!rs.next()) {
throw new RuntimeException("親,你的餘額不足!!");//有個特點結束代碼
}
//取消事務的自動提交機制
conn.setAutoCommit(false);
//===================================
//2.從張無忌的賬戶上減少1000塊錢。
sql = "UPDATE account SET balance = balance - ? WHERE name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 1000);
ps.setString(2, "張無忌");
ps.executeUpdate();
//模擬停電
//System.out.print(3/0);
//3.從趙敏的賬戶上增加1000塊錢。
sql = "UPDATE account SET balance = balance + ? WHERE name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 1000);
ps.setString(2, "趙敏");
ps.executeUpdate();
//提交事務
conn.commit();
//====================================
} catch (Exception e) {
e.printStackTrace();
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
JdbcUtil.INSTANCE.close(conn, ps, rs);
}
}
2 批量處理操作
批量操作:當需要成批插入或者更新記錄時。可以採用Java的批量跟新機制,這一機制允許多條語句一次性提交給數據庫批量處理。通常情況下比單獨提交更有效率。
JDBC批量處理語句包括下面倆個方法:
addBatch(String),添加需要批量處理的SQL語句或是參數。
executeBatch():執行批量處理語句。
通常情況下我們會遇到2種批量執行SQL語句的情況:
多條SQL語句的批量處理:Statment
一個SQL語句的批量傳參:PreparedStatement
需求:往account表中插入1000條數據。
結論:MySQL不支持PreparedStatment的性能優化,也不支持批量操作。
Statement:
Statement 批處理:一次性可以執行多條SQL語句,需要編譯多次。
應用場景:系統初始化(創建表,創建數據庫等)
添加sql語句:st.addBatch(sql) 添加sql語句
批量處理sql語句,int[] st.esecuteBatch()
清除緩存: st.clearBatch();
PreparedStatement:
PreparedStatement 批處理:一次性可以執行多條SQL語句,需要編譯多次。
應用場景:表數據初始化
添加批量參數:psmt.addBatch(sql) 添加實際參數,執行之前,需要執行psmt.setXxx()設置實際參數
執行批處理,int[] psmt.esecuteBatch()
清除緩存: psmt.clearBatch();
清除參數:psmt.clearParameter();
//不使用批量操作
//MyISAM:441ms
//InnoDB:4125ms
@Test
public void testStatement() throws Exception {
long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.INSTANCE.getConn();
Statement st = conn.createStatement();
for (int i = 0; i < 1001; i++) {
String sql = "insert into account (name,balance) values ('韋小寶',28)";
st.executeUpdate(sql);
}
JdbcUtil.INSTANCE.close(conn, st, null);
System.out.println(System.currentTimeMillis() - begin);
}
//使用批量操作
//MyISAM:492ms
//Innodb:4151ms
@Test
public void testStatementByBatch() throws Exception {
Long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.INSTANCE.getConn();
Statement st = conn.createStatement();
for (int i = 0; i < 1001; i++) {
String sql = "insert into account (name,balance) values ('韋小寶',28)";
st.addBatch(sql);//放入批量中
if (i % 200 == 0) {
st.executeBatch();//執行批量操作
st.clearBatch();//清除緩存
}
}
JdbcUtil.INSTANCE.close(conn, st, null);
System.out.println(System.currentTimeMillis() - begin);
}
//不使用批量操作
//MyISAM:473ms
//InnoDB:4087ms
@Test
public void testPreparedStatement() throws Exception {
Long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "insert into account (name,balance) values (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < 1001; i++) {
ps.setString(1, "喬峯");
ps.setInt(2, 36);
ps.executeUpdate();
}
JdbcUtil.INSTANCE.close(conn, ps, null);
System.out.println(System.currentTimeMillis() - begin);
}
//使用批量操作
//MyISAM:470sm
//InnoDB:4140sm
@Test
public void testPreparedStatementByBatch() throws Exception {
Long begin = System.currentTimeMillis();
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "insert into account (name,balance) values (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < 1001; i++) {
ps.setString(1, "喬峯");
ps.setInt(2, 36);
ps.addBatch();//添加批量參數
if (i % 200 == 0) {
ps.executeBatch();//執行批量操作
ps.clearBatch();//清除緩存
ps.clearParameters();//清除參數
}
}
JdbcUtil.INSTANCE.close(conn, ps, null);
System.out.println(System.currentTimeMillis() - begin);
}
3 大數據類型
TinyBlob, Blob, MediumBlob, LongBlob 都是二進制類型。
唯一不同的就是容量不同,可以把二進制的數據保存到數據庫,比如把一個音頻,視頻,圖片存到數據庫中。
注意:開發中我們往往把二進制文件的保存路徑存儲到數據庫中,而不是把數據存儲到數據庫。
比如:上傳一張圖片:保存到當前項目下的:/upload/12345.png
id name email headImg
1 喬峯 qiao /upload/qiao.png
2 阿朱 ah /upload/ah.png
TEXT系列:保存文字比較多(博客/小說)
TinyTEXT, TEXT, MediumTEXT, LongTEXT
對應着java中的String 在Java代碼中沒有變化。
//把圖片寫入到數據中
@Test
public void testWrite() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "INSERT INTO image (img) values (?)";
PreparedStatement ps = conn.prepareStatement(sql);
InputStream in = new FileInputStream("C:/three.jpg");
ps.setBlob(1, in);
ps.executeUpdate();
JdbcUtil.INSTANCE.close(conn, ps, null);
}
//把圖片從數據庫中讀取出來
@Test
public void testRead() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "SELECT * FROM image WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 1);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob("img");
//從數據庫中讀出來
InputStream in = blob.getBinaryStream();
//保存到磁盤的文件中:文件拷貝操作
Files.copy(in, Paths.get("D:/123.png"));
}
JdbcUtil.INSTANCE.close(conn, ps, rs);
}
//插入文檔
@Test
public void testWrite1() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "INSERT INTO image (img) values (?)";
PreparedStatement ps = conn.prepareStatement(sql);
InputStream in = new FileInputStream("C:/story.txt");
ps.setBlob(1, in);
ps.executeUpdate();
JdbcUtil.INSTANCE.close(conn, ps, null);
}
//把文檔從數據庫中拿出來
@Test
public void testRead1() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "SELECT * FROM image WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 4);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob("img");
//從數據庫中讀出來
InputStream in = blob.getBinaryStream();
//保存到磁盤的文件中:文件拷貝操作
Files.copy(in, Paths.get("D:/123.txt"));
}
JdbcUtil.INSTANCE.close(conn, ps, rs);
}
4 獲取自動生成的主鍵
Statement:
int executeUpdate(String sql):執行DML/DDL語句。
int executeUpdate(String sql, int autoGeneratedKeys):
參數:autoGeneratedKeys:是否需要返回自動生成的主鍵
Statement.RETURN_GENERATED_KEYS:要返回
Statement.NO_GENERATED_KTY:不返回
ResultSet getGeneratedKeys():獲取自動生成的主鍵
PreparedStatement:
PreparedStatement prepareStatement(String sql) :執行DML/DDL語句。
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) :
參數:autoGeneratedKeys:是否需要返回自動生成的主鍵
Statement.RETURN_GENERATED_KEYS:要返回
Statement.NO_GENERATED_KTY:不返回
@Test
public void testStatement() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
Statement st = conn.createStatement();
String sql = "insert into t_student (name) values ('A')";
st.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
ResultSet rs = st.getGeneratedKeys();
if(rs.next()){
long id = rs.getLong(1);
System.out.println(id);
}
JdbcUtil.INSTANCE.close(conn, st, rs);
}
@Test
public void testPreparedStatement() throws Exception {
Connection conn = JdbcUtil.INSTANCE.getConn();
String sql = "insert into t_student (name) values (?)";
PreparedStatement ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
ps.setString(1, "B");
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if(rs.next()){
Long id = rs.getLong(1);
System.out.println(id);
}
JdbcUtil.INSTANCE.close(conn, ps, rs);
}