java核心技術(shù)第五篇之事務(wù)和MVC模式

第一部分:事務(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ù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绳矩,一起剝皮案震驚了整個濱河市罩润,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翼馆,老刑警劉巖割以,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異应媚,居然都是意外死亡严沥,警方通過查閱死者的電腦和手機蚓让,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門债蓝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人堵漱,你說我怎么就攤上這事扎筒±痴遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵嗜桌,是天一觀的道長奥溺。 經(jīng)常有香客問我,道長骨宠,這世上最難降的妖魔是什么浮定? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮层亿,結(jié)果婚禮上桦卒,老公的妹妹穿的比我還像新娘。我一直安慰自己匿又,他們只是感情好方灾,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般裕偿。 火紅的嫁衣襯著肌膚如雪洞慎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天嘿棘,我揣著相機與錄音劲腿,去河邊找鬼。 笑死鸟妙,一個胖子當(dāng)著我的面吹牛焦人,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播圆仔,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼垃瞧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坪郭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脉幢,失蹤者是張志新(化名)和其女友劉穎歪沃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫌松,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡沪曙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萎羔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片液走。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贾陷,靈堂內(nèi)的尸體忽然破棺而出缘眶,到底是詐尸還是另有隱情,我是刑警寧澤髓废,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布巷懈,位于F島的核電站,受9級特大地震影響慌洪,放射性物質(zhì)發(fā)生泄漏顶燕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一冈爹、第九天 我趴在偏房一處隱蔽的房頂上張望涌攻。 院中可真熱鬧,春花似錦频伤、人聲如沸恳谎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惠爽。三九已至癌蓖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婚肆,已是汗流浹背租副。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留较性,地道東北人用僧。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像赞咙,于是被迫代替她去往敵國和親责循。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容