J2EE梳理(七)——關於JDBC進階


來源於how2j

特殊操作

獲取自增長ID

  • 在Statement通過execute或者executeUpdate執行完插入語句後,MySQL會爲新插入的數據分配一個自增長id,(前提是這個表的id設置爲了自增長,在Mysql創建表的時候,AUTO_INCREMENT就表示自增長)
    CREATE TABLE hero ( id int(11) AUTO_INCREMENT, ... }
  • 但是無論是execute還是executeUpdate都不會返回這個自增長id是多少。需要通過Statement的getGeneratedKeys獲取該id
    注: 第20行的代碼,後面加了個Statement.RETURN_GENERATED_KEYS參數,以確保會返回自增長ID。 通常情況下不需要加這個,有的時候需要加,所以先加上,保險一些PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
  • 代碼:
package jdbc;
   
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
   
public class TestJDBC {
   
    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
         String sql = "insert into hero values(null,?,?,?)";
        try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
                PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);          
                ) {
  
            ps.setString(1, "蓋倫");
            ps.setFloat(2, 616);
            ps.setInt(3, 100);
   
            // 執行插入語句
            ps.execute();
   
            // 在執行完插入語句後,MySQL會爲新插入的數據分配一個自增長id
            // JDBC通過getGeneratedKeys獲取該id
            ResultSet rs = ps.getGeneratedKeys();
            if (rs.next()) {
                int id = rs.getInt(1);
                System.out.println(id);
            }
   
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   
    }
}
  • res:在這裏插入圖片描述
    在這裏插入圖片描述
  • 不加Statement.RETURN_GENERATED_KEYS 也可
    在這裏插入圖片描述

獲取表的元數據

  • 數據庫服務器相關的數據,比如數據庫版本,有哪些表,表有哪些字段,字段類型是什麼等等。
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");) {
  
            // 查看數據庫層面的元數據
            // 即數據庫服務器版本,驅動版本,都有哪些數據庫等等
  
            DatabaseMetaData dbmd = c.getMetaData();
  
            // 獲取數據庫服務器產品名稱
            System.out.println("數據庫產品名稱:\t"+dbmd.getDatabaseProductName());
            // 獲取數據庫服務器產品版本號
            System.out.println("數據庫產品版本:\t"+dbmd.getDatabaseProductVersion());
            // 獲取數據庫服務器用作類別和表名之間的分隔符 如test.user
            System.out.println("數據庫和表分隔符:\t"+dbmd.getCatalogSeparator());
            // 獲取驅動版本
            System.out.println("驅動版本:\t"+dbmd.getDriverVersion());
  
            System.out.println("可用的數據庫列表:");
            // 獲取數據庫名稱
            ResultSet rs = dbmd.getCatalogs();
  
            while (rs.next()) {
                System.out.println("數據庫名稱:\t"+rs.getString(1));
            }
  
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

事務

  • 沒有事務的情況:
假設業務操作是:加血,減血各做一次
結束後,英雄的血量不變
而減血的SQL
不小心寫錯寫成了 updata(而非update)
那麼最後結果是血量增加了,而非期望的不變
  • 代碼:
package jdbc;
  
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
  
public class TestJDBC {
    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
 
        try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
            Statement s = c.createStatement();) {
  
            //沒有事務的前提下
            //假設業務操作時,加血,減血各做一次
            //結束後,英雄的血量不變
              
            //加血的SQL
            String sql1 = "update hero set hp = hp +1 where id = 22";
            s.execute(sql1);
              
            //減血的SQL
            //不小心寫錯寫成了 updata(而非update)
              
            String sql2 = "updata hero set hp = hp -1 where id = 22";
            s.execute(sql2);
  
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
  
    }
}
  • res:在這裏插入圖片描述
  • 在這裏插入圖片描述

使用事務

  • 在事務中的多個操作,要麼都成功,要麼都失敗;通過 c.setAutoCommit(false);關閉自動提交;使用 c.commit();進行手動提交
    在22行-35行之間的數據庫操作,就處於同一個事務當中,要麼都成功,要麼都失敗所以,雖然第一條SQL語句是可以執行的,但是第二條SQL語句有錯誤,其結果就是兩條SQL語句都沒有被提交。 除非兩條SQL語句都是正確的。
  • 代碼:
package jdbc;
  
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
  
public class TestJDBC {
    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
 
        try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
            Statement s = c.createStatement();) {
  
            // 有事務的前提下
            // 在事務中的多個操作,要麼都成功,要麼都失敗
  
            c.setAutoCommit(false);
  
            // 加血的SQL
            String sql1 = "update hero set hp = hp +1 where id = 22";
            s.execute(sql1);
  
            // 減血的SQL
            // 不小心寫錯寫成了 updata(而非update)
  
            String sql2 = "updata hero set hp = hp -1 where id = 22";
            s.execute(sql2);
  
            // 手動提交
            c.commit();
  
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
  
    }
}
  • res: 血量沒變:在這裏插入圖片描述
  • MYSQL 表的類型必須是INNODB才支持事務
    在Mysql中,只有當表的類型是INNODB的時候,才支持事務,所以需要把表的類型設置爲INNODB,否則無法觀察到事務.
  • 修改表的類型爲INNODB的SQL:alter table hero ENGINE = innodb;
  • 查看錶的類型的SQL:show table status from how2java;

事務應用:

  • 設計一個代碼,刪除數據前在控制檯彈出一個提示:
是否要刪除數據(Y/N)
如果用戶輸入Y,則刪除
如果輸入N則不刪除。
如果輸入的既不是Y也不是N,則重複提示
  • 代碼:
public static void main(String[] args) {
        Scanner w = new Scanner(System.in);
 
        Connection c = null;
        Statement s1 = null;
        Statement s2 = null;
        try {
            // 啓動數據庫驅動
            Class.forName("com.mysql.jdbc.Driver");
            // 建立數據庫連接
            c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
                    "admin");
            // 根據sql獲取Statement對象
            s1 = c.createStatement();
            s2 = c.createStatement();
            // 關閉自動提交
            c.setAutoCommit(false);
            // 查詢出前十條,並獲取前十條id
            String sql = "select * from hero limit 0,10";
            ResultSet rs = s1.executeQuery(sql);
            while (rs.next()) {
                int id = rs.getInt("id");
                System.out.println("是否刪除id =" + id + "的數據");
                String sql1 = "delete from hero where id = " + id;
                s2.execute(sql1);
            }
 
            // 判斷是否刪除
            while (true) {
                System.out.println("是否刪除數據(Y/N)");
                String a = w.next();
                if (a.equals("Y")) {
                    // 手動提交
                    c.commit();
                    System.out.println("刪除成功");
                    break;
                }
                if (a.equals("N")) {
                    System.out.println("放棄刪除");
                    break;
                }
            }
 
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if (s1 != null) {
                try {
                    s1.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (s1 != null) {
                try {
                    s1.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if (c != null) {
                try {
                    c.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
  • 注意:這是因爲創建兩個對象的原因是Resultset rs 對象再rs.next()循環結束之後,會將第一個對象閉合,比如如果之後的statement對象繼續使用st4Query,而不是st4Delete的話,會提示你st4Query因爲這個循環已經被閉合,沒有辦法再次使用了

ORM映射

  • ORM=Object Relationship Database Mapping
    對象和關係數據庫的映射
    簡單說,一個對象,對應數據庫裏的一條記錄

JDBC DAO

  • DAO=DataAccess Object,即數據訪問對象
  • 實際上就是運用了ORM中的思路,把數據庫相關的操作都封裝在這個類裏面,其他地方看不到JDBC的代碼
  • DAO接口:
package jdbc;
  
import java.util.List;
 
import charactor.Hero;
  
public interface DAO{
    //增加
    public void add(Hero hero);
    //修改
    public void update(Hero hero);
    //刪除
    public void delete(int id);
    //獲取
    public Hero get(int id);
    //查詢
    public List<Hero> list();
    //分頁查詢
    public List<Hero> list(int start, int count);
}
  • HeroDAO類,實現接口DAO
  • 代碼:
package jdbc;
  
import java.sql.Connection;
 
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
  
import charactor.Hero;
  
public class HeroDAO implements DAO{
  
    public HeroDAO() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
  
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
                "admin");
    }
  
    public int getTotal() {
        int total = 0;
        try (Connection c = getConnection(); Statement s = c.createStatement();) {
  
            String sql = "select count(*) from hero";
  
            ResultSet rs = s.executeQuery(sql);
            while (rs.next()) {
                total = rs.getInt(1);
            }
  
            System.out.println("total:" + total);
  
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
        return total;
    }
  
    public void add(Hero hero) {
  
        String sql = "insert into hero values(null,?,?,?)";
        try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
  
            ps.setString(1, hero.name);
            ps.setFloat(2, hero.hp);
            ps.setInt(3, hero.damage);
  
            ps.execute();
  
            ResultSet rs = ps.getGeneratedKeys();
            if (rs.next()) {
                int id = rs.getInt(1);
                hero.id = id;
            }
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
    }
  
    public void update(Hero hero) {
  
        String sql = "update hero set name= ?, hp = ? , damage = ? where id = ?";
        try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
  
            ps.setString(1, hero.name);
            ps.setFloat(2, hero.hp);
            ps.setInt(3, hero.damage);
            ps.setInt(4, hero.id);
  
            ps.execute();
  
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
  
    }
  
    public void delete(int id) {
  
        try (Connection c = getConnection(); Statement s = c.createStatement();) {
  
            String sql = "delete from hero where id = " + id;
  
            s.execute(sql);
  
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
    }
  
    public Hero get(int id) {
        Hero hero = null;
  
        try (Connection c = getConnection(); Statement s = c.createStatement();) {
  
            String sql = "select * from hero where id = " + id;
  
            ResultSet rs = s.executeQuery(sql);
  
            if (rs.next()) {
                hero = new Hero();
                String name = rs.getString(2);
                float hp = rs.getFloat("hp");
                int damage = rs.getInt(4);
                hero.name = name;
                hero.hp = hp;
                hero.damage = damage;
                hero.id = id;
            }
  
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
        return hero;
    }
  
    public List<Hero> list() {
        return list(0, Short.MAX_VALUE);
    }
  
    public List<Hero> list(int start, int count) {
        List<Hero> heros = new ArrayList<Hero>();
  
        String sql = "select * from hero order by id desc limit ?,? ";
  
        try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
  
            ps.setInt(1, start);
            ps.setInt(2, count);
  
            ResultSet rs = ps.executeQuery();
  
            while (rs.next()) {
                Hero hero = new Hero();
                int id = rs.getInt(1);
                String name = rs.getString(2);
                float hp = rs.getFloat("hp");
                int damage = rs.getInt(4);
                hero.id = id;
                hero.name = name;
                hero.hp = hp;
                hero.damage = damage;
                heros.add(hero);
            }
        } catch (SQLException e) {
  
            e.printStackTrace();
        }
        return heros;
    }
  
}

數據庫連接池

數據庫連接-傳統方式

  • 當有多個線程,每個線程都需要連接數據庫執行SQL語句的話,那麼每個線程都會創建一個連接,並且在使用完畢後,關閉連接。
  • 創建連接和關閉連接的過程也是比較消耗時間的,當多線程併發的時候,系統就會變得很卡頓。
  • 同時,一個數據庫同時支持的連接總數也是有限的,如果多線程併發量很大,那麼數據庫連接的總數就會被消耗光,後續線程發起的數據庫連接就會失敗

數據庫連接池原理-使用池

  • 與傳統方式不同,連接池在使用之前,就會創建好一定數量的連接。如果有任何線程需要使用連接,那麼就從連接池裏面借用,而不是自己重新創建.
  • 使用完畢後,又把這個連接歸還給連接池供下一次或者其他線程使用。
  • 倘若發生多線程併發情況,連接池裏的連接被借用光了,那麼其他線程就會臨時等待,直到有連接被歸還回來,再繼續使用。
  • 整個過程,這些連接都不會被關閉,而是不斷的被循環使用,從而節約了啓動和關閉連接的時間。
  • 在這裏插入圖片描述

ConnectionPool

  • ConnectionPool() 構造方法約定了這個連接池一共有多少連接

  • 在init() 初始化方法中,創建了size條連接。 注意,這裏不能使用try-with-resource這種自動關閉連接的方式,因爲連接恰恰需要保持不關閉狀態,供後續循環使用

  • getConnection, 判斷是否爲空,如果是空的就wait等待,否則就借用一條連接出去

  • returnConnection, 在使用完畢後,歸還這個連接到連接池,並且在歸還完畢後,調用notifyAll,通知那些等待的線程,有新的連接可以借用了。

  • 代碼實現:

package jdbc;
  
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
  
public class ConnectionPool {
  
    List<Connection> cs = new ArrayList<Connection>();
  
    int size;
  
    public ConnectionPool(int size) {
        this.size = size;
        init();
    }
  
    public void init() {
          
        //這裏恰恰不能使用try-with-resource的方式,因爲這些連接都需要是"活"的,不要被自動關閉了
        try {
            Class.forName("com.mysql.jdbc.Driver");
            for (int i = 0; i < size; i++) {
                Connection c = DriverManager
                        .getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin");
  
                cs.add(c);
  
            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
  
    public synchronized Connection getConnection() {
        while (cs.isEmpty()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        Connection c = cs.remove(0);
        return c;
    }
  
    public synchronized void returnConnection(Connection c) {
        cs.add(c);
        this.notifyAll();
    }
  
}
  • 測試類:首先初始化一個有3條連接的數據庫連接池
    然後創建100個線程,每個線程都會從連接池中借用連接,並且在借用之後,歸還連接。 拿到連接之後,執行一個耗時1秒的SQL語句。
package jdbc;
  
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
 
import jdbc.ConnectionPool;
   
public class TestConnectionPool {
   
    public static void main(String[] args) {
        ConnectionPool cp = new ConnectionPool(3);
        for (int i = 0; i < 100; i++) {
            new WorkingThread("working thread" + i, cp).start();
        }
   
    }
}
   
class WorkingThread extends Thread {
    private ConnectionPool cp;
   
    public WorkingThread(String name, ConnectionPool cp) {
        super(name);
        this.cp = cp;
    }
   
    public void run() {
        Connection c = cp.getConnection();
        System.out.println(this.getName()+ ":\t 獲取了一根連接,並開始工作"  );
        try (Statement st = c.createStatement()){
             
            //模擬時耗1秒的數據庫SQL語句
            Thread.sleep(1000);
            st.execute("select * from hero");
   
        } catch (SQLException | InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        cp.returnConnection(c);
    }
}
發佈了117 篇原創文章 · 獲贊 59 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章