JDBC

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)用程序。

  1. 加載驅(qū)動
  2. 連接數(shù)據(jù)庫
  3. 使用語句操作數(shù)據(jù)庫
  4. 關(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ù)庫

  1. DriverManager:驅(qū)動管理類冕碟,負責獲取數(shù)據(jù)庫的連接
    1. getConnection(String url, String user, String password):試圖建立給定數(shù)據(jù)庫的URL連接
  2. 數(shù)據(jù)庫連接地址格式:jdbc:mysql://IP:端口號/數(shù)據(jù)庫名稱
  3. Connection接口:與特定的數(shù)據(jù)庫連接(會話)
    1. createStatement():創(chuàng)建Statement執(zhí)行數(shù)據(jù)庫更刪改查等操作
    2. 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)

  1. 作用:用于執(zhí)行靜態(tài)SQL語句并返回它所生成結(jié)果的對象挑庶;
  2. int executeUpdate(String sql)執(zhí)行給定SQL語句,該語句可能為INSERT软能、UPDATE或DELETE語句迎捺,或者不返回任何內(nèi)容的SQL語句(如SQL DDL 語句)
  3. 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)用使第二行成為當前行谎懦,以此類推肚豺。

  1. boolean next():將光標從當期位置向前移一行;
  2. String getString(int columnIndex):以Java編程語言中String的形式獲取ResultSet對象的當前行中指定列的值界拦;
  3. 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

  1. websites表中增加about字段割坠,字段類型為longtext
  2. 假設(shè)在電腦的C盤根目錄下新增一個helloworld.txt文本文件,內(nèi)容不限妒牙;
  3. 在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類型摩瞎。

  1. 在websites表中增加logo字段(網(wǎng)站logo)拴签,字段類型為BLOB;
  2. 假設(shè)在電腦的C盤根目錄下存放網(wǎng)站logo的圖片名為logo.jpg旗们;
  3. 在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ù)本身具備:

  1. 原子性:是不可再分割的最小單元奢米,相當于一個個小的數(shù)據(jù)庫操作芥炭,這些操作必須同時成功,如果一個失敗則一切操作將全部失斒鸦邸;
  2. 一致性:數(shù)據(jù)庫操作前后是一致的渺蒿,保證數(shù)據(jù)有效性痢士。如果事務(wù)正常操作則系統(tǒng)維持有效性,如果出現(xiàn)了錯誤,則回到最原始狀態(tài)怠蹂,也要維持有效性善延;
  3. 隔離性:多個事務(wù)可以同時進行且彼此之間無法訪問,只有當事務(wù)完成最終操作時城侧,才可以看到結(jié)果易遣;
  4. 持久性:事務(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();
      }
    }
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姜钳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子形耗,更是在濱河造成了極大的恐慌哥桥,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件激涤,死亡現(xiàn)場離奇詭異拟糕,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門送滞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侠草,“玉大人,你說我怎么就攤上這事犁嗅”咛椋” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵褂微,是天一觀的道長功蜓。 經(jīng)常有香客問我,道長蕊梧,這世上最難降的妖魔是什么霞赫? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮肥矢,結(jié)果婚禮上端衰,老公的妹妹穿的比我還像新娘甘改。我一直安慰自己旅东,他們只是感情好十艾,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忘嫉,像睡著了一般荤牍。 火紅的嫁衣襯著肌膚如雪庆冕。 梳的紋絲不亂的頭發(fā)上康吵,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天访递,我揣著相機與錄音晦嵌,去河邊找鬼。 笑死拷姿,一個胖子當著我的面吹牛惭载,可吹牛的內(nèi)容都是我干的响巢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼含长,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜈出,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎偷厦,沒想到半個月后燕刻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡请唱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年过蹂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片本橙。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡脆诉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出击胜,到底是詐尸還是另有隱情,我是刑警寧澤骚揍,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布啰挪,位于F島的核電站,受9級特大地震影響亡呵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锰什,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一丁逝、第九天 我趴在偏房一處隱蔽的房頂上張望梭姓。 院中可真熱鬧,春花似錦誉尖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柬甥,卻和暖如春涡驮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捉捅。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工棒口, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人无牵。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像克懊,于是被迫代替她去往敵國和親七蜘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344