1. 程式人生 > 其它 >JDBC連線操作資料庫

JDBC連線操作資料庫

1.1 Driver:

Java.sql.Driver 介面是所有 JDBC 驅動程式需要實現的介面。這個介面是提供給資料庫廠商使用的,不同資料庫廠商提供不同的實現

在程式中不需要直接去訪問實現了 Driver 介面的類,而是由驅動程式管理器類(java.sql.DriverManager)去呼叫這些Driver實現

操作步驟:

1.2 四種連結方式

方式一:以下生成的Driver是固定mysql的,方式二:可以使用反射根據傳入的動態生成各種資料庫驅動

@Test
public void test1() throws SQLException {
    //   JDBC的連線測試
    // 1. driver  // 每個資料庫驅動程式需要對Driver介面進行實現
    Driver driver = new com.mysql.jdbc.Driver();

    // 2. url:  jdbc:mysql://127.0.0.1:3306/mybatis
    String url = "jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true";

    // 3. 封裝屬性
    Properties pro = new Properties();
    pro.setProperty("user","root");
    pro.setProperty("password","password");

    //  driver獲取資料庫的連線
    Connection connect = driver.connect(url, pro);
    System.out.println(connect);
    connection.close();
}

DriverManager 靜態

方式三:利用DriverManager替換Driver

@Test
public void test2()throws Exception{
    // 使用DriverManager替換driver
    Class<?> cla = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver) cla.newInstance();

    String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true";
    String user = "root";
    String password = "password";
    //  註冊Driver
    DriverManager.registerDriver(driver);

    Connection connection = DriverManager.getConnection(url, user, password);
    System.out.println(connection);
    connection.close();
}

方式四: // 在方式三的基礎上,多部分程式碼預設已經完成了

@Test
public void test2()throws Exception{
    // 使用DriverManager替換driver
    /*Class<?> cla = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver) cla.newInstance();*/

    String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true";
    String user = "root";
    String password = "password";
    //  註冊Driver
    //DriverManager.registerDriver(driver);

    Connection connection = DriverManager.getConnection(url, user, password);
    System.out.println(connection);
    connection.close();
}

1. 連線資料庫最終版

方式五: 配置資訊放在配置檔案中,程式碼讀取

 @Test
    public void test3()throws Exception{
        // 讀取配置檔案,載入配置資訊

        Properties pro = new Properties();
        // 獲取配置檔案流,FileInputStream預設在當前專案下, 類載入器預設在src下或者resource資源目錄下
        //FileInputStream fis = new FileInputStream("src\\properties.properties"); 不行,找不到resource目錄下的資源
        // 類載入器載入
        ClassLoader loader = JdbcTest.class.getClassLoader();
        InputStream fis = loader.getResourceAsStream("properties.properties");

        pro.load(fis);
        String url = pro.getProperty("url");
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");
        String driv = pro.getProperty("driver");

        Class<?> cla = Class.forName(driv);
        Driver driver = (Driver) cla.newInstance();

        //  註冊Driver
        DriverManager.registerDriver(driver);

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
        connection.close();
        fis.close();
    }

1.4 Statement

弊端:SQL注入問題:由於Statement的sql語句是拼接在,在輸入資料時,輸入資料有可能拼接成sql的關鍵字,導致出現sql語句執行出現異常

如何避免SQL注入問題:使用PreparedStatement替換Statement

@Test
public void test4() throws Exception {
    // statement 執行sql語句
    //  自定義的JDBC工具類獲取資料庫連線
    Connection connection = JDBCUtils.getConnection();
    System.out.println(connection);
    // 4.建立sql語句的執行物件Statement
    Statement statement = connection.createStatement();
    // 5.用statement物件執行sql語句,可能有返回的結果集(連結串列的形式)
    //    執行查詢語句用executeQuery(),執行插入、刪除、修改用executeUpdate()
    //    execute(),增刪改查的sql都能執行,有判斷效率會低點
    ResultSet resultSet = statement.executeQuery("select *from mybatis.books");
    while (resultSet.next()){
        System.out.print(resultSet.getInt("bookID")+"\t");    //在不知道型別的情況下可以用getObject()獲取
        System.out.print(resultSet.getObject("bookName")+"\t");// 裡面的引數要和資料庫一一對應
        System.out.println(resultSet.getObject("detail")+"\t");
    }
    // 6.釋放連線,關閉資源
    resultSet.close();
    statement.close();
    connection.close();
}

1.5 PreparedStatement

因為sql語句是預編譯的,而且語句中使用了佔位符,規定了sql語句的結構。使用者可以設定"?"的值,但是不能改變sql語句的結構,因此想在sql語句後面加上如“or 1=1”實現sql注入是行不通的。

connection.prepareStatement(sql); // 預編譯,得到後結構就固定了

優點:

  • 可以實更高效的批量操作

    • sql語句在預編譯後就會快取下來,繼續執行同樣的sql語句時,就不會有sql語句的校驗

      for(int i=0;i < 100;i++){
          pre.setObject("");
          pre.execute();
      }
      
    • 以上優化:sql只有值在變化,在多儲存一些值,後在提交執行插入操作

      1. addBatch( )、executeBatch( )、clearBatch( )
      2. mysq1伺服器預設是關閉批處理的,我們需要通過一個引數, 讓mysq1開啟批處理的支援。? rewriteBatchedstatements=true寫在配置檔案的ur1後面
      3. 3.使用更新的mysql 驅動: mysql-connector-java-5.1.37-bin.jar
      for(int i=0;i < 100;i++){
          pre.setObject("");
          // 1.攢sql
          pre.addBatch();
          if(i <=  10){
              // 執行bathch
              pre.executeBatch();
              //清空batch
              pre.clearBatch();
          }
      }
      

插入:

@Test
public void test5() throws SQLException {
   //  使用PreparedStatement避免Statement的sql注入問題
    //  使用了佔位符,解決sql注入問題

    //  自定義的JDBC工具類獲取資料庫連線
    Connection connection = JDBCUtils.getConnection();

    String sql = "INSERT into mybatis.books values(?,?,?,?)";
    PreparedStatement pre = connection.prepareStatement(sql);
    pre.setInt(1,31);
    pre.setString(2,"java神書");
    pre.setInt(3,30);
    pre.setString(4,"從入門到入獄");
    pre.execute();

    pre.close();
    connection.close();
}

修改:

@Test
public void test6()throws Exception{
    // 1. 獲取資料庫連線,此處是自定義的工具類
    Connection connection = JDBCUtils.getConnection();

    // 2. 預編譯sql語句,返回PrepareStatement
    String sql = "update books set bookID=? where bookID=?";
    PreparedStatement pre = connection.prepareStatement(sql);
    // 3. 填充佔位符
    pre.setInt(1,34);
    pre.setInt(2,31);
    // 3. 執行
    pre.execute();
    // 4. 資源的關閉
    pre.close();
    connection.close();
}

通用的執行sql的方法:利用可變形參,實現佔位符的填充

public void test7(String sql,Object ...data)throws Exception{
    // 1. 獲取資料庫連線
    Connection connection = JDBCUtils.getConnection();

    // 2. 預編譯sql語句,返回PrepareStatement
    PreparedStatement pre = connection.prepareStatement(sql);
    // 3. 填充佔位符
    for (int i = 0; i < data.length; i++) {
        pre.setObject(i+1,data[i]);
    }
    // 3. 執行
    pre.execute();
    // 4. 資源的關閉
    pre.close();
    connection.close();

}

查詢: 遍歷返回的結果集

getObject(int ) : 取出第幾個返回值的結果 (從1開始)

getObject(String ) :返回變數名是引數值得 結果

@Test
public void test9()throws Exception{
    //     查詢,返回結果集
    // 1. 獲取資料庫連線
    Connection connection = JDBCUtils.getConnection();

    // 2. 預編譯sql語句,返回PrepareStatement
    PreparedStatement pre = connection.prepareStatement("select * from mybatis.books");

    // 3. 執行
    ResultSet resultSet = pre.executeQuery();

    // 4. 遍歷返回的結果
        //方式一:
    while (resultSet.next()){        // 結果集中的next: 判斷指標的下一個位置是否有元素,返回boolean型資料,為true指標下移
        System.out.print(resultSet.getInt("bookID")+"\t\t");    //在不知道型別的情況下可以用getObject()獲取
        System.out.print(resultSet.getObject("bookName")+"\t\t");// 裡面的引數要和資料庫一一對應
        System.out.println(resultSet.getObject("detail")+"\t");
    }
    // 5. 資源的關閉
    pre.close();
    connection.close();

}

獲取返回的結果集中資料的列數,可以寫一個通用的查詢:

// 獲取返回資料,有多少列
        // 獲取返回值的元資料
        ResultSetMetaData metaData = resultSet.getMetaData();
        // 獲取有多少列
        int columnCount = metaData.getColumnCount();
		// 獲取第一列的列名
		String name = metaData.getColumnName(1);  
		// 獲取列的別名 , 沒有別名就返回列名
        String columnLabel = metaData.getColumnLabel(1);

1.6 別名解決欄位問題

針對資料庫欄位名,和java實體類的屬性名不一致的問題:

  • sql取別名: as

查詢: 結果對映,利用反射,通用查詢

public <T> List<T> test1(Class<T> clazz,String sql,Object ...args)throws Exception{
        /** 查詢:
         * 利用返回的結果集的元資料
         * 獲取返回的列數,列的別名,列名
         * 編寫一個通用(想查哪幾列資料就只顯示哪幾列資料)的查詢
         * 解決:資料庫欄位和java實體類屬性不一致問題
         * */
        // 1. 獲取資料庫連線
        Connection connection = JDBCUtils.getConnection();
        // 2. 獲取PrepareStatement 防止Statement的sql注入問題
        PreparedStatement pre = connection.prepareStatement(sql);
        // 3. 填充佔位符
        for (int i = 0; i < args.length; i++) {
            pre.setObject(i+1,args[i]);
        }
        // 4. 執行查詢,返回結果集
        ResultSet resultSet = pre.executeQuery();
        // 5. 獲取元資料 , 獲取列數
        ResultSetMetaData metaData = resultSet.getMetaData();// 元資料
        int columnCount = metaData.getColumnCount();

        // 遍歷結果集
        List list = new ArrayList<T>();
        while (resultSet.next()){
            T book = clazz.newInstance();
            // 因為只知道返回的屬性名,對屬性名的set方法不好指定,利用反射進行修改
            for (int i = 0; i < columnCount; i++) {
                String columnLabel = metaData.getColumnLabel(i + 1);  // 別名
                Object object = resultSet.getObject(columnLabel);   // 值
                Field declaredField = clazz.getDeclaredField(columnLabel);   //
                declaredField.setAccessible(true);
                declaredField.set(book,object);
            }
            list.add(book);
        }

        list.forEach(System.out::println);
        pre.close();
        connection.close();
        if (list.size()==0) return null;
        return list;
    }

1.7 新增大量資料的優化

  • preparedStatement.addBatch(): 快取sql資料
  • preparedStatement.executeBatch(): 執行快取的sql
  • preparedStatement.clearBatch():清空快取的sql
public void test5()throws Exception{
    // 獲取連線
    Connection connection = JDBCUtils.getConnection();

    //  獲取sql的執行物件
    String sql = "insert into books(bookID,bookName,bookCounts,detail) values(?,'java神書',11,'從入門到入獄')";
    PreparedStatement pre = connection.prepareStatement(sql);
    // 關閉連線的自動提交
    connection.setAutoCommit(false);
    // 填充sql的佔位符
    for (int i = 40; i <= 2000; i++) {
        pre.setInt(1,i);
        // 儲存sql
        pre.addBatch();
        if ((i % 200) == 0){
            // 執行sql
            pre.executeBatch();
            // 清空快取的sql
            pre.clearBatch();
        }
    }
    // 提交事務  ,真正把資料插入資料庫
    connection.commit();

    // 關閉資源
    pre.close();
    connection.close();
}

1.8 PreparedStatement對資料庫中的Blob資料操作

Blob資料型別能夠儲存較大的資料

只能使用PreparedStatement進行操作,Blob不支援StatementSql語句的拼接

存圖片

把圖片變成一個檔案流傳過去

@Test
public void test3()throws Exception{
    // 獲取連線
    Connection connection = JDBCUtils.getConnection();
    String sql = "update books set photo = ? where bookID = ?";
    PreparedStatement pre = connection.prepareStatement(sql);
    FileInputStream fis = new FileInputStream("E:\\初音\\test3.png");
    int id = 3;
    //  Blob值注入,佔位符
    pre.setBlob(1,fis);
    pre.setObject(2,id);

    // 提交執行
    pre.executeUpdate();

    // 關閉資源
    fis.close();
    connection.close();
}

取圖片:

getBinaryStream(String ):獲取到Blob欄位返回的一個流

@Test
public void test4()throws Exception{
    // 獲取連線
    Connection connection = JDBCUtils.getConnection();
    String sql = "select books.photo from books where bookID=?";
    PreparedStatement pre = connection.prepareStatement(sql);
    int id = 3;
    //  Blob值注入,佔位符
    pre.setObject(1,id);

    // 提交執行
    ResultSet resultSet = pre.executeQuery();
    // 把Blob型別的欄位下載下來,以檔案的形式儲存在本地中
    InputStream photo = null;
    if (resultSet.next()){
         photo = resultSet.getBinaryStream("photo");
    }

    byte[] buffer = new byte[1024];
    int len;
    FileOutputStream fos = new FileOutputStream(new File("E:\\初音\\test3.png"));
    while ((len = photo.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    // 關閉資源
    if (photo == null) photo.close();
    fos.close();
    connection.close();
}

事務回滾:

@Test
public void test1() throws SQLException {
    //  事務考慮自動提交的DML操作
   Connection connection = null;
   try {
       connection = JDBCUtils.getConnection();
       // 取消自動提交
       connection.setAutoCommit(false);
       String sql1 = "delete from books where bookID =1998";
       String sql2 = "delete from books where bookID =1997";
       PreparedStatement pre1 = connection.prepareStatement(sql1);
       // 執行
       pre1.executeUpdate();
       // 異常
       System.out.println(10/0);
       PreparedStatement pre2 = connection.prepareStatement(sql2);
       pre2.executeUpdate();

       // 提交
       connection.commit();
       if(pre1!=null) pre1.close();
       if(pre2!=null) pre2.close();
   }catch (Exception e){
       // 事務回滾
       connection.rollback();
   }finally{
       // 關閉資源
       connection.close();
    }
}