JDBC
JDBC(Java Data Base Connectivity, java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的javaAPI,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問访惜,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準授滓,據(jù)此可以構(gòu)建更高級的工具和接口羞海,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應(yīng)用程序。
- 加載驅(qū)動
- 連接數(shù)據(jù)庫
- 使用語句操作數(shù)據(jù)庫
- 關(guān)閉數(shù)據(jù)庫連接旬蟋,釋放資源
加載驅(qū)動(以MySQL舉例)
Java連接MySQL需要驅(qū)動包,最新下載地址為:http://dev.mysql.com/downloads/connector/j/,解壓后得到j(luò)ar庫文件革娄,然后在對應(yīng)的項目中導(dǎo)入該庫文件倾贰。
MySQL8.0之前版本
驅(qū)動名:com.mysql.jdbc.Driver
加載方式:Class.forName("com.mysql.jdbc.Driver")
MySQL8.0之后版本
驅(qū)動名:com.mysql.cj.jdbc.Driver
加載方式:Class.forName("com.mysql.cj.jdbc.Driver")
連接關(guān)閉數(shù)據(jù)庫
- DriverManager:驅(qū)動管理類冕碟,負責獲取數(shù)據(jù)庫的連接
- getConnection(String url, String user, String password):試圖建立給定數(shù)據(jù)庫的URL連接
- 數(shù)據(jù)庫連接地址格式:jdbc:mysql://IP:端口號/數(shù)據(jù)庫名稱
- Connection接口:與特定的數(shù)據(jù)庫連接(會話)
- createStatement():創(chuàng)建Statement執(zhí)行數(shù)據(jù)庫更刪改查等操作
- close():立即釋放此Connection對象的數(shù)據(jù)庫和JDBC資源,而不是等待他們被自動釋放
舉個例子:
import java.sql.*;
public class MySQLDemo{
//MySQL8.0以下版本-JDBC數(shù)據(jù)庫名及數(shù)據(jù)庫URL匆浙,數(shù)據(jù)庫名稱為test_db
static final String JDBC_DRIVER = "com.mysql.Driver";
statis final String DB_URL="jdbc:mysql://localhost:3306/test_db"
//MySQL8.0以上版本-JDBC數(shù)據(jù)庫名及數(shù)據(jù)庫URL安寺,數(shù)據(jù)庫名稱為test_db
//static final String JDBC_DRIVER="com.mysql.cj.jdbc.Driver";
//static final String DB_URL="jdbc:mysql://localhost:3306/test_db"
//數(shù)據(jù)庫的用戶名與密碼,根據(jù)你自己的數(shù)據(jù)庫進行設(shè)置
static final String User ="root";
static final String Password="123456";
public static void main(String... args){
Connection conn = null;
try{
//注冊JDBC驅(qū)動
Class.forName(JDBC_DRIVER);
System.out.println("注冊驅(qū)動成功");
//連接數(shù)據(jù)庫并得到Connection會話
conn = DriverManager.getConnection(DB_URL, User, Password);
}catch(ClassNotFoundException e){
e.printStackTrace();
System.out.println("注冊驅(qū)動失敗")
}finally{
//最后關(guān)閉Connection會話首尼,釋放資源
if(conn !=null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
操作數(shù)據(jù)庫(Statement)
- 作用:用于執(zhí)行靜態(tài)SQL語句并返回它所生成結(jié)果的對象挑庶;
- int executeUpdate(String sql)執(zhí)行給定SQL語句,該語句可能為INSERT软能、UPDATE或DELETE語句迎捺,或者不返回任何內(nèi)容的SQL語句(如SQL DDL 語句)
- void close():立即釋放此Statement對象的數(shù)據(jù)庫和資源,而不是等待該對象自動關(guān)閉
封裝DBUtil工具類
public class DBUtil{
//MySQL8.0以下版本-JDBC數(shù)據(jù)庫名及數(shù)據(jù)庫URL查排,數(shù)據(jù)庫名稱為test_db
static final String JDBC_DRIVER = "com.mysql.Driver";
statis final String DB_URL="jdbc:mysql://localhost:3306/test_db"
//MySQL8.0以上版本-JDBC數(shù)據(jù)庫名及數(shù)據(jù)庫URL凳枝,數(shù)據(jù)庫名稱為test_db
//static final String JDBC_DRIVER="com.mysql.cj.jdbc.Driver";
//static final String DB_URL="jdbc:mysql://localhost:3306/test_db"
//數(shù)據(jù)庫的用戶名與密碼,根據(jù)你自己的數(shù)據(jù)庫進行設(shè)置
static final String User ="root";
static final String Password="123456";
/**
*獲取數(shù)據(jù)庫連接
*/
public Connection getConnection() throws Exception{
//注冊JDBC驅(qū)動
Class.forName(JDBC_DRIVER);
//連接數(shù)據(jù)庫并得到Connection會話
return DriverManager.getConnection(DB_URL, User, Password);
}
/**
*關(guān)閉連接
* @param conn
* @throws Exception
*/
public void close(Connection conn) throws Exception{
if(conn!=null){
conn.close();
}
}
/**
* 同時關(guān)閉Statement和Connection
*/
public void close(Statement stmt, Connection conn) throws Exception{
if(stmt !=null){
stmt.close();
}
if(conn !=null){
conn.close();
}
}
}
下面執(zhí)行連接數(shù)據(jù)庫均采用上述的DBUtil工具類跋核。
創(chuàng)建測試數(shù)據(jù)
接下來我們在MySQL中創(chuàng)建test_db數(shù)據(jù)庫并創(chuàng)建websites數(shù)據(jù)表岖瑰,表結(jié)構(gòu)如下:
CREATE TABLE `websites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL DEFAULT '' COMMENT '站點名稱',
`url` varchar(255) NOT NULL DEFAULT '',
`alexa` int(11) NOT NULL DEFAULT '0' COMMENT 'Alexa 排名',
`country` char(10) NOT NULL DEFAULT '' COMMENT '國家',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
測試插入:
public class TestInsert{
public static void main(String... args) throws Exception{
DBUtil dbUtil = new DBUtil();
//SQL語句,這里的1也可以設(shè)置為null砂代,因為為它設(shè)置了自增
String sql="INSERT INTO `websites` VALUES ('1', 'Google', 'https://www.google.cm/', '1', 'USA')";
Connection conn = dbUtil.getConnection();//獲取數(shù)據(jù)庫連接
Statement stmt = conn.createStatement();//獲取Statement
//進行SQL語句進行插入蹋订,其中返回結(jié)果表示數(shù)據(jù)庫操作的數(shù)量,這里插入一條數(shù)據(jù)返回的就是1刻伊,插入N條返回N露戒。
int result = stmt.executeUpdate(sql);
System.out.println("操作的結(jié)果:"+ result+"數(shù)據(jù)");
stmt.close();關(guān)閉stmt
dbUtil.close(conn);//關(guān)閉Connection
}
}
這里我們將需要插入的數(shù)據(jù)直接封裝在SQL語句中,但是實際開發(fā)的時候是由外部傳入的捶箱,我們可以進行修改為:
public class TestInsert{
public static int addWebsite(String name, String url, int alexa, String country) throws Exception{
DBUtil dbUtil = new DBUtil();
//SQL語句玫锋,這里的1也可以設(shè)置為null,因為為它設(shè)置了自增
String sql="INSERT INTO `websites` VALUES (null, '"+name+"', '"+website+"', '"+alexa+"', '"+country+"')";
Connection conn = dbUtil.getConnection();//獲取數(shù)據(jù)庫連接
Statement stmt = conn.createStatement();//獲取Statement
//進行SQL語句進行插入讼呢,其中返回結(jié)果表示數(shù)據(jù)庫操作的數(shù)量,這里插入一條數(shù)據(jù)返回的就是1谦炬,插入N條返回N悦屏。
int result = stmt.executeUpdate(sql);
System.out.println("操作的結(jié)果:"+ result+"數(shù)據(jù)");
stmt.close();關(guān)閉stmt
dbUtil.close(conn);//關(guān)閉Connection
return result;
}
public static void main(String... args) throws Exception{
int result=addWebsite("Google", "https://www.google.com/", 1, "USA");
if(result==1){
System.out.println("添加成功");
}else{
System.out.println("添加失敗");
}
}
}
但是傳入那么多參數(shù)并不符合面向?qū)ο蟮乃枷耄覀儜?yīng)該將網(wǎng)站信息封裝成Java類键思,以對象的形式進行操作础爬,具體如下:
class Website{
private int id;
private String name;
private String url;
private int alexa;
private String country;
//下面是對應(yīng)的構(gòu)造方法、Getter和Setter方法吼鳞,請自行添加
}
添加完Website類之后看蚜,TestInsert添加AddWebsite2方法,操作如下:
public static int addWebsite2(Website website) throws Exception{
DBUtil dbUtil = new DBUtil();
//SQL語句赔桌,這里的1也可以設(shè)置為null供炎,因為為它設(shè)置了自增
String sql="INSERT INTO 'websites' VALUES (null, '"+website.getName()+"', '"+website.getUrl()+"', '"+website.getAlexa()+"', '"+website.getCountry()+"')";
Connection conn = dbUtil.getConnection();//獲取數(shù)據(jù)庫連接
Statement stmt = conn.createStatement();//獲取Statement
//進行SQL語句進行插入渴逻,其中返回結(jié)果表示數(shù)據(jù)庫操作的數(shù)量,這里插入一條數(shù)據(jù)返回的就是1音诫,插入N條返回N惨奕。
int result = stmt.executeUpdate(sql);
System.out.println("操作的結(jié)果:"+ result+"數(shù)據(jù)");
stmt.close();關(guān)閉stmt
dbUtil.close(conn);//關(guān)閉Connection
return result;
}
修改main方法如下:
public static void main(String... args) throws Exception{
Website website = new Website("Google", "http://www.google.com/", 1, "USA");
int result=addWebsite2(website);
System.out.println(result==1?"添加成功":"添加失敗");
}
其中更新、刪除操作進本相同竭钝,不同的是數(shù)據(jù)庫語句梨撞,這里就不過多的舉例子!
PreparedStatement實現(xiàn)增刪改
PreparedStatement是Statement的子接口香罐,屬于預(yù)處理操作卧波。與Statement不同的是,PreparedStatement在操作時庇茫,是先在數(shù)據(jù)表中準備好一條SQL語句港粱,但是此SQL語句的具體內(nèi)容暫時不設(shè)置,而是在之后進行設(shè)置港令。
插入
我們?nèi)匀皇褂蒙厦娴臄?shù)據(jù)庫啥容,使用PreparedStatement插入一個網(wǎng)站,具體如下:
DBUtil dbUtil = new DBUtil();
public static int addWebsite(Website website) throws Exception{
Connection conn = dbUtil.getConnection();
String sql = "INSERT INTO websites values(null, ?, ?, ?, ?)";
PreparedStatement pstmt = conn.preparedStatement(sql);
pstmt.setString(1, website.getName());//從1開始
pstmt.setString(2, website.getUrl());
pstmt.setInt(3, website.getAlexa());
pstmt.setString(4, website.getCountry());
//所以SQL語句的四個問號分別對應(yīng)name顷霹、URL咪惠、alexa、country
int result = conn.executeUpdate();
dbUtil.close(pstmt, conn);
return result;
}
更新
現(xiàn)在數(shù)據(jù)庫中已經(jīng)存在了(id=1淋淀,name=Google遥昧,url="https://www.google.com/",alexa=1,country=USA)的數(shù)據(jù)了。如果我們要將更改這條數(shù)據(jù)朵纷,用PreparedStatement接口應(yīng)該怎么做呢炭臭?
DBUtil dbUtil = new DBUtil();
public static int updateWebsite(Website website) throws Exception{
Connection conn = dbUtil.getConnection();
String sql ="UPDATE websites set name=?,url=?,alexa=?,country=? where id=?";
PreparedStatement pstmt = conn.preparedStatement(sql);
pstmt.putString(1, website.getName());
pstmt.putString(2, website.getUrl());
pstmt.putInt(3, website.getAlexa());
pstmt.putString(4, website.getCountry());
pstmt.putInt(5, website.getId());
int result =pstmt.executeUpdate();
dbUtil.close(pstmt, conn);
return result;
}
刪除
假設(shè)我們要刪除id=1的數(shù)據(jù),代碼如下:
DBUtil dbUtil = new DBUtil();
//刪除指定id的websiste
public static int deleteWebsite(int id) throws Exception{
Connection conn = dbUtil.getConnection();
String deleteSql ="DELETE from websites where id=?";
PreparedStatement pstmt = conn.preparedStatement(deleteSql);
pstmt.putInt(1, id);
int result=pstmt.executeUpdate();
dbUtil.close(pstmt, conn);
return result;
}
ResultSet結(jié)果集
當我們查詢數(shù)據(jù)庫時袍辞,返回的是一個二維的結(jié)果集鞋仍,我們需要使用ResultSet來遍歷結(jié)果,獲取每一行的數(shù)據(jù)搅吁。
ResultSet光標最初位于第一行之前威创,第一次調(diào)用next()方法使第一行成為當前行,第二次調(diào)用使第二行成為當前行谎懦,以此類推肚豺。
- boolean next():將光標從當期位置向前移一行;
- String getString(int columnIndex):以Java編程語言中String的形式獲取ResultSet對象的當前行中指定列的值界拦;
- String getString(int columnLabel):以Java編程語言中String的形式獲取ResultSet對象的當前行中指定列的值吸申。
實戰(zhàn)
DBUtil dbUtil = new DBUtil();
public List<Website> queryAllWebsite()throws Exception{
Connection conn = dbUtil.getConnection();
String querySql = "SELECT * from websites";
PreparedStatement pstmt = conn.preparedStatement(querySql);
ResultSet rs = pstmt.executeQuery();//executeQuery執(zhí)行查詢,返回ResultSet結(jié)果集
List<Website> websites = new ArrayList();
while(rs.next()){
int id = rs.getInt(1);//獲取第一列的值:id
String name=rs.getString(2);//獲取第二列的值:name
String url=rs.getString(3);//獲取第三列的值:url
int alexa=rs.getInt(4);//獲取第四列的值:alexa
String country=rs.getString(5);//獲取第五列的值:country
Website website=new Website(id, name, url, alexa, country);
websites.add(website);
}
dbUtil.close(pstmt, conn);
return websites;
}
在while語句中我們也可以直接使用字段名稱進行查詢,操作如下:
int id = rs.getInt("id");
String name=rs.getString("name");
String url=rs.getString("url");
int alexa=rs.getInt("alexa");
String country=rs.getString("country");
處理大數(shù)據(jù)對象
- CLOB:(character large object)可以存儲字符大數(shù)據(jù)對象截碴,比如長篇小說梳侨;
- BLOB:(binary large object)可以存放二進制大數(shù)據(jù)對象,比如圖片隐岛、電影猫妙、音樂;
CLOB實戰(zhàn)
因為CLOB是存儲字符大數(shù)據(jù)對象聚凹,我們可以設(shè)置字段的類型為TEXT或者LONGTEXT
- 在websites表中增加
about
字段割坠,字段類型為longtext - 假設(shè)在電腦的C盤根目錄下新增一個helloworld.txt文本文件,內(nèi)容不限妒牙;
- 在Website類中增加類型為File的about字段彼哼。
實例代碼如下:
public class TestClob{
DBUtil dbUtil = new DBUtil();
//插入網(wǎng)站
public int addWebsite(Website website) throws Exception{
Connection conn = dbUtil.getConnection();
String sql = "INSERT INTO websites values(null, ?,?,?,?,?)";//多了about的?
PreparedStatement pstmt = conn.preparedStatement(sql);
pstmt.putString(1, website.getName());
pstmt.putString(2, website.getUrl());
pstmt.putInt(3, website.getAlexa());
pstmt.putString(4, website.getCountry());
File about=website.getAbout();
InputStream in = new FileInputStream(about);
pstmt.setAsciiStream(5, in, in.length());//這里不是putString而是setAsciiStream
int result=pstmt.excuteUpdate();
dbUtil.close(pstmt, conn);
return result;
}
//讀取網(wǎng)站
public void queryWebsite(int id)throws Exception{
Connection conn = dbUtil.getConnection();
String sql="SELECT * from websites where id=?";
PreparedStatement pstmt = conn.preparesStatement(sql);
pstmt.putString(1, id);
ResultSet rs = pstmt.excuteQuery();
while(rs.next()){
int id = rs.getInt("id");//依次獲取name|country|alexa|url即可
Clob clob = rs.getClob("about");//先通過getClob獲取Clob對象
String about=clob.getSubString(1,(int)clob.length());//利用getSubString獲取內(nèi)容
//也可以通過getAsciiStream流的形式獲取內(nèi)容
System.out.println("id:"+id+"\tabout:" + about);
}
dbUtil.close(pstmt, conn);
}
public static void main(String... args) throws Exception{
Website website = new Website("Google","http://www.google.com/", 1, "USA");
File fAbout = new File("C:/helloworld.txt");
website.setAbout(fAbout);//設(shè)置文件
int result = addWebsite(website);
System.out.println(result==1?"添加成功":"添加失敗");
}
}
BLOB實戰(zhàn)
因為Blob是存儲二級制文件,包括圖片湘今、音樂等敢朱,數(shù)據(jù)庫中可以申明為BLOB、LONGBLOB類型摩瞎。
- 在websites表中增加logo字段(網(wǎng)站logo)拴签,字段類型為BLOB;
- 假設(shè)在電腦的C盤根目錄下存放網(wǎng)站logo的圖片名為logo.jpg旗们;
- 在Website類中增加類型為File的logo字段
實例代碼如下:
public class TestClob{
DBUtil dbUtil = new DBUtil();
//插入網(wǎng)站
public int addWebsite(Website website) throws Exception{
Connection conn = dbUtil.getConnection();
String sql = "INSERT INTO websites values(null, ?,?,?,?,?,?)";//多了一個logo的?
PreparedStatement pstmt = conn.preparedStatement(sql);
pstmt.putString(1, website.getName());
pstmt.putString(2, website.getUrl());
pstmt.putInt(3, website.getAlexa());
pstmt.putString(4, website.getCountry());
File about=website.getAbout();
InputStream in = new FileInputStream(about);
pstmt.setAsciiStream(5, in, in.length());//這里不是putString而是setAsciiStream
File logo = website.getLogo();
InputStream logoIn= new FileInputStream(logo);
pstmt.setBinaryStream(6, logoIn, logoIn.length());//這里是setBinaryStream
int result=pstmt.excuteUpdate();
dbUtil.close(pstmt, conn);
return result;
}
//讀取網(wǎng)站
public void queryWebsite(int id)throws Exception{
Connection conn = dbUtil.getConnection();
String sql="SELECT * from websites where id=?";
PreparedStatement pstmt = conn.preparesStatement(sql);
pstmt.putString(1, id);
ResultSet rs = pstmt.excuteQuery();
while(rs.next()){
int id = rs.getInt("id");//依次獲取name|country|alexa|url即可
//獲取about
Clob clob = rs.getClob("about");//先通過getClob獲取Clob對象
String about=clob.getSubString(1,(int)clob.length());//利用getSubString獲取內(nèi)容
//獲取網(wǎng)站logo
Blob bLogo = rs.getBlob("logo");
//定義文件輸出流蚓哩,將文件保存到D盤根目錄
FileOutputStream out=new FileOutputStream(new File("D:/logo.jpg"));
out.write(bLogo.getBytes(1, ((int)bLogo).length()));
out.close();
System.out.println("id:"+id+"\tabout:" + about);
}
dbUtil.close(pstmt, conn);
}
public static void main(String... args) throws Exception{
Website website = new Website("Google","http://www.google.com/", 1, "USA");
File fAbout = new File("C:/helloworld.txt");
website.setAbout(fAbout);//設(shè)置文件
File fLogo = new File("C:/logo.jpg");
website.setLogo(fLogo);
int result = addWebsite(website);
System.out.println(result==1?"添加成功":"添加失敗");
}
}
CallableStatement調(diào)用存儲過程
CallableStatement主要是調(diào)用數(shù)據(jù)庫中的存儲過程,是PreparedStatement的子接口上渴。使用CallableStatement可以接收存儲過程的返回值岸梨。
- void registerOutParameter(int parameterIndex, int sqlType):按順序為止parameterIndex將OUT參數(shù)注冊為JDBC類型sqlType
首先添加存儲過程,根據(jù)website的id獲取website的名稱稠氮,操作如下:
//創(chuàng)建存儲過程
DELIMITER &&
CREATE PROCEDURE pro_getSiteNameById(IN siteId INT, OUT sn CHAR(20))
BEGIN
select name into sn from websites where id=siteId;
END
&&
DELIMITER ;
//測試存儲過程
CALL pro_getSiteNameById(1, @siteName);//執(zhí)行
SELECT @siteName;//輸出
CallableStatement調(diào)用存儲過程曹阔,代碼如下:
public class TestProcedure{
DBUtil dbUtil = new DBUtil();
public String getSiteNameById(int siteId) throws Exception{
Connection conn = dbUtil.getConnection();
String sql="{CALL pro_getSiteNameById(?,?)}";
CallableStatement cstmt = conn.prepareCall(sql);//prepareCall得到CallableStatement
cstmt.setInt(1, siteId);//設(shè)置第一個參數(shù)
cstmt.registerOutParameter(2, Types.CHAR);//注冊第二個參數(shù)類型
cstmt.execute();
String siteName = cstmt.getString("sn");
dbUtil.close(cstmt,conn);
return siteName;
}
}
使用元數(shù)據(jù)分析數(shù)據(jù)庫
- DatabaseMetaData:獲取數(shù)據(jù)庫基本信息,包括版本隔披、名稱以及表信息等
- String getDatabaseProductName():數(shù)據(jù)庫產(chǎn)品的名稱
- int getDriverMajorVersion():獲取JDBC驅(qū)動的主版本號
- int getDriverMinorVersion():獲取JDBC驅(qū)動的次版本號
- String getDriverName:獲取驅(qū)動名稱
- ResultSetMetaData:獲取ResultSet對象中列的基本信息
- int getColumnCount():返回此ResultSet對象中的列數(shù)
- String getColumnName(int column):獲取指定列的名稱
- int getColumnTypeName(int column):獲取指定列的SQL類型名稱
實例代碼如下:
public void aboutDatabaseMetaData() throws Exception{
DBUtil dbUtil = new DBUtil();
Connection conn = dbUtil.getConnection();
DatabaseMetaData metaData = conn.getMetaData();//獲取元數(shù)據(jù)
System.out.println("數(shù)據(jù)庫名稱:"+ metaData.getDatabaseProductName());
System.out.println("數(shù)據(jù)庫主版本號:" + metaData.getDriverMajorVersion());
}
public void aboutResultMetaData() throws Exception{
DBUtil dbUtil = new DBUtil();
Connection conn = dbUtil.getConnection();
String sql ="select * from websites";
PreparedStatement pstmt = conn.preparedStatement();
ResultSet rs = pstmt.executeQuery(sql);
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
System.out.println("website 共有:" + columnCount+" 列");
for(int i=0; i<columnCount; i++)
{
System.out.println("第"+i+"列名稱為:" + metaData.getColumnName(i)
+"\t 列的類型為:" + metaData.getColumnTypeName(i));
}
}
JDBC處理事務(wù)
所謂事務(wù)就是所有的操作要么一起成功赃份,要么一起失敗。事務(wù)本身具備:
- 原子性:是不可再分割的最小單元奢米,相當于一個個小的數(shù)據(jù)庫操作芥炭,這些操作必須同時成功,如果一個失敗則一切操作將全部失斒鸦邸;
- 一致性:數(shù)據(jù)庫操作前后是一致的渺蒿,保證數(shù)據(jù)有效性痢士。如果事務(wù)正常操作則系統(tǒng)維持有效性,如果出現(xiàn)了錯誤,則回到最原始狀態(tài)怠蹂,也要維持有效性善延;
- 隔離性:多個事務(wù)可以同時進行且彼此之間無法訪問,只有當事務(wù)完成最終操作時城侧,才可以看到結(jié)果易遣;
- 持久性:事務(wù)完成后,它對系統(tǒng)的影響是持久的嫌佑。該修改即使出現(xiàn)致命的系統(tǒng)故障也將一直保持豆茫。
創(chuàng)建賬戶表
首先創(chuàng)建賬戶表account,包含id屋摇、賬戶名稱和賬戶余額揩魂,代碼如下:
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL DEFAULT '' COMMENT '站點名稱',
`accountBalance` INTEGER NOT NULL DEFAULT '0' COMMENT '賬戶余額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
然后插入兩個賬戶,分別為(張三炮温,1000)和(李四火脉,1000)。當張三向李四轉(zhuǎn)賬500的時候柒啤,其實出發(fā)兩個數(shù)據(jù)庫操作:
- 張三賬戶的余額變成500
- 李四賬戶的月變成1500
必須保證上述兩個操作同時成功倦挂,才能認為轉(zhuǎn)賬操作是成功,如果有一個失敗則必須回到轉(zhuǎn)賬操作之前的狀態(tài)(兩個人都是1000)担巩。
不用事務(wù)(存在風(fēng)險)
public class Test{
DBUtil dbUtil = new DBUtil();
/**
* 賬戶轉(zhuǎn)出
*/
public void transferOut(Connection conn, String accountName, int money) throws Exception{
String sql = "UPDATE account set accountBalance=accountBalance-? where name=?";
PreparedStatement pstmt = con.preparedStatement(sql);
pstmt.setInt(1, 500);
pstmt.setString(2, accountName);
pstmt.executUpdate();
}
/**
* 賬戶轉(zhuǎn)入
*/
public void transferIn(Connection conn, String accountName, int money) throws Exception{
String sql = "UPDATE account set accountBalance=accountBalance+? where name=?";
PreparedStatement pstmt = con.preparedStatement(sql);
pstmt.setInt(1, 500);
pstmt.setString(2, accountName);
pstmt.executUpdate();
}
public static void main(String... args) throws Exception{
Connection conn = dbUtil.getConnection();
int money=500;
transferOut(conn, "張三", money);
transferIn(conn, "李四", money);
dbUtil.close(conn);
}
}
以上操作是存在風(fēng)險的并不能保證兩個操作能夠同時成功方援。
采用事務(wù)操作
轉(zhuǎn)入和轉(zhuǎn)出操作與上面是一致的,主要是修改main方法兵睛,代碼如下:
public static void main(String... args){
Connection conn;
try{
conn = dbUtil.getConnection();
conn.setAutoCommit(false);//首先將自動提交進行關(guān)閉
System.out.println("張三開始向李四轉(zhuǎn)賬");
int money=500;
transferOut(conn, "張三", money);
transferIn(conn, "李四", money);
System.out.println("轉(zhuǎn)賬成功");
}catch(Exception e){
if(conn !=null){
try{
conn.rollback(); //當操作失敗的時候回滾到之前的狀態(tài)
}catch(Exception e){
e.printStackTrace();
}
}
}finally{
if(conn !=null){
try{
conn.commit();//提交事務(wù)
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
setAutoCommit肯骇、rollback、commit三個方法的結(jié)合實現(xiàn)了事務(wù)的操作祖很!
保存點
假設(shè)存在這種情況笛丙,張三將錢轉(zhuǎn)給第三方,由第三方轉(zhuǎn)給李四假颇,也就是張三負責轉(zhuǎn)胚鸯,至于李四是否收到并不關(guān)心。
那么我們可以使用保存點來實現(xiàn)這個功能笨鸡,具體操作如下:
public static void main(String... args){
Connection conn;
SavePoint sp;
try{
conn = dbUtil.getConnection();
conn.setAutoCommit(false);//首先將自動提交進行關(guān)閉
System.out.println("張三開始向李四轉(zhuǎn)賬");
int money=500;
transferOut(conn, "張三", money);
sp = conn.setSavePoint();//設(shè)置一個保存點
transferIn(conn, "李四", money);
System.out.println("轉(zhuǎn)賬成功");
}catch(Exception e){
if(conn !=null){
try{
//當操作失敗的時候回滾到sp這個保存點的位置
conn.rollback(sp);
}catch(Exception e){
e.printStackTrace();
}
}
}finally{
if(conn !=null){
try{
conn.commit();//提交事務(wù)
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}