第一部分:事務(wù)
1.事務(wù)的簡介:
1.1 在一組操作中(比如增加操作,修改操作),只有增加和修改操作都成功之后,這兩個操作才能真正的成功.
,如果這兩個操作中,有一個失敗了,這兩個操作都失敗了.
1.2 應(yīng)用的場景:轉(zhuǎn)賬的例子.
(1) 有兩個人:小奧和小溫
(2) 小溫轉(zhuǎn)賬5000給小奧
(3) 小溫少5000,小奧多5000
(4) 產(chǎn)生問題:小溫給小奧轉(zhuǎn)賬5000,小溫少5000,發(fā)生異常錯誤,小奧沒有得到錢
(5) 使用事務(wù)解決問題
2.mysql中操作事務(wù):
2.0 兩個概念
(1) 提交事務(wù):讓表中數(shù)據(jù)真正生效
(2) 回滾事務(wù):回到操作之前的狀態(tài)
2.1 在mysql中事務(wù)默認是自動提交的
2.2 設(shè)置mysql的事務(wù)提交方式不是自動提交,需要手動提交事務(wù).
? (1)查詢當(dāng)前mysql的事務(wù)提交方式:show variables like '%commit%';
? (2)查詢提交方式:set autocommit = off/0; 0---OFF 1---ON
? (3)mysql數(shù)據(jù)庫操作事務(wù)語句
? = 打開事務(wù):start transaction;
? = 提交事務(wù):commit;
? = 回滾事務(wù):rollback;
3.jdbc操作事務(wù):
3.1 在jdbc操作中,事務(wù)也是自動提交的
3.2 設(shè)置事務(wù)不是自動提交
? (1) 在Connection里面setAutoCommit(boolean autocommit),設(shè)置是否自動提交
=參數(shù)值,默認是true,設(shè)置不自動提交設(shè)置值false;
? (2) 在connection里面commit(),提交事務(wù)
? (3) 在Connection里面rollback(),回滾事務(wù)
3.3 演示轉(zhuǎn)賬的例子()
4.事務(wù)的特性:
4.1有四個特性
? (1)原子性 : 在一組操作中,要么都成功,有一個失敗所有的都失敗.
? (2)一致性 : 在操作之前和之后數(shù)據(jù)一致的
? (3)隔離性 : 多個事務(wù)之間的操作不會互相影響的
? = 開啟第一個事務(wù)一
? = 開啟第二個事務(wù)二
? == 如果在事務(wù)一,添加數(shù)據(jù),不提交事務(wù)
? == 在事務(wù)二厘米,查詢不到添加的數(shù)據(jù)的
? (4)持久性 : 提交事務(wù)之后,數(shù)據(jù)真正生效
5.如果不考慮事務(wù)的隔離性,產(chǎn)生問題(三個讀的問題)
5.1臟讀
(1)有兩個事務(wù),其中一個事務(wù)讀到另一個事務(wù)沒有提交的數(shù)據(jù)
insert into account values('小胖',30000);
insert into account values('小蒼',30000);
(2)演示操作步驟:
第一步:打開兩個cmd窗口,分別連接數(shù)據(jù)庫,切換到day14;
第二步:在左邊的窗口中設(shè)置事務(wù)的隔離級別 read uncommitted
set session transaction isolation level read uncommitter;
第三步:在兩個窗口中分別開啟事務(wù)
第四步:在右邊窗口,小胖給小蒼轉(zhuǎn)賬10000;
update account sel sal = sal -10000 where username = "小胖";
update account sal sal = sal + 10000 where username = "小蒼";
(3)產(chǎn)生的問題: 如果小胖把事務(wù)回滾了,小蒼查詢不到數(shù)據(jù),稱為臟讀.
(4)解決臟讀的方法: 設(shè)置事務(wù)額隔離級別
(5)臟讀是一個問題,不允許發(fā)生的
5.2不可重復(fù)讀
(1)有兩個事務(wù),其中一個沒有提交的事務(wù)讀到另一事務(wù)提交的update的操作
(2) 操作步驟:
第一步:打開兩個cmd窗口,分別連接數(shù)據(jù)庫,切換到day14;
第二步:在左邊的窗口設(shè)置隔離級別 read committed
set session set sal = sal-10000 where username = '小胖';
第三步:在兩個窗口中分別開啟事務(wù)
第四步:在右邊窗口,小胖給小蒼轉(zhuǎn)賬10000;
update account set sal = sal -10000 where username = '小胖';
update account set sal = sal + 10000 where username = '小蒼';
第五步: 在左邊查詢結(jié)果,發(fā)現(xiàn)數(shù)據(jù)變化,左邊是在一個事務(wù)中,沒有提交的事務(wù),卻讀到另一事務(wù)中提交的數(shù)據(jù).
稱為不可重復(fù)讀
(3)是一種現(xiàn)象,在一些情況下允許發(fā)生的
(4)解決臟讀的方法:設(shè)置事務(wù)的隔離級別解決
(5)演示步驟:
第一步:打開兩個cmd窗口,分別連接數(shù)據(jù)庫,切換到day14;
第二步:在左邊的窗口中設(shè)置事務(wù)的隔離級別? repeatable read
set session transaction isolation level repeatable read;
第三步:在兩個cmd窗口中分別開啟事務(wù)
第四步:在右邊窗口,小胖給小蒼轉(zhuǎn)賬10000;
update account set sal = sal-10000 where username = '小胖';
update account set sal = sal + 10000 where username = "小蒼";
(3)產(chǎn)生問題:如果小胖把事務(wù)回滾了,小蒼查詢不到數(shù)據(jù),稱為臟讀
5.3虛讀(幻讀)
(1)有兩個事務(wù),其中一個沒有提交的事務(wù)讀到另一事務(wù)提交的insert的操作
5.4解決讀的問題: 設(shè)置事務(wù)的隔離級別解決
數(shù)據(jù)庫提供四個隔離級別,防止三類讀問題:
serializable : 串行的.避免臟讀,不可重復(fù)讀和虛讀發(fā)生
repeatable read : 重復(fù)讀.避免臟讀,但是不可重復(fù)讀和虛讀有可能發(fā)生
read committed : 已提交讀.避免臟讀,但是不可重復(fù)讀和虛讀有可能發(fā)生
read uncommitted : 未提交讀.臟讀,不可重復(fù)讀,虛讀
隔離級別優(yōu)先級:read uncommitted << read commited << repeatble read << serializable
(2)mysql數(shù)據(jù)庫默認隔離級別: repeatble read
(3)操作語句
select @@tx isolation; 查詢當(dāng)前事務(wù)隔離級別
set session transaction isolation level 設(shè)置事務(wù)隔離級別
6.JDBC中設(shè)置事務(wù)的隔離級別
6.1 Connection里面setTransactionIsolation(int lexel)方法設(shè)置
= 方法的參數(shù):使用Connection里面常量表示不同的隔離級別
關(guān)于事務(wù)中掌握內(nèi)容:
1.mysql里面操作事務(wù)語句:
(1) 開啟事務(wù):start transaction
(2) 提交事務(wù):commit
(3) 回滾事務(wù):rollback
(4) 在mysql里面默認自動提交
2.jdbc操作事務(wù)的三個方法
3.事務(wù)的四個特性
4.如果不考慮隔離性,產(chǎn)生三個讀的問題
(1)設(shè)置事務(wù)的隔離級別解決
(2)mysql默認的隔離級別,repeatable read
*/
/*
01.事務(wù)_概述
1)."事務(wù)"是"數(shù)據(jù)庫"中的概念械念,它是指針對一個業(yè)務(wù)彤守,在數(shù)據(jù)庫中要執(zhí)行多個操作纪铺。
? ? 例如:銀行轉(zhuǎn)賬
張三給李四轉(zhuǎn)賬1000元;
? 在數(shù)據(jù)庫中至少要做兩個操作:
1).將張三的賬戶減少1000元淆院;
2).將李四的賬戶增加1000元竭贩;
? 對于數(shù)據(jù)庫軟件颗搂,要有能力將多個SQL語句作為一個"整體"寨蹋,要么全部成功,要么全部失敗擂涛。
? 這個整體被執(zhí)行的業(yè)務(wù)就叫:事務(wù)读串。
2).我們今天講到的事務(wù)處理的方式:
1).在MySQL中怎樣直接操作事務(wù);
2).通過JDBC怎樣操作數(shù)據(jù)庫中的事務(wù)撒妈;
3).通過DBUtils怎樣操作數(shù)據(jù)庫中的事務(wù)恢暖;
02.事務(wù)_MySQL中的事務(wù)處理
1).自動事務(wù):MySQL的默認事務(wù)處理方式
將每條SQL作為一個獨立的事務(wù)的進行處理,會被立即修改到數(shù)據(jù)庫中踩身。
2).手動事務(wù):
1).關(guān)閉自動事務(wù)[不常用]:
A).查看當(dāng)前的事務(wù)處理方式:show variables like 'autocommit';
? ? 結(jié)果:autocommit ON (自動提交--打開)
B).關(guān)閉自動提交:
set autocommit = off;
C).發(fā)送SQL語句
update users set ....
....
update users set ....
D).提交:
commit;//將把之前發(fā)送的所有SQL語句全部更新
或者
? 回滾:
rollback;//將把之前所有的SQL語句全部取消
注意:此設(shè)置只對當(dāng)前的連接用戶有效
2).在"自動事務(wù)"的情況下胀茵,臨時開啟一個事務(wù)【常用】:
A).臨時開啟一個事務(wù):
start transaction;
B).發(fā)送SQL語句
update users set ....
....
update users set ....
C).提交:
commit;
或者
? 回滾:
rollback;
注意:不論提交或者回滾,當(dāng)前的事務(wù)立即結(jié)束挟阻。而且立即恢復(fù)到之前的提交模式。
? ? ? 如果不提交或者回滾,就斷開連接附鸽,后期數(shù)據(jù)庫會將此次事務(wù)的所有操作全部回滾脱拼。
03.事務(wù)_JDBC中的事務(wù)處理
......
conn.setAutoCommit(false);//開啟事務(wù)--設(shè)置Connection對象的"自動提交-false"
try{
int row1 = stmt.executeUpdate("update users set loginName = 'aa33' where uid = 1");
int row2 = stmt.executeUpdate("update users set loginName = 'bb33' where uid = 2");
conn.commit();//5.提交
}catch(Exception e){
conn.rollback();//6.回滾事務(wù)
}finally{
conn.setAutoCommit(true);//開啟自動提交
conn.close();//關(guān)閉連接
}
System.out.println("完畢!");
源碼:
package cn.baidu.demo01_JDBC中的事務(wù)處理;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class Demo {
public static void main(String[] args) throws Exception {
//1.注冊驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連接對象
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/hei66_day21","root","123");
//3.開啟事務(wù)--設(shè)置Connection對象的"自動提交-false"
conn.setAutoCommit(false);
//4.發(fā)送SQL語句
Statement stmt = conn.createStatement();
try{
int row1 = stmt.executeUpdate("update users set loginName = 'aa33' where uid = 1");
int row2 = stmt.executeUpdate("update users set loginName = 'bb33' where uid = 2");
//5.提交
conn.commit();
System.out.println("提交事務(wù)......");
}catch(Exception e){
//6.回滾事務(wù)
conn.rollback();
System.out.println("回滾事務(wù)......");
}finally{
//開啟自動提交
conn.setAutoCommit(true);
//關(guān)閉連接
conn.close();
}
System.out.println("完畢坷备!");
}
}
04.事務(wù)_DBUtils中的事務(wù)處理
QueryRunner qr = new QueryRunner();//1.創(chuàng)建一個QueryRunner對象
//2.獲取一個Connection對象
ComboPooledDataSource ds = new ComboPooledDataSource();
Connection conn = ds.getConnection();
//設(shè)置為手動提交
conn.setAutoCommit(false);
//3.發(fā)送SQL語句
String sql1 = "update users set loginName = 'aa55' where uid = 1";
String sql2 = "update users2 set loginName = 'bb55' where uid = 2";
try{
int row1 = qr.update(conn, sql1);//【注意--調(diào)用的update方法要接收一個Connection對象】
int row2 = qr.update(conn, sql2);//【注意--調(diào)用的update方法要接收一個Connection對象】
//提交
conn.commit();
System.out.println("提交事務(wù)......");
}catch(Exception e){
//回滾
conn.rollback();
System.out.println("回滾事務(wù)......");
}finally{
//設(shè)置為自動提交
conn.setAutoCommit(true);
//回收連接
conn.close();
}
源碼:
package cn.baidu.demo02_DBUtils中的事務(wù)處理;
import java.sql.Connection;
import java.sql.Statement;
import org.apache.commons.dbutils.QueryRunner;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class Demo {
public static void main(String[] args) throws Exception {
//1.創(chuàng)建一個QueryRunner對象
QueryRunner qr = new QueryRunner();
//2.獲取一個Connection對象
ComboPooledDataSource ds = new ComboPooledDataSource();
Connection conn = ds.getConnection();
//設(shè)置為手動提交
conn.setAutoCommit(false);
//3.發(fā)送SQL語句
String sql1 = "update users set loginName = 'aa55' where uid = 1";
String sql2 = "update users2 set loginName = 'bb55' where uid = 2";
try{
int row1 = qr.update(conn, sql1);
int row2 = qr.update(conn, sql2);
//提交
conn.commit();
System.out.println("提交事務(wù)......");
}catch(Exception e){
//回滾
conn.rollback();
System.out.println("回滾事務(wù)......");
}finally{
//設(shè)置為自動提交
conn.setAutoCommit(true);
//回收連接
conn.close();
}
}
}
-----------------------------------------------------------------------------------
05.MVC模式_不使用MVC模式實現(xiàn)登錄注冊案例
package cn.baidu.demo03_不使用MVC模式實現(xiàn)登錄注冊案例;
import java.sql.SQLException;
import java.util.Scanner;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
public class Demo {
public static void main(String[] args) throws SQLException {
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("1.登錄? ? ? 2.注冊? ? ? ? 3.退出:");
int op = sc.nextInt();
switch(op){
case 1://登錄
toLogin();
break;
case 2://注冊
toRegist();
break;
case 3://退出
System.out.println("謝謝使用Oㄅā!");
System.exit(0);
default:
System.out.println("錯誤的輸入省撑!");
break;
}
}
}
//注冊
private static void toRegist() throws SQLException {
Scanner sc = new Scanner(System.in);
System.out.println("請輸入用戶名:");
String loginName = sc.next();
System.out.println("請輸入密碼:");
String loginPwd = sc.next();
//1.驗證用戶名和密碼的字符
//略
//2.數(shù)據(jù)庫驗證--用戶名不能重復(fù)
QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from users where loginName = ?";
User userBean = qr.query(sql, new BeanHandler<User>(User.class),loginName);
if(userBean != null){
System.out.println("用戶名:" + loginName + " 已存在赌蔑!");
return;
}
//3.將這條信息寫入到數(shù)據(jù)庫
sql = "insert into users values(null,?,?)";
int row = qr.update(sql,loginName,loginPwd);
if(row > 0){
System.out.println("注冊成功!");
}else{
System.out.println("注冊失斁癸娃惯!");
}
}
//登錄
private static void toLogin() throws SQLException {
Scanner sc = new Scanner(System.in);
System.out.println("請輸入登錄名:");
String loginName = sc.next();
System.out.println("請輸入密碼:");
String loginPwd = sc.next();
//查詢數(shù)據(jù)
QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from users where loginName = ? and loginPwd = ?";
User user = qr.query(sql, new BeanHandler<User>(User.class),loginName,loginPwd);
if(user != null){
System.out.println("歡迎:" + loginName + " 登錄系統(tǒng)!肥败!");
}else{
System.out.println("用戶名或密碼錯誤V呵场!");
}
}
}
package cn.baidu.demo03_不使用MVC模式實現(xiàn)登錄注冊案例;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource(){
return dataSource;
}
}
06.MVC模式_使用MVC模式實現(xiàn)登錄
MVC模式:(MVC模式產(chǎn)生的原因)如果將程序的所有代碼寫在一個類中,這樣代碼量太大,會對后期程序維護造成困難.
什么是MVC模式呢?
A:任何的程序都可以分為兩部分代碼:
? 1.用于接收用戶數(shù)據(jù),為用戶顯示數(shù)據(jù)的代碼:視圖層? 例如:鍵盤錄入和輸出語句
? 2.用于業(yè)務(wù)邏輯處理的代碼 :控制層? 例如:數(shù)據(jù)邏輯處理和執(zhí)行SQL語句等
? 3.作為邏輯模型 :模型層? 例如:JavaBean
B:作為企業(yè)級開發(fā),要分五層:
1.視圖層(View)(1.接收數(shù)據(jù);2.顯示數(shù)據(jù))
2.控制層(Controller)(1.業(yè)務(wù)分發(fā):查找相應(yīng)的業(yè)務(wù)層)
3.業(yè)務(wù)層(Service)(1.負責(zé)處理具體的業(yè)務(wù)邏輯)
4.持久層(DAO)(1.所有訪問數(shù)據(jù)庫的代碼)
5.數(shù)據(jù)類型(類:Model)
(從1-5,進過數(shù)據(jù)庫再返回去5-1)
? 好處:1.將不同功能的代碼分到不同的類中,使一個類中都具有相同功能的代碼(高內(nèi)聚)
? 2.從視圖層-->控制層-->業(yè)務(wù)層-->持久層 有一個"順序的依賴關(guān)系",反向,則沒有這種依賴關(guān)系,相互獨立.(低耦合)
? 注意:1.從視圖層到持久層,之間是順序的依賴關(guān)系,不能跳級.
? 2.返現(xiàn)不存在依賴關(guān)系,不能在底層主動調(diào)用上一層的類中的方法.
/*
package cn.baidu.demo04_MVC實現(xiàn)登錄注冊;
public class UserModel {
private Integer uid;
private String loginName;
private String loginPwd;
public UserModel(Integer uid, String loginName, String loginPwd) {
super();
this.uid = uid;
this.loginName = loginName;
this.loginPwd = loginPwd;
}
public UserModel() {
super();
// TODO Auto-generated constructor stub
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getLoginPwd() {
return loginPwd;
}
public void setLoginPwd(String loginPwd) {
this.loginPwd = loginPwd;
}
@Override
public String toString() {
return "User [uid=" + uid + ", loginName=" + loginName + ", loginPwd=" + loginPwd + "]";
}
}
package cn.baidu.demo04_MVC實現(xiàn)登錄注冊;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource(){
return dataSource;
}
}
/*
* 視圖層:只接受和輸出語句,顯示
*
*/
public class View {
private Controller control = new Controller();
public void start() {
Scanner sc = new Scanner(System.in);
while(true) {
System.out.println("1.登錄? ? ? ? ? ? ? 2.注冊? ? ? ? ? ? ? ? 3.退出");
int op = sc.nextInt();
switch(op) {
case 1://登錄
toLogin();
break;
case 2://注冊
toRegist();
break;
case 3://退出
System.out.println("謝謝使用!!");
System.exit(0);
default :
System.out.println("錯誤的輸入!");
break;
}
}
}
//注冊
private void toRegist() {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
System.out.println("請輸入用戶名:");
String loginName = sc.nextLine();
System.out.println("請輸入密碼:");
String loginPwd = sc.nextLine();
//把接收的數(shù)據(jù)封裝到一個User對象
UserModel user = new UserModel();
user.setLoginName(loginName);
user.setLoginPwd(loginPwd);
//調(diào)用控制層
try{
if(control.toRegist(user)) {
System.out.println("注冊成功!");
}else {
System.out.println("注冊失敗!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
//登錄
private void toLogin() {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
System.out.println("請輸入登錄名:");
String loginName = sc.nextLine();
System.out.println("請輸入密碼:");
String loginPwd = sc.nextLine();
//封裝JavaBean
UserModel user = new UserModel();
user.setLoginName(loginName);
user.setLoginPwd(loginPwd);
//調(diào)用控制層
try {
if(this.control.toLogin(user)) {
System.out.println("歡迎:" + loginName + "登錄系統(tǒng)!");
//啟動學(xué)員信息管理的視圖層代碼
}else {
System.out.println("登錄失敗!可能的原因:用戶名和密碼錯誤!");
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
package com.baidu_02;
/*
* 控制層:進行業(yè)務(wù)分發(fā),發(fā)給業(yè)務(wù)層
*
*/
public class Controller {
private Service service = new Service();
//1.注冊
public boolean toRegist(UserModel user) throws Exception {
//1.調(diào)用Service
return service.toRegist(user);
}
//2.登錄
public boolean toLogin(UserModel user) throws Exception {
//調(diào)用Service
return service.toLogin(user);
}
}
//業(yè)務(wù)層:主要進行邏輯處理
public class Service {
private DAO dao = new DAO();
//1.注冊:
public boolean toRegist(UserModel user) throws Exception {
/*
* 1.驗證用戶名和密碼的字符
* 用戶名6-12個字符,由數(shù)字,大小字母,小寫字母組成
* 密碼:必須是6位數(shù)字
*
*/
String regex = "[0-9,A-Z,a-z]{6,12}";
if(!user.getLoginName().matches(regex)) {
return false;
}
//? \\d? 表示數(shù)字
regex = "\\d{6}";
if(!user.getLoginPwd().matches(regex)) {
return false;
}
//2.數(shù)據(jù)庫驗證--用戶名不能重復(fù)--調(diào)用DAO
UserModel resultUser = dao.findByLoginName(user.getLoginName());
//此用戶名已經(jīng)被使用
if(resultUser != null) {
return false;
}
//3.將這條信息寫入到數(shù)據(jù)庫
return dao.save(user);
}
//2.登錄
public boolean toLogin(UserModel user) throws Exception {
//1.做一些用戶名和密碼的字符驗證
//...
//2.查詢此用戶
UserModel resultBean = dao.findByLoginAndPassword(user);
return resultBean != null;
}
}
//持久層:主要對數(shù)據(jù)庫進行操作
public class DAO {
private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//1.使用用戶名查詢一條記錄
public UserModel findByLoginName(String loginName) throws Exception {
String sql = "select * from user where loginName = ?";
UserModel user = qr.query(sql, new BeanHandler<UserModel>(UserModel.class), loginName);
//查到返回:UserModel對象:否則:返回NULL
return user;
}
//2.寫入一條記錄
public boolean save(UserModel user) throws Exception {
String sql = "insert into user values(null,?,?)";
int row = qr.update(sql, user.getLoginName(),user.getLoginPwd());
//如果不影響1行: 返回true;如果影響0行:返回false
return row > 0;
}
//3.用戶名和密碼查詢一個用戶
public UserModel findByLoginAndPassword(UserModel user) throws Exception {
String sql = "select * from user where loginName = ? and loginPwd = ?";
//這里的BeanHandler是因為查詢滿足條件的數(shù)據(jù)的第一條記錄
UserModel query = qr.query(sql, new BeanHandler<UserModel>(UserModel.class), user.getLoginName(),user.getLoginPwd());
return query;
}
}
/*
07.轉(zhuǎn)賬案例_MVC模式部署分析
08.轉(zhuǎn)賬案例_MVC模式事務(wù)處理實現(xiàn)
10.線程間共享對象實現(xiàn)_ThreadLocal
11.轉(zhuǎn)賬案例_使用ThreadLocal實現(xiàn)在service層和DAO層實現(xiàn)共享Connection對象
其實從程序角度看馒稍,tlt變量的確是一個皿哨,毫無疑問的。但是為什么打印出來的數(shù)字就互不影響呢纽谒?
是因為使用了Integer嗎证膨?-----不是。
原因是:protected T initialValue()和get()鼓黔,因為每個線程在調(diào)用get()時候央勒,發(fā)現(xiàn)Map中不存在就創(chuàng)建。調(diào)用它的時候请祖,就創(chuàng)建了一個新變量
订歪,類型為T。每次都新建肆捕,當(dāng)然各用個的互不影響了刷晋。
意義:
1.提供了保存對象的方法:每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中慎陵,各管各的眼虱,線程可以正確的訪問到自己的對象。
2.避免參數(shù)傳遞的方便的對象訪問方式:將一個共用的ThreadLocal靜態(tài)實例作為key席纽,將不同對象的引用保存到不同線程的ThreadLocalMap中捏悬,然后在線程執(zhí)行的各處通過這個靜態(tài)ThreadLocal實例的get()
方法取得自己線程保存的那個對象,避免了將這個對象作為參數(shù)傳遞的麻煩润梯。
理解ThreadLocal中提到的變量副本? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
“當(dāng)使用ThreadLocal維護變量時过牙,ThreadLocal為每個使用該變量的線程提供獨立的變量副本” —— 并不是通過ThreadLocal.set( )實現(xiàn)的甥厦,而是每個線程使用“new對象”(或拷貝) 的操作來創(chuàng)建對象副本, 通過ThreadLocal.set()將這個新創(chuàng)建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map寇钉,執(zhí)行ThreadLocal.get()時刀疙,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象(ThreadLocal實例是作為map的key來使用的)扫倡。
如果ThreadLocal.set( )進去的對象是多線程共享的同一個對象谦秧,那么ThreadLocal.get( )取得的還是這個共享對象本身 —— 那么ThreadLocal還是有并發(fā)訪問問題的!
解決線程安全問題撵溃,本質(zhì)上就是解決資源共享問題疚鲤,一般有以下手段:
? ? ? 1)可重入(不依賴環(huán)境);2)互斥(同一時間段只允許一個線程使用)缘挑;3)原子操作集歇;4)Thread-Local
為了保證函數(shù)是可重入的,需要做到一下幾點:
1卖哎,不在函數(shù)內(nèi)部使用靜態(tài)或者全局數(shù)據(jù)
2鬼悠,不返回靜態(tài)或者全局數(shù)據(jù),所有的數(shù)據(jù)都由函數(shù)調(diào)用者提供
3亏娜,使用本地數(shù)據(jù)焕窝,或者通過制作全局數(shù)據(jù)的本地拷貝來保護全局數(shù)據(jù)
4, 如果必須訪問全局數(shù)據(jù)维贺,使用互斥鎖來保護
5它掂,不調(diào)用不可重入函數(shù)
可重入函數(shù):可重入函數(shù)是線程安全函數(shù)的一種,其特點在于它們被多個線程調(diào)用時溯泣,不會引用任何共享數(shù)據(jù)虐秋。
可重入函數(shù)通常要比不可重入的線程安全函數(shù)效率高一些,因為它們不需要同步操作垃沦。更進一步說客给,將第2類線程不安全函數(shù)轉(zhuǎn)化為線程安全函數(shù)的唯一方法就是重寫它,使之可重入肢簿。
顯式可重入函數(shù):如果所有函數(shù)的參數(shù)都是傳值傳遞的(沒有指針)靶剑,并且所有的數(shù)據(jù)引用都是本地的自動棧變量(也就是說沒有引用靜態(tài)或全局變量),那么函數(shù)就是顯示可重入的池充,也就是說不管如何調(diào)用桩引,我們都可斷言它是可重入的。
隱式可重入函數(shù):可重入函數(shù)中的一些參數(shù)是引用傳遞(使用了指針)收夸,也就是說坑匠,在調(diào)用線程小心地傳遞指向非共享數(shù)據(jù)的指針時,它才是可重入的卧惜。例如rand_r就是隱式可重入的厘灼。
我們使用可重入(reentrant)來包括顯式可重入函數(shù)和隱式可重入函數(shù)夹纫。然而,可重入性有時是調(diào)用者和被調(diào)用者共有的屬性手幢,并不只是被調(diào)用者單獨的屬性捷凄。
原子操作是指不會被線程調(diào)度機制打斷的操作忱详;這種操作一旦開始围来,就一直運行到結(jié)束,中間不會有任何 context switch (切[1]? 換到另一個線程).
public class View {
public void start(){
Scanner sc = new Scanner(System.in);
System.out.println("轉(zhuǎn)出方:");
String outUserName = sc.next();
System.out.println("轉(zhuǎn)入方:");
String inUserName = sc.next();
System.out.println("轉(zhuǎn)賬金額:");
int money = sc.nextInt();
//封裝JavaBean
TransBean t = new TransBean();
t.setOutUserName(outUserName);
t.setInUserName(inUserName);
t.setMoney(money);
Thread tt;
//調(diào)用控制層
Controller contrl = new Controller();
try {
if(contrl.toTrans(t)){
System.out.println("轉(zhuǎn)賬成功匈睁!");
}else{
System.out.println("轉(zhuǎn)賬失敗监透,所有操作被取消!");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Controller {
public boolean toTrans(TransBean t) throws SQLException{
//調(diào)用服務(wù)層
Service service = new Service();
return service.toTrans(t);
}
}
public class Service {
private DAO dao = new DAO();
public boolean toTrans(TransBean t) throws SQLException{
//1.驗證賬戶是否存在航唆;
AccountBean acc = dao.findByUserName(t.getOutUserName());
if(acc == null){
return false;
}
acc = dao.findByUserName(t.getInUserName());
if(acc == null){
return false;
}
//2.驗證轉(zhuǎn)出方的余額是否支持轉(zhuǎn)賬胀蛮;
Integer outBalance = dao.findBalanceByUserName(t.getOutUserName());
if(t.getMoney() > outBalance){
return false;
}
//3.事務(wù)處理實施轉(zhuǎn)賬業(yè)務(wù);
//1.獲取一個連接對象
Connection conn = JDBCUtils.getDataSource().getConnection();
//2.關(guān)閉自動提交
conn.setAutoCommit(false);
//將conn通過ThreadLocal存儲到當(dāng)前線程的map中
Const.local.set(conn);
//3.調(diào)用dao糯钙,執(zhí)行轉(zhuǎn)賬
try{
boolean b1 = dao.updateBalanceByUserName(t.getOutUserName(), -t.getMoney());
int i = 10 / 0;
boolean b2 = dao.updateBalanceByUserName(t.getInUserName(), t.getMoney());
if(b1 && b2){
//4.提交
conn.commit();
return true;
}else{
//5.回滾
conn.rollback();
return false;
}
}catch(Exception e){
//5.回滾
conn.rollback();
return false;
}finally{
conn.setAutoCommit(true);//還原為自動事務(wù)
conn.close();//歸還到連接池中
}
}
}
public class DAO {
private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//1.使用用戶名查找一個用戶
public AccountBean findByUserName(String userName) throws SQLException{
String sql = "select * from account where username = ?";
AccountBean acc = qr.query(sql, new BeanHandler<AccountBean>(AccountBean.class),userName);
return acc;
}
//2.獲取某個用戶的余額
public Integer findBalanceByUserName(String userName) throws SQLException{
String sql = "select balance from account where username = ?";
Object result = qr.query(sql, new ScalarHandler(),userName);
if(result != null){
return Integer.valueOf(result.toString());
}
return null;
}
//3.修改某個賬戶的余額
public boolean updateBalanceByUserName(String userName,Integer money) throws SQLException{
String sql = "update account set balance = balance + ? where username = ?";
//通過ThreadLocal從當(dāng)前線程對象的Map中獲取conn
int row = qr.update(Const.local.get(), sql, money,userName);
return row > 0;
}
}
public class Const {
public static ThreadLocal<Connection> local = new ThreadLocal<>();
}
07.轉(zhuǎn)賬案例_MVC模式部署分析
/*
* 前端的JavaBean粪狼,跟前端表單對應(yīng)
*/
public class TransBean {
private String outUserName;
private String inUserName;
private Integer money;
public TransBean(String outUserName, String inUserName, Integer money) {
super();
this.outUserName = outUserName;
this.inUserName = inUserName;
this.money = money;
}
public TransBean() {
super();
// TODO Auto-generated constructor stub
}
public String getOutUserName() {
return outUserName;
}
public void setOutUserName(String outUserName) {
this.outUserName = outUserName;
}
public String getInUserName() {
return inUserName;
}
public void setInUserName(String inUserName) {
this.inUserName = inUserName;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
@Override
public String toString() {
return "TransBean [outUserName=" + outUserName + ", inUserName=" + inUserName + ", money=" + money + "]";
}
}
/*
* 后端的JavaBean,跟表結(jié)構(gòu)相同
*/
public class AccountBean {
private Integer id;
private String username;
private Integer money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
public AccountBean() {
super();
// TODO Auto-generated constructor stub
}
public AccountBean(Integer id, String username, Integer money) {
super();
this.id = id;
this.username = username;
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", username=" + username + ", money=" + money + "]";
}
}
package cn.baidu.demo05_MVC_結(jié)合事務(wù)處理實現(xiàn)轉(zhuǎn)賬案例;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
public static DataSource getDataSource(){
return dataSource;
}
}
import java.util.Scanner;
//視圖層
public class View {
public void start(){
Scanner sc = new Scanner(System.in);
System.out.println("轉(zhuǎn)出方:");
String outUserName = sc.next();
System.out.println("轉(zhuǎn)入方:");
String inUserName = sc.next();
System.out.println("轉(zhuǎn)賬金額:");
int money = sc.nextInt();
//封裝JavaBean
TransBean t = new TransBean();
t.setOutUserName(outUserName);
t.setInUserName(inUserName);
t.setMoney(money);
Thread tt;
//調(diào)用控制層
Controller contrl = new Controller();
try {
if(contrl.toTrans(t)){
System.out.println("轉(zhuǎn)賬成功任岸!");
}else{
System.out.println("轉(zhuǎn)賬失敗再榄,所有操作被取消!");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//控制層
public class Controller {
public boolean toTrans(TransBean t) throws SQLException{
//調(diào)用服務(wù)層
Service service = new Service();
return service.toTrans(t);
}
}
//業(yè)務(wù)層或者服務(wù)層
public class Service {
private DAO dao = new DAO();
public boolean toTrans(TransBean t) throws SQLException{
//1.驗證賬戶是否存在享潜;
AccountBean acc = dao.findByUserName(t.getOutUserName());
if(acc == null){
return false;
}
acc = dao.findByUserName(t.getInUserName());
if(acc == null){
return false;
}
//2.驗證轉(zhuǎn)出方的余額是否支持轉(zhuǎn)賬困鸥;
Integer outBalance = dao.findBalanceByUserName(t.getOutUserName());
if(t.getMoney() > outBalance){
return false;
}
//3.事務(wù)處理實施轉(zhuǎn)賬業(yè)務(wù);
//1.獲取一個連接對象
Connection conn = JDBCUtils.getDataSource().getConnection();
//2.關(guān)閉自動提交
conn.setAutoCommit(false);
//3.調(diào)用dao剑按,執(zhí)行轉(zhuǎn)賬
try{
boolean b1 = dao.updateBalanceByUserName(conn,t.getOutUserName(), -t.getMoney());
// int i = 10 / 0;
boolean b2 = dao.updateBalanceByUserName(conn,t.getInUserName(), t.getMoney());
if(b1 && b2){
//4.提交
conn.commit();
return true;
}else{
//5.回滾
conn.rollback();
return false;
}
}catch(Exception e){
//5.回滾
conn.rollback();
return false;
}finally{
conn.setAutoCommit(true);//還原為自動事務(wù)
conn.close();//歸還到連接池中
}
}
}
public class DAO {
private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
//1.使用用戶名查找一個用戶
public AccountBean findByUserName(String userName) throws SQLException{
String sql = "select * from account where username = ?";
AccountBean acc = qr.query(sql, new BeanHandler<AccountBean>(AccountBean.class),userName);
return acc;
}
//2.獲取某個用戶的余額
public Integer findBalanceByUserName(String userName) throws SQLException{
String sql = "select balance from account where username = ?";
Object result = qr.query(sql, new ScalarHandler(),userName);
if(result != null){
return Integer.valueOf(result.toString());
}
return null;
}
//3.修改某個賬戶的余額
public boolean updateBalanceByUserName(Connection conn,String userName,Integer money) throws SQLException{
String sql = "update account set balance = balance + ? where username = ?";
int row = qr.update(conn, sql, money,userName);
return row > 0;
}
}
08.轉(zhuǎn)賬案例_MVC模式事務(wù)處理實現(xiàn)
10.線程間共享對象實現(xiàn)_ThreadLocal
11.轉(zhuǎn)賬案例_使用ThreadLocal實現(xiàn)在service層和DAO層實現(xiàn)共享Connection對象
------------------------以下內(nèi)容【了解】-------------------------------------------
12.事務(wù)的特性_ACID
原子性:強調(diào)事務(wù)的不可分割.多條語句要么都成功疾就,要么都失敗。
一致性:強調(diào)的是事務(wù)的執(zhí)行的前后艺蝴,數(shù)據(jù)要保持一致.
隔離性:一個事務(wù)的執(zhí)行不應(yīng)該受到其他事務(wù)的干擾.
持久性:事務(wù)一旦結(jié)束(提交/回滾)數(shù)據(jù)就持久保持到了數(shù)據(jù)庫.
13.事務(wù)的特性_不考慮事務(wù)的隔離性可能引發(fā)的問題
* 臟讀 :一個事務(wù)讀到另一個事務(wù)還沒有提交的數(shù)據(jù).
* 不可重復(fù)讀 :一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的update的數(shù)據(jù),導(dǎo)致在當(dāng)前的事務(wù)中多次查詢結(jié)果不一致.
* 虛讀/幻讀 :一個事務(wù)讀到另一個事務(wù)已經(jīng)提交的insert的數(shù)據(jù),導(dǎo)致在當(dāng)前的事務(wù)中多次的查詢結(jié)果不一致.
14.事務(wù)的特性_解決引發(fā)的讀問題
設(shè)置事務(wù)的隔離級別:
1 read uncommitted :未提交讀.臟讀猬腰,不可重復(fù)讀,虛讀都可能發(fā)生.
2 read committed :已提交讀.避免臟讀.但是不可重復(fù)讀和虛讀有可能發(fā)生.(Oracle默認)
4 repeatable read :可重復(fù)讀.避免臟讀,不可重復(fù)讀.但是虛讀有可能發(fā)生.(MySql默認)
8 serializable :串行化的.避免臟讀猜敢,不可重復(fù)讀姑荷,虛讀的發(fā)生.
1).查看當(dāng)前事務(wù)的隔離級別:
select @@tx_isolation;
2).設(shè)置當(dāng)前事務(wù)的隔離級別:
set session transaction isolation level 上面1,2,4,8四個隔離級別名稱之一;
3).級別超高锣枝,越安全厢拭,效率越低。
==========================================================================================================================
學(xué)習(xí)目標(biāo)總結(jié):
? 1撇叁、理解事務(wù)的概念
a. 能夠說出事務(wù)的概念
事務(wù)指的是邏輯上的一組操作(多條sql語句),組成這組操作的各個單元要么全都成功,要么全都失敗供鸠。
b. 能夠說出事務(wù)的特性
原子性:強調(diào)事務(wù)的不可分割.多條語句要么都成功,要么都失敗陨闹。
一致性:強調(diào)的是事務(wù)的執(zhí)行的前后楞捂,數(shù)據(jù)要保持一致.
隔離性:一個事務(wù)的執(zhí)行不應(yīng)該受到其他事務(wù)的干擾.
持久性:事務(wù)一旦結(jié)束(提交/回滾)數(shù)據(jù)就持久保持到了數(shù)據(jù)庫.
2薄坏、理解臟讀,不可重復(fù)讀,幻讀的概念及解決辦法
a. 能夠說出不考慮事務(wù)的隔離性會出現(xiàn)的問題
1.臟讀:一個事務(wù)讀到另一個事務(wù)還沒有提交的數(shù)據(jù).
2.不可重復(fù)讀:一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的update的數(shù)據(jù),導(dǎo)致在當(dāng)前的事務(wù)中多次查詢結(jié)果不一致.
3.虛讀/幻讀:一個事務(wù)讀到另一個事務(wù)已經(jīng)提交的insert的數(shù)據(jù),導(dǎo)致在當(dāng)前的事務(wù)中多次的查詢結(jié)果不一致.
b. 能夠說出如何使用事務(wù)的隔離級別分別可以解決哪些問題
1 read uncommitted:什么都沒解決。
2 read committed:只解決臟讀寨闹。(Oracle默認)
4 repeatable read:解決了臟讀胶坠、不可重復(fù)讀、(MYSQL中也解決了虛讀)(MySql默認)
8 serializable:將兩個事務(wù)完全隔離繁堡,一個事務(wù)沒有執(zhí)行完畢沈善,另一個事務(wù)只能等待;
3椭蹄、能夠在MySQL中使用事務(wù)
a. 說出mysql中對事務(wù)支持的特點
1.自動事務(wù):MySQL默認
2.手動事務(wù):
b. 使用命令行手動開啟事務(wù)
start transaction;
c. 使用命令行手動提交事務(wù)
commit;
d. 使用命令行手動回滾事務(wù)
rollback;
e. 說出MySQL數(shù)據(jù)庫的默認隔離級別
repeatable read
4闻牡、能夠在轉(zhuǎn)賬功能中使用DBUtils開發(fā)包完成事務(wù)控制
a. 能夠編寫一個轉(zhuǎn)賬的程序功能
b. 能夠在程序中添加事務(wù)保證轉(zhuǎn)賬程序的數(shù)據(jù)正確
5、能夠使用DBUtils并通過ThreadLocal綁定Connection的方式控制事務(wù)
a. 獨立編寫操作數(shù)據(jù)庫的JDBC程序
b. 能夠在程序中使用ThreadLocal綁定Connection的方式控制事務(wù)