JDBC

JDBC

JDBC(Java Data Base Connectivity, java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的javaAPI,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準(zhǔn),據(jù)此可以構(gòu)建更高級的工具和接口,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應(yīng)用程序。

  1. 加載驅(qū)動(dòng)
  2. 連接數(shù)據(jù)庫
  3. 使用語句操作數(shù)據(jù)庫
  4. 關(guān)閉數(shù)據(jù)庫連接,釋放資源

加載驅(qū)動(dòng)(以MySQL舉例)

Java連接MySQL需要驅(qū)動(dòng)包,最新下載地址為:http://dev.mysql.com/downloads/connector/j/,解壓后得到j(luò)ar庫文件,然后在對應(yīng)的項(xiàng)目中導(dǎo)入該庫文件。

MySQL8.0之前版本

驅(qū)動(dòng)名:com.mysql.jdbc.Driver
加載方式:Class.forName("com.mysql.jdbc.Driver")

MySQL8.0之后版本

驅(qū)動(dòng)名:com.mysql.cj.jdbc.Driver
加載方式:Class.forName("com.mysql.cj.jdbc.Driver")

連接關(guān)閉數(shù)據(jù)庫

  1. DriverManager:驅(qū)動(dòng)管理類,負(fù)責(zé)獲取數(shù)據(jù)庫的連接
    1. getConnection(String url, String user, String password):試圖建立給定數(shù)據(jù)庫的URL連接
  2. 數(shù)據(jù)庫連接地址格式:jdbc:mysql://IP:端口號(hào)/數(shù)據(jù)庫名稱
  3. Connection接口:與特定的數(shù)據(jù)庫連接(會(huì)話)
    1. createStatement():創(chuàng)建Statement執(zhí)行數(shù)據(jù)庫更刪改查等操作
    2. close():立即釋放此Connection對象的數(shù)據(jù)庫和JDBC資源,而不是等待他們被自動(dòng)釋放

舉個(gè)例子:

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ù)庫進(jìn)行設(shè)置
    static final String User ="root";
    static final String Password="123456";

    public static void main(String... args){
        Connection conn = null;
        try{
            //注冊JDBC驅(qū)動(dòng)
            Class.forName(JDBC_DRIVER);
            System.out.println("注冊驅(qū)動(dòng)成功");
            //連接數(shù)據(jù)庫并得到Connection會(huì)話
            conn = DriverManager.getConnection(DB_URL, User, Password);
        }catch(ClassNotFoundException e){
            e.printStackTrace();
            System.out.println("注冊驅(qū)動(dòng)失敗")
        }finally{
      //最后關(guān)閉Connection會(huì)話,釋放資源
            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ù)庫和資源,而不是等待該對象自動(dòng)關(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ù)庫進(jìn)行設(shè)置
    static final String User ="root";
    static final String Password="123456";

/**
 *獲取數(shù)據(jù)庫連接
 */
    public Connection getConnection() throws Exception{
            //注冊JDBC驅(qū)動(dòng)
            Class.forName(JDBC_DRIVER);
            //連接數(shù)據(jù)庫并得到Connection會(huì)話
            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();
        }
    }
  
  /**
   * 同時(shí)關(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 '站點(diǎn)名稱',
  `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,因?yàn)闉樗O(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
    //進(jìn)行SQL語句進(jìn)行插入,其中返回結(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語句中,但是實(shí)際開發(fā)的時(shí)候是由外部傳入的,我們可以進(jìn)行修改為:

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,因?yàn)闉樗O(shè)置了自增
        String sql="INSERT INTO `websites` VALUES (null, '"+name+"', '"+website+"', '"+alexa+"', '"+country+"')";
        Connection conn = dbUtil.getConnection();//獲取數(shù)據(jù)庫連接
        Statement stmt = conn.createStatement();//獲取Statement
    //進(jìn)行SQL語句進(jìn)行插入,其中返回結(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類,以對象的形式進(jìn)行操作,具體如下:

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,因?yàn)闉樗O(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
    //進(jìn)行SQL語句進(jìn)行插入,其中返回結(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?"添加成功":"添加失敗");
}

其中更新、刪除操作進(jìn)本相同,不同的是數(shù)據(jù)庫語句,這里就不過多的舉例子!

PreparedStatement實(shí)現(xiàn)增刪改

PreparedStatement是Statement的子接口,屬于預(yù)處理操作。與Statement不同的是,PreparedStatement在操作時(shí),是先在數(shù)據(jù)表中準(zhǔn)備好一條SQL語句,但是此SQL語句的具體內(nèi)容暫時(shí)不設(shè)置,而是在之后進(jìn)行設(shè)置。

插入

我們?nèi)匀皇褂蒙厦娴臄?shù)據(jù)庫,使用PreparedStatement插入一個(gè)網(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語句的四個(gè)問號(hào)分別對應(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è)我們要?jiǎng)h除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é)果集

當(dāng)我們查詢數(shù)據(jù)庫時(shí),返回的是一個(gè)二維的結(jié)果集,我們需要使用ResultSet來遍歷結(jié)果,獲取每一行的數(shù)據(jù)。

ResultSet光標(biāo)最初位于第一行之前,第一次調(diào)用next()方法使第一行成為當(dāng)前行,第二次調(diào)用使第二行成為當(dāng)前行,以此類推。

  1. boolean next():將光標(biāo)從當(dāng)期位置向前移一行;
  2. String getString(int columnIndex):以Java編程語言中String的形式獲取ResultSet對象的當(dāng)前行中指定列的值;
  3. String getString(int columnLabel):以Java編程語言中String的形式獲取ResultSet對象的當(dāng)前行中指定列的值。

實(shí)戰(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語句中我們也可以直接使用字段名稱進(jìn)行查詢,操作如下:

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)可以存儲(chǔ)字符大數(shù)據(jù)對象,比如長篇小說;
  • BLOB:(binary large object)可以存放二進(jìn)制大數(shù)據(jù)對象,比如圖片、電影、音樂;

CLOB實(shí)戰(zhàn)

因?yàn)镃LOB是存儲(chǔ)字符大數(shù)據(jù)對象,我們可以設(shè)置字段的類型為TEXT或者LONGTEXT

  1. websites表中增加about字段,字段類型為longtext
  2. 假設(shè)在電腦的C盤根目錄下新增一個(gè)helloworld.txt文本文件,內(nèi)容不限;
  3. 在Website類中增加類型為File的about字段。

實(shí)例代碼如下:

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實(shí)戰(zhàn)

因?yàn)锽lob是存儲(chǔ)二級制文件,包括圖片、音樂等,數(shù)據(jù)庫中可以申明為BLOB、LONGBLOB類型。

  1. 在websites表中增加logo字段(網(wǎng)站logo),字段類型為BLOB;
  2. 假設(shè)在電腦的C盤根目錄下存放網(wǎng)站logo的圖片名為logo.jpg;
  3. 在Website類中增加類型為File的logo字段

實(shí)例代碼如下:

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, ?,?,?,?,?,?)";//多了一個(gè)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)用存儲(chǔ)過程

CallableStatement主要是調(diào)用數(shù)據(jù)庫中的存儲(chǔ)過程,是PreparedStatement的子接口。使用CallableStatement可以接收存儲(chǔ)過程的返回值。

  • void registerOutParameter(int parameterIndex, int sqlType):按順序?yàn)橹筽arameterIndex將OUT參數(shù)注冊為JDBC類型sqlType

首先添加存儲(chǔ)過程,根據(jù)website的id獲取website的名稱,操作如下:

//創(chuàng)建存儲(chǔ)過程
DELIMITER &&
CREATE PROCEDURE pro_getSiteNameById(IN siteId INT, OUT sn CHAR(20))
BEGIN
    select name into sn from websites where id=siteId;
END
&&
DELIMITER ;

//測試存儲(chǔ)過程
CALL pro_getSiteNameById(1, @siteName);//執(zhí)行
SELECT @siteName;//輸出

CallableStatement調(diào)用存儲(chǔ)過程,代碼如下:

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è)置第一個(gè)參數(shù)
        cstmt.registerOutParameter(2, Types.CHAR);//注冊第二個(gè)參數(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ū)動(dòng)的主版本號(hào)
    • int getDriverMinorVersion():獲取JDBC驅(qū)動(dòng)的次版本號(hào)
    • String getDriverName:獲取驅(qū)動(dòng)名稱
  • ResultSetMetaData:獲取ResultSet對象中列的基本信息
    • int getColumnCount():返回此ResultSet對象中的列數(shù)
    • String getColumnName(int column):獲取指定列的名稱
    • int getColumnTypeName(int column):獲取指定列的SQL類型名稱

實(shí)例代碼如下:

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ù)庫主版本號(hào):" + 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. 原子性:是不可再分割的最小單元,相當(dāng)于一個(gè)個(gè)小的數(shù)據(jù)庫操作,這些操作必須同時(shí)成功,如果一個(gè)失敗則一切操作將全部失敗;
  2. 一致性:數(shù)據(jù)庫操作前后是一致的,保證數(shù)據(jù)有效性。如果事務(wù)正常操作則系統(tǒng)維持有效性,如果出現(xiàn)了錯(cuò)誤,則回到最原始狀態(tài),也要維持有效性;
  3. 隔離性:多個(gè)事務(wù)可以同時(shí)進(jìn)行且彼此之間無法訪問,只有當(dāng)事務(wù)完成最終操作時(shí),才可以看到結(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 '站點(diǎn)名稱',
  `accountBalance` INTEGER NOT NULL DEFAULT '0' COMMENT '賬戶余額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

然后插入兩個(gè)賬戶,分別為(張三,1000)和(李四,1000)。當(dāng)張三向李四轉(zhuǎn)賬500的時(shí)候,其實(shí)出發(fā)兩個(gè)數(shù)據(jù)庫操作:

  • 張三賬戶的余額變成500
  • 李四賬戶的月變成1500

必須保證上述兩個(gè)操作同時(shí)成功,才能認(rèn)為轉(zhuǎn)賬操作是成功,如果有一個(gè)失敗則必須回到轉(zhuǎn)賬操作之前的狀態(tài)(兩個(gè)人都是1000)。

不用事務(wù)(存在風(fēng)險(xiǎn))

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)險(xiǎn)的并不能保證兩個(gè)操作能夠同時(shí)成功。

采用事務(wù)操作

轉(zhuǎn)入和轉(zhuǎn)出操作與上面是一致的,主要是修改main方法,代碼如下:

public static void main(String... args){
  Connection conn;
  try{
    conn = dbUtil.getConnection();
    conn.setAutoCommit(false);//首先將自動(dòng)提交進(jìn)行關(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();  //當(dāng)操作失敗的時(shí)候回滾到之前的狀態(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三個(gè)方法的結(jié)合實(shí)現(xiàn)了事務(wù)的操作!

保存點(diǎn)

假設(shè)存在這種情況,張三將錢轉(zhuǎn)給第三方,由第三方轉(zhuǎn)給李四,也就是張三負(fù)責(zé)轉(zhuǎn),至于李四是否收到并不關(guān)心。

那么我們可以使用保存點(diǎn)來實(shí)現(xiàn)這個(gè)功能,具體操作如下:

public static void main(String... args){
  Connection conn;
  SavePoint sp;
  try{
    conn = dbUtil.getConnection();
    conn.setAutoCommit(false);//首先將自動(dòng)提交進(jìn)行關(guān)閉
    System.out.println("張三開始向李四轉(zhuǎn)賬");
    int money=500;
    transferOut(conn, "張三", money);
    sp = conn.setSavePoint();//設(shè)置一個(gè)保存點(diǎn)
    transferIn(conn, "李四", money);
    System.out.println("轉(zhuǎn)賬成功");
  }catch(Exception e){
    if(conn !=null){
      try{
        //當(dāng)操作失敗的時(shí)候回滾到sp這個(gè)保存點(diǎn)的位置
        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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容