JDBC核心技術(shù) [1] 概述 獲取鏈接5種方式 增刪改查 使用模板 插入數(shù)據(jù) BLOB 批量插入等

第1章:JDBC概述

1.1 數(shù)據(jù)的持久化
  • 持久化(persistence):把數(shù)據(jù)保存到可掉電式存儲(chǔ)設(shè)備中以供之后使用。大多數(shù)情況下,特別是企業(yè)級(jí)應(yīng)用,數(shù)據(jù)持久化意味著將內(nèi)存中的數(shù)據(jù)保存到硬盤上加以”固化”,而持久化的實(shí)現(xiàn)過程大多通過各種關(guān)系數(shù)據(jù)庫來完成

  • 持久化的主要應(yīng)用是將內(nèi)存中的數(shù)據(jù)存儲(chǔ)在關(guān)系型數(shù)據(jù)庫中,當(dāng)然也可以存儲(chǔ)在磁盤文件、XML數(shù)據(jù)文件中。


    數(shù)據(jù)持久化
1.2 Java中的數(shù)據(jù)存儲(chǔ)技術(shù)
  • 在Java中,數(shù)據(jù)庫存取技術(shù)可分為如下幾類:

    • JDBC直接訪問數(shù)據(jù)庫

    • JDO (Java Data Object )技術(shù)

    • 第三方O/R工具,如Hibernate, Mybatis 等

  • JDBC是java訪問數(shù)據(jù)庫的基石,JDO、Hibernate、MyBatis等只是更好的封裝了JDBC。

1.3 JDBC介紹
  • JDBC(Java Database Connectivity)是一個(gè)獨(dú)立于特定數(shù)據(jù)庫管理系統(tǒng)、通用的SQL數(shù)據(jù)庫存取和操作的公共接口(一組API),定義了用來訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)Java類庫,(java.sql,javax.sql)使用這些類庫可以以一種標(biāo)準(zhǔn)的方法、方便地訪問數(shù)據(jù)庫資源。
  • JDBC為訪問不同的數(shù)據(jù)庫提供了一種統(tǒng)一的途徑,為開發(fā)者屏蔽了一些細(xì)節(jié)問題。
  • JDBC的目標(biāo)是使Java程序員使用JDBC可以連接任何提供了JDBC驅(qū)動(dòng)程序的數(shù)據(jù)庫系統(tǒng),這樣就使得程序員無需對(duì)特定的數(shù)據(jù)庫系統(tǒng)的特點(diǎn)有過多的了解,從而大大簡(jiǎn)化和加快了開發(fā)過程。
  • 如果沒有JDBC,那么Java程序訪問數(shù)據(jù)庫時(shí)是這樣的:


    沒有JDBC

    有了JDBC,Java程序訪問數(shù)據(jù)庫時(shí)是這樣的:


    有了JDBC

    綜述
1.4 JDBC體系結(jié)構(gòu)
  • JDBC接口(API)包括兩個(gè)層次:
    • 面向應(yīng)用的API:Java API,抽象接口,供應(yīng)用程序開發(fā)人員使用(連接數(shù)據(jù)庫,執(zhí)行SQL語句,獲得結(jié)果)。
    • 面向數(shù)據(jù)庫的API:Java Driver API,供開發(fā)商開發(fā)數(shù)據(jù)庫驅(qū)動(dòng)程序用。

JDBC是sun公司提供一套用于數(shù)據(jù)庫操作的接口,java程序員只需要面向這套接口編程即可。

不同的數(shù)據(jù)庫廠商,需要針對(duì)這套接口,提供不同實(shí)現(xiàn)。不同的實(shí)現(xiàn)的集合,即為不同數(shù)據(jù)庫的驅(qū)動(dòng)。 ————面向接口編程

1.5 JDBC程序編寫步驟
JDBC程序編寫步驟

第2章:獲取數(shù)據(jù)庫連接

2.1 要素一:Driver接口實(shí)現(xiàn)類
2.1.1 Driver接口介紹
  • java.sql.Driver 接口是所有 JDBC 驅(qū)動(dòng)程序需要實(shí)現(xiàn)的接口。這個(gè)接口是提供給數(shù)據(jù)庫廠商使用的,不同數(shù)據(jù)庫廠商提供不同的實(shí)現(xiàn)。
  • 在程序中不需要直接去訪問實(shí)現(xiàn)了 Driver 接口的類,而是由驅(qū)動(dòng)程序管理器類(java.sql.DriverManager)去調(diào)用這些Driver實(shí)現(xiàn)。
    • Oracle的驅(qū)動(dòng):oracle.jdbc.driver.OracleDriver
    • mySql的驅(qū)動(dòng): com.mysql.jdbc.Driver
      OJBC

      JDBC

      將上述jar包拷貝到Java工程的一個(gè)目錄中,習(xí)慣上新建一個(gè)lib文件夾。
      拷貝jar包

      在驅(qū)動(dòng)jar上右鍵-->Build Path-->Add to Build Path
      構(gòu)建路徑

      注意:如果是Dynamic Web Project(動(dòng)態(tài)的web項(xiàng)目)話,則是把驅(qū)動(dòng)jar放到WebContent(有的開發(fā)工具叫WebRoot)目錄中的WEB-INF目錄中的lib目錄下即可
      web應(yīng)用
2.2 要素二:URL
  • JDBC URL 用于標(biāo)識(shí)一個(gè)被注冊(cè)的驅(qū)動(dòng)程序,驅(qū)動(dòng)程序管理器通過這個(gè) URL 選擇正確的驅(qū)動(dòng)程序,從而建立到數(shù)據(jù)庫的連接。

  • JDBC URL的標(biāo)準(zhǔn)由三部分組成,各部分間用冒號(hào)分隔。

    • jdbc:子協(xié)議:子名稱
    • 協(xié)議:JDBC URL中的協(xié)議總是jdbc
    • 子協(xié)議:子協(xié)議用于標(biāo)識(shí)一個(gè)數(shù)據(jù)庫驅(qū)動(dòng)程序
    • 子名稱:一種標(biāo)識(shí)數(shù)據(jù)庫的方法。子名稱可以依不同的子協(xié)議而變化,用子名稱的目的是為了定位數(shù)據(jù)庫提供足夠的信息。包含主機(jī)名(對(duì)應(yīng)服務(wù)端的ip地址),端口號(hào),數(shù)據(jù)庫名
  • 舉例:

    協(xié)議

    幾種常用數(shù)據(jù)庫的 JDBC URL

  • MySQL的連接URL編寫方式:

  • Oracle 9的連接URL編寫方式:

    • jdbc:oracle:thin:@主機(jī)名稱:oracle服務(wù)端口號(hào):數(shù)據(jù)庫名稱
    • jdbc:oracle:thin:@localhost:1521:icanci
  • SQLServer的連接URL編寫方式:

2.3 要素三:用戶名和密碼
  • user,password可以用“屬性名=屬性值”方式告訴數(shù)據(jù)庫
  • 可以調(diào)用 DriverManager 類的 getConnection() 方法建立到數(shù)據(jù)庫的連接
2.4 數(shù)據(jù)庫連接方式舉例

2.4.1 連接方式一

@Test
public void testConnection() throws Exception{
    Driver driver = new com.mysql.jdbc.Driver();
    String url = "jdbc:mysql://localhost:3306/test";
    Properties properties = new Properties();
    properties.setProperty("user","root");
    properties.setProperty("password","ok");
    try {
        Connection connection = driver.connect(url, properties);
        System.out.println(connection);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
    }
}

說明:上述代碼中顯式出現(xiàn)了第三方數(shù)據(jù)庫的API

2.4.2 連接方式二

@Test
public void testConnection2() throws Exception{
    //1.獲取Driver對(duì)象 通過反射獲取
    Class clazz = Class.forName("com.mysql.jdbc.Driver");
    Driver driver  =(Driver) clazz.newInstance();
    //需要鏈接的數(shù)據(jù)庫
    String url = "jdbc:mysql://localhost:3306/test";
    Properties properties = new Properties();
    properties.setProperty("user","root");
    properties.setProperty("password","ok");
    try {
        Connection connection = driver.connect(url, properties);
        System.out.println(connection);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
    }
}

說明:相較于方式一,這里使用反射實(shí)例化Driver,不在代碼中體現(xiàn)第三方數(shù)據(jù)庫的API。體現(xiàn)了面向接口編程思想。

2.4.3 連接方式三

@Test
public void testConnection3() throws Exception{
    //0.獲取Driver的實(shí)現(xiàn)類對(duì)象
    Class clazz = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver) clazz.newInstance();
    //1.注冊(cè)驅(qū)動(dòng)
    DriverManager.registerDriver(driver);
    //2.獲取鏈接
    String url  = "jdbc:mysql://localhost:3306/test";
    String  username  ="root";
    String password = "ok";
    Connection connection = DriverManager.getConnection(url, username, password);
    System.out.println(connection);
    connection.close();
}

2.4.4 連接方式四

@Test
public void testConnection4() throws Exception{
    //1.加載驅(qū)動(dòng)
     Class.forName("com.mysql.jdbc.Driver");
    //2.獲取鏈接
    String url  = "jdbc:mysql://localhost:3306/test";
    String  username  ="root";
    String password = "ok";
    Connection connection = DriverManager.getConnection(url, username, password);
    System.out.println(connection);
    connection.close();
}

2.4.5 連接方式五

@Test
public void testConnection5() throws Exception {
    //讀取配置文件
    InputStream resourceAsStream = TestConnection1.class.getClassLoader().getResourceAsStream("db.properties");
    Properties pro = new Properties();
    pro.load(resourceAsStream);
    String url = pro.getProperty("url");
    String className = pro.getProperty("className");
    String username = pro.getProperty("username");
    String password = pro.getProperty("password");
    //1.加載驅(qū)動(dòng)
    Class.forName(className);
    //2.獲取鏈接
    Connection connection = DriverManager.getConnection(url, username, password);
    System.out.println(connection);
    connection.close();
}

配置文件

className=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=ok

說明:使用配置文件的方式保存配置信息,在代碼中加載配置文件
使用配置文件的好處:
①實(shí)現(xiàn)了代碼和數(shù)據(jù)的分離,如果需要修改配置信息,直接在配置文件中修改,不需要深入代碼
②如果修改了配置信息,省去重新編譯的過程。

第3章:使用PreparedStatement實(shí)現(xiàn)CRUD操作

3.1 操作和訪問數(shù)據(jù)庫
  • 數(shù)據(jù)庫連接被用于向數(shù)據(jù)庫服務(wù)器發(fā)送命令和 SQL 語句,并接受數(shù)據(jù)庫服務(wù)器返回的結(jié)果。其實(shí)一個(gè)數(shù)據(jù)庫連接就是一個(gè)Socket連接。

  • 在 java.sql 包中有 3 個(gè)接口分別定義了對(duì)數(shù)據(jù)庫的調(diào)用的不同方式:

    • Statement:用于執(zhí)行靜態(tài) SQL 語句并返回它所生成結(jié)果的對(duì)象。
    • PrepatedStatement:SQL 語句被預(yù)編譯并存儲(chǔ)在此對(duì)象中,可以使用此對(duì)象多次高效地執(zhí)行該語句。
  • CallableStatement:用于執(zhí)行 SQL 存儲(chǔ)過程


    存儲(chǔ)過程
3.2 使用Statement操作數(shù)據(jù)表的弊端
  • 通過調(diào)用 Connection 對(duì)象的 createStatement() 方法創(chuàng)建該對(duì)象。該對(duì)象用于執(zhí)行靜態(tài)的 SQL 語句,并且返回執(zhí)行結(jié)果。
  • Statement 接口中定義了下列方法用于執(zhí)行 SQL 語句:
int excuteUpdate(String sql):執(zhí)行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):執(zhí)行查詢操作SELECT
  • 但是使用Statement操作數(shù)據(jù)表存在弊端:

    • 問題一:存在拼串操作,繁瑣
    • 問題二:存在SQL注入問題
  • SQL 注入是利用某些系統(tǒng)沒有對(duì)用戶輸入的數(shù)據(jù)進(jìn)行充分的檢查,而在用戶輸入數(shù)據(jù)中注入非法的 SQL 語句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,從而利用系統(tǒng)的 SQL 引擎完成惡意行為的做法。

  • 對(duì)于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(從Statement擴(kuò)展而來) 取代 Statement 就可以了。

  • 代碼演示:

package cn.icanci.jdbc;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;

import org.junit.Test;

public class StatementTest {

    // 使用Statement的弊端:需要拼寫sql語句,并且存在SQL注入的問題
    @Test
    public void testLogin() {
        Scanner scan = new Scanner(System.in);
        System.out.print("用戶名:");
        String userName = scan.nextLine();
        System.out.print("密   碼:");
        String password = scan.nextLine();

        // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '
        // ='1' or '1' = '1';
        String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
                + "'";
        User user = get(sql, User.class);
        if (user != null) {
            System.out.println("登陸成功!");
        } else {
            System.out.println("用戶名或密碼錯(cuò)誤!");
        }
    }

    // 使用Statement實(shí)現(xiàn)對(duì)數(shù)據(jù)表的查詢操作
    public <T> T get(String sql, Class<T> clazz) {
        T t = null;

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            // 1.加載配置文件
            InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("db.properties");
            Properties pros = new Properties();
            pros.load(is);

            // 2.讀取配置信息
            String user = pros.getProperty("username");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("className");
            // 3.加載驅(qū)動(dòng)
            Class.forName(driverClass);
            // 4.獲取連接
            conn = DriverManager.getConnection(url, user, password);
            st = conn.createStatement();
            rs = st.executeQuery(sql);
            // 獲取結(jié)果集的元數(shù)據(jù)
            ResultSetMetaData rsmd = rs.getMetaData();
            // 獲取結(jié)果集的列數(shù)
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    // //1. 獲取列的名稱
                    // String columnName = rsmd.getColumnName(i+1);
                    // 1. 獲取列的別名
                    String columnName = rsmd.getColumnLabel(i + 1);
                    // 2. 根據(jù)列名獲取對(duì)應(yīng)數(shù)據(jù)表中的數(shù)據(jù)
                    Object columnVal = rs.getObject(columnName);
                    // 3. 將數(shù)據(jù)表中得到的數(shù)據(jù),封裝進(jìn)對(duì)象
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(t, columnVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 關(guān)閉資源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return null;
    }
}

綜上:


替換
3.3 PreparedStatement的使用
3.3.1 PreparedStatement介紹
  • 可以通過調(diào)用 Connection 對(duì)象的 preparedStatement(String sql) 方法獲取 PreparedStatement 對(duì)象

  • PreparedStatement 接口是 Statement 的子接口,它表示一條預(yù)編譯過的 SQL 語句

  • PreparedStatement 對(duì)象所代表的 SQL 語句中的參數(shù)用問號(hào)(?)來表示,調(diào)用 PreparedStatement 對(duì)象的 setXxx() 方法來設(shè)置這些參數(shù). setXxx() 方法有兩個(gè)參數(shù),第一個(gè)參數(shù)是要設(shè)置的 SQL 語句中的參數(shù)的索引(從 1 開始),第二個(gè)是設(shè)置的 SQL 語句中的參數(shù)的值

3.3.2 PreparedStatement vs Statement
  • 代碼的可讀性和可維護(hù)性。

  • PreparedStatement 能最大可能提高性能:

    • DBServer會(huì)對(duì)預(yù)編譯語句提供性能優(yōu)化。因?yàn)轭A(yù)編譯語句有可能被重復(fù)調(diào)用,所以語句在被DBServer的編譯器編譯后的執(zhí)行代碼被緩存下來,那么下次調(diào)用時(shí)只要是相同的預(yù)編譯語句就不需要編譯,只要將參數(shù)直接傳入編譯過的語句執(zhí)行代碼中就會(huì)得到執(zhí)行。
    • 在statement語句中,即使是相同操作但因?yàn)閿?shù)據(jù)內(nèi)容不一樣,所以整個(gè)語句本身不能匹配,沒有緩存語句的意義.事實(shí)是沒有數(shù)據(jù)庫會(huì)對(duì)普通語句編譯后的執(zhí)行代碼緩存。這樣每執(zhí)行一次都要對(duì)傳入的語句編譯一次。
    • (語法檢查,語義檢查,翻譯成二進(jìn)制命令,緩存)
  • PreparedStatement 可以防止 SQL 注入

3.3.3 Java與SQL對(duì)應(yīng)數(shù)據(jù)類型轉(zhuǎn)換表
Java類型 SQL類型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP
3.3.4 使用PreparedStatement實(shí)現(xiàn)增、刪、改操作

增加

//向Customer添加數(shù)據(jù)
@Test
public void  testInsert() throws Exception{
    //讀取配置文件
    InputStream resourceAsStream = TestConnection1.class.getClassLoader().getResourceAsStream("db.properties");
    Properties pro = new Properties();
    pro.load(resourceAsStream);
    String url = pro.getProperty("url");
    String className = pro.getProperty("className");
    String username = pro.getProperty("username");
    String password = pro.getProperty("password");
    //1.加載驅(qū)動(dòng)
    Class.forName(className);
    //2.獲取鏈接
    Connection connection = DriverManager.getConnection(url, username, password);

    //編寫sql語句
    String sql  = "insert into customers(name,email,birth) values (?,?,?)";
    PreparedStatement prep = connection.prepareStatement(sql);
    //設(shè)置占位符參數(shù)
    prep.setString(1,"哈希");
    prep.setString(2,"icanci@163.com");
    prep.setDate(3,new Date(System.currentTimeMillis()));
    //執(zhí)行操作
    prep.execute();

    connection.close();
    prep.close();
}

修改
JdbcUtils工具類

package cn.icanci.jdbc.utils;

import cn.icanci.jdbc.TestConnection1;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.utils
 * @Date: Created in 2020/2/16 7:41
 * @ClassAction: 操作數(shù)據(jù)庫的鏈接
 */
public class JdbcUtils {

    private static String url;
    private static String className;
    private static String username;
    private static String password;

    static {
        InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
        Properties pro = new Properties();
        try {
            pro.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        url = pro.getProperty("url");
        className = pro.getProperty("className");
        username = pro.getProperty("username");
        password = pro.getProperty("password");
    }

    public static Connection getConnection() {
        //讀取配置文件
        Connection conn = null;
        try {
            //1.加載驅(qū)動(dòng)
            Class.forName(className);
            //2.獲取鏈接
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static void closeConnection(Connection conn, PreparedStatement prep) {
        try {
            if (conn!=null){
                conn.close();
            }
            if (prep!=null){
                prep.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
//Customer修改數(shù)據(jù)
@Test
public void  testUpdate() throws Exception{
    Connection conn = JdbcUtils.getConnection();
    //編寫sql語句
    String sql  = "update customers set name=?,email=? where id = ?";
    PreparedStatement prep = conn.prepareStatement(sql);
    prep.setString(1,"哈希2");
    prep.setString(2,"icanci@136.com");
    prep.setInt(3,19);
    prep.executeUpdate();
    JdbcUtils.closeConnection(conn,prep);
}

刪除

//Customers刪除數(shù)據(jù)
@Test
public void  testDelete() throws Exception{
    Connection conn = JdbcUtils.getConnection();
    //編寫sql語句
    String sql = "delete from customers where id = ?";
    PreparedStatement prep = conn.prepareStatement(sql);
    prep.setInt(1,19);
    prep.executeUpdate();
    JdbcUtils.closeConnection(conn,prep);
}
編寫通用修改JDBC操作
package cn.icanci.jdbc.utils;

import cn.icanci.jdbc.TestConnection1;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.utils
 * @Date: Created in 2020/2/16 7:41
 * @ClassAction: 操作數(shù)據(jù)庫的鏈接
 */
public class JdbcUtils {

    private static String url;
    private static String className;
    private static String username;
    private static String password;

    static {
        InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
        Properties pro = new Properties();
        try {
            pro.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        url = pro.getProperty("url");
        className = pro.getProperty("className");
        username = pro.getProperty("username");
        password = pro.getProperty("password");
    }

    public static Connection getConnection() {
        //讀取配置文件
        Connection conn = null;
        try {
            //1.加載驅(qū)動(dòng)
            Class.forName(className);
            //2.獲取鏈接
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static void closeConnection(Connection conn, PreparedStatement prep) {
        try {
            if (conn!=null){
                conn.close();
            }
            if (prep!=null){
                prep.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //通用的增加刪除修改
    public static void update(String sql, Object... objs) throws Exception {
        try (Connection conn = JdbcUtils.getConnection();
             PreparedStatement prep = conn.prepareStatement(sql)) {
            for (int i = 0; i < objs.length; i++) {
                prep.setObject(i + 1, objs[i]);
            }
            prep.executeUpdate();
            JdbcUtils.closeConnection(conn, prep);
        }
    }
}

測(cè)試

@Test
public  void testUpdateMethod() throws Exception{
    JdbcUtils.update("delete from customers where id = ?",13);
}

說明:使用PreparedStatement實(shí)現(xiàn)的查詢操作可以替換Statement實(shí)現(xiàn)的查詢操作,解決Statement拼串和SQL注入問題。

3.4 ResultSet與ResultSetMetaData
3.4.1 ResultSet
  • 查詢需要調(diào)用PreparedStatement 的 executeQuery() 方法,查詢結(jié)果是一個(gè)ResultSet 對(duì)象

  • ResultSet 對(duì)象以邏輯表格的形式封裝了執(zhí)行數(shù)據(jù)庫操作的結(jié)果集,ResultSet 接口由數(shù)據(jù)庫廠商提供實(shí)現(xiàn)

  • ResultSet 返回的實(shí)際上就是一張數(shù)據(jù)表。有一個(gè)指針指向數(shù)據(jù)表的第一條記錄的前面。

  • ResultSet 對(duì)象維護(hù)了一個(gè)指向當(dāng)前數(shù)據(jù)行的游標(biāo),初始的時(shí)候,游標(biāo)在第一行之前,可以通過 ResultSet 對(duì)象的 next() 方法移動(dòng)到下一行。調(diào)用 next()方法檢測(cè)下一行是否有效。若有效,該方法返回 true,且指針下移。相當(dāng)于Iterator對(duì)象的 hasNext() 和 next() 方法的結(jié)合體。

  • 當(dāng)指針指向一行時(shí), 可以通過調(diào)用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值。

    • 例如: getInt(1), getString("name")
    • 注意:Java與數(shù)據(jù)庫交互涉及到的相關(guān)Java API中的索引都從1開始。
  • ResultSet 接口的常用方法:

    • boolean next()
    • getString()

    • 查詢流程

      查詢實(shí)例

@Test
public void testQuery1() throws Exception{
    Connection conn = JdbcUtils.getConnection();
    //編寫sql語句
    String sql = "select id,name,email,birth  from customers where id = ?";
    PreparedStatement prep = conn.prepareStatement(sql);
    prep.setInt(1,1);
    //執(zhí)行返回查詢的結(jié)果集

    ResultSet resultSet  = prep.executeQuery();
    while (resultSet.next()){
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        String email = resultSet.getString("email");
        java.util.Date date = resultSet.getDate("birth");
        Customer customer = new Customer(id,name,email,date);
        System.out.println(customer);
    }
    JdbcUtils.closeConnection(conn,prep);
}
3.4.2 ResultSetMetaData
  • 可用于獲取關(guān)于 ResultSet 對(duì)象中列的類型和屬性信息的對(duì)象

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):獲取指定列的名稱

    • getColumnLabel(int column):獲取指定列的別名

    • getColumnCount():返回當(dāng)前 ResultSet 對(duì)象中的列數(shù)。

    • getColumnTypeName(int column):檢索指定列的數(shù)據(jù)庫特定的類型名稱。

    • getColumnDisplaySize(int column):指示指定列的最大標(biāo)準(zhǔn)寬度,以字符為單位。

    • isNullable(int column):指示指定列中的值是否可以為 null。

    • isAutoIncrement(int column):指示是否自動(dòng)為指定列進(jìn)行編號(hào),這樣這些列仍然是只讀的。


      查詢

問題1:得到結(jié)果集后, 如何知道該結(jié)果集中有哪些列 ? 列名是什么?

需要使用一個(gè)描述 ResultSet 的對(duì)象, 即 ResultSetMetaData

問題2:關(guān)于ResultSetMetaData

  1. 如何獲取 ResultSetMetaData: 調(diào)用 ResultSet 的 getMetaData() 方法即可
  2. 獲取 ResultSet 中有多少列:調(diào)用 ResultSetMetaData 的 getColumnCount() 方法
  3. 獲取 ResultSet 每一列的列的別名是什么:調(diào)用 ResultSetMetaData 的getColumnLabel() 方法 推薦使用,因?yàn)間etColumnName只能獲取列名
    查詢流程

    針對(duì)單表的查詢操作
package cn.icanci.jdbc.study;

import cn.icanci.jdbc.dao.Customer;
import cn.icanci.jdbc.utils.JdbcUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.study
 * @Date: Created in 2020/2/16 8:48
 * @ClassAction:
 */
public class QueryForCustomer {
    public static Customer queryForCustomer(String sql, Object... objs) throws Exception {
        Connection conn = JdbcUtils.getConnection();
        PreparedStatement prep = conn.prepareStatement(sql);
        for (int i = 0; i < objs.length; i++) {
            prep.setObject(i + 1, objs[i]);
        }

        ResultSet resultSet = prep.executeQuery();
        //獲取結(jié)果集的元數(shù)據(jù)
        ResultSetMetaData metaData = resultSet.getMetaData();
        //獲取結(jié)果集的列數(shù)
        int columnCount = metaData.getColumnCount();

        if (resultSet.next()) {
            Customer customer = new Customer();
            for (int i = 0; i < columnCount; i++) {
                Object value = resultSet.getObject(i + 1);
                //獲取每個(gè)列的名字
                String columnName = metaData.getColumnName(i + 1);
                //給Customer 指定 賦值
                Field declaredField = Customer.class.getDeclaredField(columnName);
                declaredField.setAccessible(true);
                declaredField.set(customer, value);
            }
            return customer;
        }

        resultSet.close();
        JdbcUtils.closeConnection(conn,prep);
        return null;
   } 

測(cè)試

@Test
public void testQueryForCustomer()throws Exception{
    Customer customer = QueryForCustomer.queryForCustomer("select id,name,email,birth from customers where id =?",2);
    System.out.println(customer);
}

注意點(diǎn):domain的字段名必須和數(shù)據(jù)庫對(duì)應(yīng)表的字段名相同

3.5 資源的釋放

  • 釋放ResultSet, Statement,Connection。
  • 數(shù)據(jù)庫連接(Connection)是非常稀有的資源,用完后必須馬上釋放,如果Connection不能及時(shí)正確的關(guān)閉將導(dǎo)致系統(tǒng)宕機(jī)。Connection的使用原則是盡量晚創(chuàng)建,盡量早的釋放。
  • 可以在finally中關(guān)閉,保證及時(shí)其他代碼出現(xiàn)異常,資源也一定能被關(guān)閉。
3.6 JDBC API小結(jié)
  • 兩種思想

    • 面向接口編程的思想
    • ORM思想(object relational mapping)
      • 一個(gè)數(shù)據(jù)表對(duì)應(yīng)一個(gè)java類
      • 表中的一條記錄對(duì)應(yīng)java類的一個(gè)對(duì)象
      • 表中的一個(gè)字段對(duì)應(yīng)java類的一個(gè)屬性

    sql是需要結(jié)合列名和表的屬性名來寫。注意起別名。

  • 兩種技術(shù)

    • JDBC結(jié)果集的元數(shù)據(jù):ResultSetMetaData
      • 獲取列數(shù):getColumnCount()
      • 獲取列的別名:getColumnLabel()
    • 通過反射,創(chuàng)建指定類的對(duì)象,獲取指定的屬性并賦值

JdbcTemplate模板類

package cn.icanci.jdbc.utils;
import  java.util.ArrayList;

import cn.icanci.jdbc.domain.Customer;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.List;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.utils
 * @Date: Created in 2020/2/16 8:13
 * @ClassAction:
 */
public class JdbcTemplate {
    /**
     * 通用的增加刪除修改
     * @param sql 需要執(zhí)行的sql語句
     * @param objs 需要傳入的參數(shù)
     * @throws Exception
     */
    public static void update(String sql, Object... objs) throws Exception {
        try (Connection conn = JdbcUtils.getConnection();
             PreparedStatement prep = conn.prepareStatement(sql)) {
            for (int i = 0; i < objs.length; i++) {
                prep.setObject(i + 1, objs[i]);
            }
            prep.executeUpdate();
            JdbcUtils.closeConnection(conn, prep);
        }
    }

    public static <T>T  getInstance(String sql,Class<T> clazz,Object... objs)throws Exception{
        Connection conn = JdbcUtils.getConnection();
        PreparedStatement prep = conn.prepareStatement(sql);
        for (int i = 0; i < objs.length; i++) {
            prep.setObject(i + 1, objs[i]);
        }

        ResultSet resultSet = prep.executeQuery();
        //獲取結(jié)果集的元數(shù)據(jù)
        ResultSetMetaData metaData = resultSet.getMetaData();
        //獲取結(jié)果集的列數(shù)
        int columnCount = metaData.getColumnCount();

        if (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                Object value = resultSet.getObject(i + 1);
                //獲取每個(gè)列的名字
                String columnName = metaData.getColumnName(i + 1);
                //給Customer 指定 賦值
                Field declaredField = Customer.class.getDeclaredField(columnName);
                declaredField.setAccessible(true);
                declaredField.set(t, value);
            }
            return t;
        }
        resultSet.close();
        JdbcUtils.closeConnection(conn,prep);
        return null;
    }

    public static <T> List<T>  getInstanceList(String sql, Class<T> clazz, Object... objs)throws Exception{
        Connection conn = JdbcUtils.getConnection();
        PreparedStatement prep = conn.prepareStatement(sql);
        for (int i = 0; i < objs.length; i++) {
            prep.setObject(i + 1, objs[i]);
        }

        ResultSet resultSet = prep.executeQuery();
        //獲取結(jié)果集的元數(shù)據(jù)
        ResultSetMetaData metaData = resultSet.getMetaData();
        //獲取結(jié)果集的列數(shù)
        int columnCount = metaData.getColumnCount();
        List<T> list = new ArrayList<T> ();
        while (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                Object value = resultSet.getObject(i + 1);
                //獲取每個(gè)列的名字
                String columnName = metaData.getColumnName(i + 1);
                //給Customer 指定 賦值
                Field declaredField = Customer.class.getDeclaredField(columnName);
                declaredField.setAccessible(true);
                declaredField.set(t, value);
            }
            list.add(t);
        }
        resultSet.close();
        JdbcUtils.closeConnection(conn,prep);
        return list;
    }

}

測(cè)試 測(cè)試通過的

@Test
public void testQueryForCustomer() throws Exception {
    Customer customer = JdbcTemplate.getInstance("select id,name,email,birth from customers where id =?", Customer.class, 2);
    System.out.println(customer);
    System.out.println("---------------------------");
    List<Customer> list = JdbcTemplate.getInstanceList("select id,name,email,birth from customers", Customer.class);
    list.forEach(System.out::println);
}

第4章 操作BLOB類型字段

4.1 MySQL BLOB類型
  • MySQL中,BLOB是一個(gè)二進(jìn)制大型對(duì)象,是一個(gè)可以存儲(chǔ)大量數(shù)據(jù)的容器,它能容納不同大小的數(shù)據(jù)。

  • 插入BLOB類型的數(shù)據(jù)必須使用PreparedStatement,因?yàn)锽LOB類型的數(shù)據(jù)無法使用字符串拼接寫的。

  • MySQL的四種BLOB類型(除了在存儲(chǔ)的最大信息量上不同外,他們是等同的)


    四種Blob類型
  • 實(shí)際使用中根據(jù)需要存入的數(shù)據(jù)大小定義不同的BLOB類型。

  • 需要注意的是:如果存儲(chǔ)的文件過大,數(shù)據(jù)庫的性能會(huì)下降。

  • 如果在指定了相關(guān)的Blob類型以后,還報(bào)錯(cuò):xxx too large,那么在mysql的安裝目錄下,找my.ini文件加上如下的配置參數(shù): max_allowed_packet=16M。同時(shí)注意:修改了my.ini文件之后,需要重新啟動(dòng)mysql服務(wù)。

4.2 向數(shù)據(jù)表中插入大數(shù)據(jù)類型
//Customer 插入數(shù)據(jù)
@Test
public void testInsert3()throws Exception{
    Connection conn = JdbcUtils.getConnection();
    String sql = "insert into customers(name,email,birth,photo) values (?,?,?,?)";
    PreparedStatement prep = conn.prepareStatement(sql);
    PreparedStatement ps = conn.prepareStatement(sql);
    prep.setString(1,"啦啦啦");
    prep.setString(2,"lalal@icanci,cn");
    prep.setDate(3,new Date(System.currentTimeMillis()));
    FileInputStream inputStream = new FileInputStream(new File("1001400.jpg"));
    prep.setBlob(4,inputStream);
    prep.execute();
    JdbcUtils.closeConnection(conn,prep);
}
4.3 從數(shù)據(jù)表中讀取大數(shù)據(jù)類型
//向數(shù)據(jù)表customers中插入Blob類型的字段
@Test
public void testGet() throws Exception {
    Connection conn = JdbcUtils.getConnection();
    String sql = "select * from customers where id =?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setInt(1, 22);
    ResultSet resultSet = ps.executeQuery();
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        String email = resultSet.getString("email");
        Date date = resultSet.getDate("birth");
        Blob photo = resultSet.getBlob("photo");

        //把圖片保存
        InputStream is = photo.getBinaryStream();
        FileOutputStream out = new FileOutputStream("lalal.png");
        byte[] buffer = new byte[1204];
        int len;
        while ((len = is.read()) != -1) {
            out.write(buffer, 0, len);
        }
        Customer customer = new Customer(id, name, email, date);
        System.out.println(customer);
        is.close();
        out.close();
    }
    JdbcUtils.closeConnection(conn, ps);
}
4.4 修改數(shù)據(jù)表中的Blob類型字段

和之前類似

Connection conn = JdbcUtils.getConnection();
String sql = "update customers set photo = ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);

// 填充占位符
// 操作Blob類型的變量
FileInputStream fis = new FileInputStream("coffee.png");
ps.setBlob(1, fis);
ps.setInt(2, 25);

ps.execute();

fis.close();
JdbcUtils.closeConnection(conn, ps);

第5章 批量插入

5.1 批量執(zhí)行SQL語句

當(dāng)需要成批插入或者更新記錄時(shí),可以采用Java的批量更新機(jī)制,這一機(jī)制允許多條語句一次性提交給數(shù)據(jù)庫批量處理。通常情況下比單獨(dú)提交處理更有效率

JDBC的批量處理語句包括下面三個(gè)方法:

  • addBatch(String):添加需要批量處理的SQL語句或是參數(shù);
  • executeBatch():執(zhí)行批量處理語句;
  • clearBatch():清空緩存的數(shù)據(jù)

通常我們會(huì)遇到兩種批量執(zhí)行SQL語句的情況:

  • 多條SQL語句的批量處理;
  • 一個(gè)SQL語句的批量傳參;
5.2 高效的批量插入

舉例:向數(shù)據(jù)表中插入20000條數(shù)據(jù)

  • 數(shù)據(jù)庫中提供一個(gè)goods表。創(chuàng)建如下:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
5.2.1 實(shí)現(xiàn)層次一:使用Statement 耗費(fèi)時(shí)間 23704 ms
@Test  //耗費(fèi)時(shí)間 23704 ms
public void testInsertMany() throws Exception {
    Long start = System.currentTimeMillis();
    Connection conn = JdbcUtils.getConnection();
    Statement st = conn.createStatement();
    for (int i = 1; i <= 20000; i++) {
        String sql = "insert into goods(name) values ('name')";
        st.executeUpdate(sql);
    }
    Long end = System.currentTimeMillis();
    System.out.println(end - start);
    st.close();
    conn.close();
}
5.2.2 實(shí)現(xiàn)層次二:使用PreparedStatement 耗費(fèi)時(shí)間 25450 ms
@Test  //插入時(shí)間 25450
public void test2()throws Exception{
    Long start = System.currentTimeMillis();
    Connection conn = JdbcUtils.getConnection();
    String sql = "insert into goods(name) values (?)";
    PreparedStatement prep = conn.prepareStatement(sql);
    for (int i = 1; i <= 20000; i++) {
        prep.setObject(1,"name_"+i);
        prep.execute();
    }
    Long end = System.currentTimeMillis();
    System.out.println(end - start);
    JdbcUtils.closeConnection(conn,prep);
}
5.2.3 實(shí)現(xiàn)層次三 耗費(fèi)時(shí)間 548 ms
  • 使用 addBatch() / executeBatch() / clearBatch()
  • mysql服務(wù)器默認(rèn)是關(guān)閉批處理的,我們需要通過一個(gè)參數(shù),讓mysql開啟批處理的支持。 ?rewriteBatchedStatements=true 寫在配置文件的url后面
  • 使用更新的mysql 驅(qū)動(dòng):mysql-connector-java-5.1.37-bin.jar 在 5.1.37 以上
@Test  //耗費(fèi)時(shí)間 548 ms  插入1000000數(shù)據(jù)條 耗時(shí) 8233 ms
public void test3()throws Exception{
    /**使用 addBatch() / executeBatch() / clearBatch()
     * mysql服務(wù)器默認(rèn)是關(guān)閉批處理的,我們需要通過一個(gè)參數(shù),讓mysql開啟批處理的支持。 ?rewriteBatchedStatements=true 寫在配置文件的url后面
     * 使用更新的mysql 驅(qū)動(dòng):mysql-connector-java-5.1.37-bin.jar  在 5.1.37 以上
     */
    Long start = System.currentTimeMillis();
    Connection conn = JdbcUtils.getConnection();
    String sql = "insert into goods(name) values (?)";
    PreparedStatement prep = conn.prepareStatement(sql);
    for (int i = 1; i <= 20000; i++) {
        prep.setObject(1,"name_"+i);
        //攢sql
        prep.addBatch();
        if (i%500 == 0){
            //執(zhí)行
            prep.executeBatch();
            //清空
            prep.clearBatch();
        }
    }
    Long end = System.currentTimeMillis();
    System.out.println(end - start);
    JdbcUtils.closeConnection(conn,prep);
}
5.2.4 實(shí)現(xiàn)層次四 耗費(fèi)時(shí)間 496 ms 1000000 耗時(shí) 5726ms
  • 使用Connection 的 setAutoCommit(false) / commit()
@Test  //耗費(fèi)時(shí)間 496 ms  1000000 耗時(shí) 5726ms
public void test4()throws Exception{
    /**
     * 使用Connection 的 setAutoCommit(false)  /  commit()
     */
    Long start = System.currentTimeMillis();
    Connection conn = JdbcUtils.getConnection();
    conn.setAutoCommit(false);
    String sql = "insert into goods(name) values (?)";
    PreparedStatement prep = conn.prepareStatement(sql);
    for (int i = 1; i <= 20000; i++) {
        prep.setObject(1,"name_"+i);
        //攢sql
        prep.addBatch();
        if (i%500 == 0){
            //執(zhí)行
            prep.executeBatch();
            //清空
            prep.clearBatch();
        }
    }
    conn.commit();
    Long end = System.currentTimeMillis();
    System.out.println(end - start);
    JdbcUtils.closeConnection(conn,prep);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,207評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,110評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,305評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,532評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評(píng)論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,033評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,268評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • 本文主要內(nèi)容 1、JDBC 2、DBUtils 01JDBC概念和數(shù)據(jù)庫驅(qū)動(dòng)程序 A: JDBC概念和數(shù)據(jù)庫驅(qū)動(dòng)程...
    勝浩_ae28閱讀 405評(píng)論 0 0
  • 以下我是歸納的JDBC知識(shí)點(diǎn)圖: 圖上的知識(shí)點(diǎn)都可以在我其他的文章內(nèi)找到相應(yīng)內(nèi)容。 JDBC常見面試題 JDBC操...
    Java3y閱讀 1,735評(píng)論 0 15
  • 本節(jié)介紹Statement接口及其子類PreparedStatement和CallableStatement。 它...
    zlb閱讀 1,179評(píng)論 0 0
  • 本文內(nèi)容 1.什么是JDBC以及為什么要使用JDBC 2.JDBC核心API的講解 3.使用JDBC核心API進(jìn)行...
    Vincilovfang閱讀 1,230評(píng)論 0 11
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,569評(píng)論 0 4