成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java JDBC編程

鄒強 / 2542人閱讀

摘要:和很像,嚴(yán)格來說,應(yīng)該是模仿了是設(shè)計。程序中使用該類的主要功能是獲取對象,該類包含如下方法該方法獲得對應(yīng)數(shù)據(jù)庫的連接代表數(shù)據(jù)庫連接對象,每個代表一個物理連接會話。當(dāng)執(zhí)行查詢時,返回查詢到的結(jié)果集。程序可以通過操作該對象來取出查詢結(jié)果。

JDBC基礎(chǔ)

JDBC的全稱是Java Database Connectivity,即Java數(shù)據(jù)庫連接,它是一種可以執(zhí)行SQL語句的Java API。程序可通過JDBC API連接到關(guān)系數(shù)據(jù)庫,并使用結(jié)構(gòu)化查詢語言(SQL,數(shù)據(jù)庫標(biāo)準(zhǔn)的查詢語言)來完成對數(shù)據(jù)庫的查詢、更新

與其他數(shù)據(jù)庫編程環(huán)境相比,JDBC為數(shù)據(jù)庫開發(fā)提供了標(biāo)準(zhǔn)的API,使用JDBC開發(fā)的數(shù)據(jù)庫應(yīng)用可以跨平臺運行,而且還可以跨數(shù)據(jù)庫(如果全部使用標(biāo)準(zhǔn)的SQL語句)。也就是說如果使用JDBC開發(fā)一個數(shù)據(jù)庫應(yīng)用,則該應(yīng)用既可以在Windows操作系統(tǒng)上運行,又可以在Unix等其他操作系統(tǒng)上運行,既可以使用MySQL數(shù)據(jù)庫,又可以使用Oracle等其他的數(shù)據(jù)庫,應(yīng)用程序不需要做任何的修改

JDBC簡介

Java語言的各種跨平臺特性,都采用相似的結(jié)構(gòu)。因為他們都需要讓相同的程序在不同的平臺上運行,所以需要中間的轉(zhuǎn)換程序(為了實現(xiàn)Java程序的跨平臺,Java為不同的操作系統(tǒng)提供了不同的Java虛擬機)。同樣,為了JDBC程序可以跨平臺,也需要不同的數(shù)據(jù)庫廠商提供相應(yīng)的驅(qū)動程序

Sun提供的JDBC可以完成以下三個基本操作:

建立與數(shù)據(jù)庫的鏈接

執(zhí)行SQL語句

獲得SQL語句的執(zhí)行結(jié)果

JDBC驅(qū)動程序

數(shù)據(jù)庫驅(qū)動程序是JDBC程序和數(shù)據(jù)庫之間的轉(zhuǎn)換層,數(shù)據(jù)庫驅(qū)動程序負責(zé)將JDBC調(diào)用映射成特定的數(shù)據(jù)庫調(diào)用

ODB,Open Database Connectivity,即開放數(shù)據(jù)庫鏈接。ODBC和JDBC很像,嚴(yán)格來說,應(yīng)該是JDBC模仿了ODBC是設(shè)計。ODBC也允許應(yīng)用程序通過一種通用的API訪問不同的數(shù)據(jù)庫管理系統(tǒng),從而使得基于ODBC的應(yīng)用程序可以在不同的數(shù)據(jù)庫之間切換。同樣,ODBC也需要各數(shù)據(jù)庫廠商提供相應(yīng)的驅(qū)動程序,而ODBC負責(zé)管理這些驅(qū)動程序

JDBC驅(qū)動通常有如下4種類型

JDBC + ODBC橋的方式

直接將JDBC API隱射成數(shù)據(jù)庫特定的客戶端API。這種驅(qū)動包含特定數(shù)據(jù)庫的本地代碼,用于訪問特定數(shù)據(jù)庫的客戶端

支持三層結(jié)構(gòu)的JDBC訪問方式,主要用于Applet階段,通過Applet訪問數(shù)據(jù)庫

純java的,直接與數(shù)據(jù)庫實例交互,。這種驅(qū)動是智能型的,它知道數(shù)據(jù)庫使用的底層協(xié)議,是目前最流行的JDBC驅(qū)動

通常建議選擇第4種JDBC驅(qū)動,這種驅(qū)動避開了本地代碼,減少了應(yīng)用開發(fā)的復(fù)雜性,也減少了產(chǎn)生沖突和出錯的可能。如果對性能有嚴(yán)格的要求,則可以考慮使用第2種JDBC驅(qū)動,但使用這種驅(qū)動,則勢必增加編碼和維護的困難

JDBC比ODBC多了如下幾個優(yōu)勢

ODBC更復(fù)雜,ODBC中有幾個命令需要配置很多復(fù)雜的選項,而JDBC則采用簡單、直觀的方式來管理數(shù)據(jù)庫連接

JDBC比ODBC安全性更高,更易部署

JDBC的經(jīng)典用法 JDBC 4.2常用接口和類簡介

JAVA8關(guān)于JDBC4.2的新增功能:
DriverManager:用于管理JDBC驅(qū)動的服務(wù)類。程序中使用該類的主要功能是獲取Connection對象,該類包含如下方法

public static synchronized Connection getConnection(String url, String user, String password) throws SQLException:該方法獲得url對應(yīng)數(shù)據(jù)庫的連接

Connection

Connection:代表數(shù)據(jù)庫連接對象,每個Connection代表一個物理連接會話。要想訪問數(shù)據(jù)庫,必須先得到數(shù)據(jù)庫連接。該接口的常用方法如下:

Statement createStatement() throws SQLException:該方法返回一個Statement對象

PreparedStatement prepareStatement(String sql) throws SQLException:該方法返回預(yù)編譯的Statement對象,即將SQL語句提交到數(shù)據(jù)庫進行預(yù)編譯

CallableStatement prepareCall(String sql) throws SQLException:該方法返回CallableStatement對象,該對象用于調(diào)用存儲過程

上面三個方法都返回用于執(zhí)行SQL語句的Statement對象,PreparedStatement、CallableStatement是Statement的子類,只有獲得了Statement之后才可以執(zhí)行SQL語句

除此之外,Connection還有如下幾個用于控制事務(wù)的方法:

Savepoint setSavepoint() throws SQLException:創(chuàng)建一個保存點

Savepoint setSavepoint(String name):以指定名字來創(chuàng)建一個保存點

void setTransactionIsolation(int level):設(shè)置事務(wù)的隔離級別

void rollback():回滾事務(wù)

void rollback(Savepoint savepoint):將事務(wù)回滾到指定的保存點

void setAutoCommit(boolean autoCommit):關(guān)閉自動提交,打開事務(wù)

void commit() throws SQLException:提交事務(wù)

Java7位Connection新增了setSchema(String schema)、getSchema()兩個方法,這兩個方法用于控制該Connection訪問的數(shù)據(jù)庫Schema。還為Connection新增了setNetworkTimeout(Executor executor, int milliseconds)、getNetworkTimeout()兩個方法來控制數(shù)據(jù)庫連接的超時行為

Statement

Statement:用于執(zhí)行SQL語句的工具接口。該對象既可以執(zhí)行DDL、DCL語句,也可以用于執(zhí)行DML語句,還可以用于執(zhí)行SQL查詢。當(dāng)執(zhí)行SQL查詢時,返回查詢到的結(jié)果集。它的常用方法如下:

ResultSet executeQuery(String sql) throws SQLException:該方法用于執(zhí)行查詢語句,并返回查詢結(jié)果對應(yīng)ResultSet對象。該方法只能用于執(zhí)行查詢語句

int executeUpdate(String sql) throws SQLException:該方法用于執(zhí)行DML語句,并返回受影響的行數(shù);該方法也可用于執(zhí)行DDL語句,執(zhí)行DDL語句將返回0

boolean execute(String sql) throws SQLException:該方法可以執(zhí)行任何SQL語句。如果執(zhí)行后第一個結(jié)果為ResultSet對象,則返回true;如果執(zhí)行后第一個結(jié)果為受影響的行數(shù)或沒有任何結(jié)果,則返回false

Java7為Statement新增了closeOnCompletion()方法,如果Statement執(zhí)行了此方法,則當(dāng)所有依賴于該Statement的ResultSet關(guān)閉時,該Statement會自動關(guān)閉。Java7還為Statement提供了一個isCloseOnCompletion()方法,該方法用于判斷該Statement是否打開了“closeOnCompletion”

PreparedStatement

PreparedStatement:預(yù)編譯的Statement對象,PreparedStatement是Statement的子接口,它允許數(shù)據(jù)庫預(yù)編譯SQL語句(這些SQL語句通常帶有參數(shù)),以后每次只改變sql命令的參數(shù),避免數(shù)據(jù)庫每次都需要編譯SQL語句,無需再傳入SQL語句,因此性能更好。使用PreparedStatement執(zhí)行SQL語句時,無須再傳入SQL語句,只要為預(yù)編譯的SQL語句傳入?yún)?shù)值即可

PreparedStatement同樣有executeQuery()、executeUpdate()和execute()方法,只是這三個方法無須接收SQL字符串,因為PreparedStatement對象已預(yù)編譯了SQL命令,只要為這些方法傳入?yún)?shù)即可。所以它比Statement多了如下方法:

void setXxx(int parameterIndex, Xxx value):該方法根據(jù)傳入?yún)?shù)值的類型不同,需要使用不同的方法。傳入的值根據(jù)索引傳給SQL語句中指定位置的參數(shù)

ResultSet

ResultSet:結(jié)果集對象。該對象包含訪問查詢結(jié)果的方法,ResultSet可以通過列索引或列名獲得列數(shù)據(jù)。它包含了如下常用方法來移動記錄指針

void close():釋放ResultSet對象

boolean absolute(int row):將結(jié)果集的記錄指針移動到第row行,如果row是負數(shù),則移動到倒數(shù)第row行,如果移動后的記錄指針指向一條有效記錄,則該方法返回true

void beforeFisrt():將ResultSet的記錄指針定位到首行之前,這是ResultSet結(jié)果集記錄指針的初始狀態(tài)——記錄指針的起始位置位于第一行之前。

boolean first():將ResultSet的記錄指針定位到首行。如果移動后的記錄指針指向一條有效記錄,則該方法返回true

boolean previous():將ResultSet的記錄指針定位到上一行,如果移動后的記錄指針指向一條有效記錄,則該方法返回true

boolean next():將結(jié)果集的記錄指針定位到下一行,如果移動后的記錄指針指向一條有效的記錄,則該方法返回true

boolean last():將結(jié)果集的記錄指針定位到最后一行,如果移動后的記錄指針指向一條有效的記錄,則該方法返回true

void afterLast():將ResultSet的記錄指針定位到最后一行之后

當(dāng)把記錄指針移動到指定行之后,ResultSet可通過getXxx(int columnIndex)或getXxx(String columnLabel)方法來獲取當(dāng)前行、指定列的值,前者根據(jù)列索引獲取值,后者根據(jù)列名獲取值

JDBC編程步驟 1 加載數(shù)據(jù)庫驅(qū)動

通常使用Class類的forName()靜態(tài)方法來加載驅(qū)動

// 加載驅(qū)動,driverClass就是數(shù)據(jù)庫驅(qū)動類所對應(yīng)的字符串
Class.forName(driverClass);
// 加載MySQL的驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 加載Oracle的驅(qū)動
Class.forName("oracle.jabc.driver.OracleDriver");
2 通過DriverManager獲取數(shù)據(jù)庫的鏈接
// 獲取數(shù)據(jù)庫連接
DriverManager.getConnection(String url, Stirng user, String pass)

當(dāng)使用DriverManager來獲取鏈接,通常需要傳入三個參數(shù):數(shù)據(jù)庫URL、登錄數(shù)據(jù)庫的用戶名和密碼

數(shù)據(jù)庫URL通常遵循如下寫法:jdbc是固定的;subprotocol指定連接到特定數(shù)據(jù)庫的驅(qū)動;other和stuff也是不固定的

jdbc:subprotocol:other stuff
3 通過Connection對象創(chuàng)建Statement(或者PreparedStatement)對象

createStatement():創(chuàng)建基本的Statement對象

prepareStatement(String sql):根據(jù)傳入的SQL語句創(chuàng)建預(yù)編譯的Statement對象

prepareCall(String sql):根據(jù)傳入的SQL語句創(chuàng)建CallableStatement對象

4 使用Statement執(zhí)行SQL語句

execute():可以執(zhí)行任何SQL語句,但比較麻煩

executeUpdate():主要用于執(zhí)行DML和DDL語句。執(zhí)行DML返回受影響的SQL語句行數(shù),執(zhí)行DDL返回0

executeQuery():只能執(zhí)行查詢語句,執(zhí)行后返回代表查詢結(jié)果的ResultSet對象

5 操作結(jié)果集

如果執(zhí)行的SQL語句是查詢語句,則執(zhí)行結(jié)果將返回一個ResultSet對象,該對象里保存了SQL語句查詢的結(jié)果。程序可以通過操作該ResultSet對象來取出查詢結(jié)果。ResultSet對象主要提供了如下兩類方法

next()、previous()、first()、last()、beforeFrist()、afterLast()、absolute()等移動指針的方法

getXxx()方法獲取記錄指針指向行,特定列的值。既可使用列名作為參數(shù)可讀性更好、使用索引作為參數(shù)性能更好

6 回收數(shù)據(jù)庫資源

包括關(guān)閉ResultSet、Statement和Connection等資源

import java.sql.*;
public class ConnMySql
{
    public static void main(String[] args) throws Exception
    {
        // 1.加載驅(qū)動,使用反射的知識,現(xiàn)在記住這么寫。
        Class.forName("com.mysql.jdbc.Driver");
        try(
            // 2.使用DriverManager獲取數(shù)據(jù)庫連接,
            // 其中返回的Connection就代表了Java程序和數(shù)據(jù)庫的連接
            // 不同數(shù)據(jù)庫的URL寫法需要查驅(qū)動文檔知道,用戶名、密碼由DBA分配
            Connection conn = DriverManager.getConnection(
                "jdbc:mysql://127.0.0.1:3306/select_test"
                , "root" , "32147");
            // 3.使用Connection來創(chuàng)建一個Statment對象
            Statement stmt = conn.createStatement();
            // 4.執(zhí)行SQL語句
            /*
            Statement有三種執(zhí)行sql語句的方法:
            1 execute 可執(zhí)行任何SQL語句。- 返回一個boolean值,
              如果執(zhí)行后第一個結(jié)果是ResultSet,則返回true,否則返回false
            2 executeQuery 執(zhí)行Select語句 - 返回查詢到的結(jié)果集
            3 executeUpdate 用于執(zhí)行DML語句。- 返回一個整數(shù),
              代表被SQL語句影響的記錄條數(shù)
            */
            ResultSet rs = stmt.executeQuery("select s.* , teacher_name"
                + " from student_table s , teacher_table t"
                + " where t.teacher_id = s.java_teacher"))
        {
            // ResultSet有系列的getXxx(列索引 | 列名),用于獲取記錄指針
            // 指向行、特定列的值,不斷地使用next()將記錄指針下移一行,
            // 如果移動之后記錄指針依然指向有效行,則next()方法返回true。
            while(rs.next())
            {
                System.out.println(rs.getInt(1) + "	"
                    + rs.getString(2) + "	"
                    + rs.getString(3) + "	"
                    + rs.getString(4));
            }
        }
    }
}
執(zhí)行SQL語句的方式 使用Java8新增的executeLargeUpdate方法執(zhí)行DDL和DML語句

以下程序示范了使用executeUpdate()方法(MySQL驅(qū)動暫不支持executeLargeUpdate()方法)創(chuàng)建數(shù)據(jù)表。該示例并沒有直接把數(shù)據(jù)庫連接信息寫在程序里,而是使用一個mysql.ini文件(properties文件)來保存數(shù)據(jù)庫連接信息,這是比較成熟的做法——當(dāng)需要把應(yīng)用程序從開發(fā)環(huán)境移植到生產(chǎn)環(huán)境時,無須修改源代碼,只需修改mysql.ini配置文件即可

import java.util.*;
import java.io.*;
import java.sql.*;

public class ExecuteDDL
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile)
        throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }
    public void createTable(String sql)throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        try(
        // 獲取數(shù)據(jù)庫連接
        Connection conn = DriverManager.getConnection(url , user , pass);
        // 使用Connection來創(chuàng)建一個Statment對象
        Statement stmt = conn.createStatement())
        {
            // 執(zhí)行DDL,創(chuàng)建數(shù)據(jù)表
            stmt.executeUpdate(sql);
        }
    }
    public static void main(String[] args) throws Exception
    {
        ExecuteDDL ed = new ExecuteDDL();
        ed.initParam("mysql.ini");
        ed.createTable("create table jdbc_test "
            + "( jdbc_id int auto_increment primary key, "
            + "jdbc_name varchar(255), "
            + "jdbc_desc text);");
        System.out.println("-----建表成功-----");
    }
}

下面程序執(zhí)行一條insert語句,這條insert語句會向剛剛建立的jdbc_test數(shù)據(jù)表中插入幾條記錄。因為使用了帶子查詢的insert語句,所以可以一次插入多條語句

import java.util.*;
import java.io.*;
import java.sql.*;

public class ExecuteDML
{
    private String driver;
    private String url;
    private String user;
    private String pass;

    public void initParam(String paramFile)
        throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }
    public int insertData(String sql)throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url
                , user , pass);
            // 使用Connection來創(chuàng)建一個Statment對象
            Statement stmt = conn.createStatement())
        {
            // 執(zhí)行DML,返回受影響的記錄條數(shù)
            return stmt.executeUpdate(sql);
        }
    }
    public static void main(String[] args)throws Exception
    {
        ExecuteDML ed = new ExecuteDML();
        ed.initParam("mysql.ini");
        int result = ed.insertData("insert into jdbc_test(jdbc_name,jdbc_desc)"
            + "select s.student_name , t.teacher_name "
            + "from student_table s , teacher_table t "
            + "where s.java_teacher = t.teacher_id;");
        System.out.println("--系統(tǒng)中共有" + result + "條記錄受影響--");
    }
}
使用execute方法執(zhí)行SQL語句

Statement的execute()方法幾乎可以執(zhí)行任何SQL語句,但它執(zhí)行SQL語句時比較麻煩,通常沒有必要使用execute()方法來執(zhí)行SQL語句,使用executeQuery()或executeUpdate()方法更簡單。但如果不清楚SQL語句的類型,則只能使用execute()方法來執(zhí)行該SQL語句

getResult():獲取該Statement執(zhí)行查詢語句所返回的ResultSet對象

getUpdateCount():獲取該Statement()執(zhí)行DML語句所影響的記錄行數(shù)

import java.util.*;
import java.io.*;
import java.sql.*;

public class ExecuteSQL
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile)throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }
    public void executeSql(String sql)throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url
                , user , pass);
            // 使用Connection來創(chuàng)建一個Statement對象
            Statement stmt = conn.createStatement()
            )
        {
            // 執(zhí)行SQL,返回boolean值表示是否包含ResultSet
            boolean hasResultSet = stmt.execute(sql);
            // 如果執(zhí)行后有ResultSet結(jié)果集
            if (hasResultSet)
            {
                try(
                    // 獲取結(jié)果集
                    ResultSet rs = stmt.getResultSet()
                    )
                {
                    // ResultSetMetaData是用于分析結(jié)果集的元數(shù)據(jù)接口
                    ResultSetMetaData rsmd = rs.getMetaData();
                    int columnCount = rsmd.getColumnCount();
                    // 迭代輸出ResultSet對象
                    while (rs.next())
                    {
                        // 依次輸出每列的值
                        for (int i = 0 ; i < columnCount ; i++ )
                        {
                            System.out.print(rs.getString(i + 1) + "	");
                        }
                        System.out.print("
");
                    }
                }
            }
            else
            {
                System.out.println("該SQL語句影響的記錄有"
                    + stmt.getUpdateCount() + "條");
            }
        }
    }
    public static void main(String[] args) throws Exception
    {
        ExecuteSQL es = new ExecuteSQL();
        es.initParam("mysql.ini");
        System.out.println("------執(zhí)行刪除表的DDL語句-----");
        es.executeSql("drop table if exists my_test");
        System.out.println("------執(zhí)行建表的DDL語句-----");
        es.executeSql("create table my_test"
            + "(test_id int auto_increment primary key, "
            + "test_name varchar(255))");
        System.out.println("------執(zhí)行插入數(shù)據(jù)的DML語句-----");
        es.executeSql("insert into my_test(test_name) "
            + "select student_name from student_table");
        System.out.println("------執(zhí)行查詢數(shù)據(jù)的查詢語句-----");
        es.executeSql("select * from my_test");
    }
}
使用PreparedStatement執(zhí)行SQL語句

創(chuàng)建PreparedStatement對象使用Connection的preparedStatement()方法,該方法需要傳入一個SQL字符串,該字符串可以包含符參數(shù)

// 創(chuàng)建一個PreparedStatement對象
pstmt = conn.preparedStatement("insert into student_table values(null,?,1)");

PreparedStatement也提供了execute()、executeUpdate()、executeQuery()三個方法來執(zhí)行SQL語句,不過這三個方法無須參數(shù),因為PreparedStatement提供了一系列的setXxx(int index, Xxx value)方法來傳入?yún)?shù)值

如果程序很清楚PreparedStatement預(yù)編譯SQL語句中各參數(shù)的類型,則使用相應(yīng)的setXxx()方法來傳入?yún)?shù)即可;如果程序不清楚編譯SQL語句中各參數(shù)的類型,則可以使用setObject()方法來傳入?yún)?shù),由PreparedStatement來負責(zé)類型轉(zhuǎn)換

下面程序示范使用Statement和PreparedStatement分別插入100條記錄的對比。使用Statement需要傳入100條SQL語句,但使用PreparedStatement則只需傳入1條預(yù)編譯的SQL語句,然后100次為該PreparedStatement的參數(shù)設(shè)值即可

import java.util.*;
import java.io.*;
import java.sql.*;

public class PreparedStatementTest
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile)throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
        // 加載驅(qū)動
        Class.forName(driver);
    }
    public void insertUseStatement()throws Exception
    {
        long start = System.currentTimeMillis();
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url
                , user , pass);
            // 使用Connection來創(chuàng)建一個Statment對象
            Statement stmt = conn.createStatement())
        {
            // 需要使用100條SQL語句來插入100條記錄
            for (int i = 0; i < 100 ; i++ )
            {
                stmt.executeUpdate("insert into student_table values("
                    + " null ,"姓名" + i + "" , 1)");
            }
            System.out.println("使用Statement費時:"
                + (System.currentTimeMillis() - start));
        }
    }
    public void insertUsePrepare()throws Exception
    {
        long start = System.currentTimeMillis();
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url
                , user , pass);
            // 使用Connection來創(chuàng)建一個PreparedStatement對象
            PreparedStatement pstmt = conn.prepareStatement(
                "insert into student_table values(null,?,1)"))

        {
            // 100次為PreparedStatement的參數(shù)設(shè)值,就可以插入100條記錄
            for (int i = 0; i < 100 ; i++ )
            {
                pstmt.setString(1 , "姓名" + i);
                pstmt.executeUpdate();
            }
            System.out.println("使用PreparedStatement費時:"
                + (System.currentTimeMillis() - start));
        }
    }
    public static void main(String[] args) throws Exception
    {
        PreparedStatementTest pt = new PreparedStatementTest();
        pt.initParam("mysql.ini");
        pt.insertUseStatement();
        pt.insertUsePrepare();
    }
}

SQL注入是一個較常見的Cracker入侵方式,它利用SQL語句的漏洞來入侵。以下程序以一個簡單的登錄窗口為例來介紹這種SQL注入的結(jié)果。下面登錄窗口包含兩個文本框,一個用于輸入用戶名,一個用于輸入密碼,系統(tǒng)根據(jù)用戶輸入與jdbc_test表里的記錄進行匹配,如果找到相應(yīng)記錄則提示登陸成功

public class LoginFrame
{
    private final String PROP_FILE = "mysql.ini";
    private String driver;
    // url是數(shù)據(jù)庫的服務(wù)地址
    private String url;
    private String user;
    private String pass;
    // 登錄界面的GUI組件
    private JFrame jf = new JFrame("登錄");
    private JTextField userField = new JTextField(20);
    private JTextField passField = new JTextField(20);
    private JButton loginButton = new JButton("登錄");
    public void init()throws Exception
    {
        Properties connProp = new Properties();
        connProp.load(new FileInputStream(PROP_FILE));
        driver = connProp.getProperty("driver");
        url = connProp.getProperty("url");
        user = connProp.getProperty("user");
        pass = connProp.getProperty("pass");
        // 加載驅(qū)動
        Class.forName(driver);
        // 為登錄按鈕添加事件監(jiān)聽器
        loginButton.addActionListener(e -> {
            // 登錄成功則顯示“登錄成功”
            if (validate(userField.getText(), passField.getText()))
            {
                JOptionPane.showMessageDialog(jf, "登錄成功");
            }
            // 否則顯示“登錄失敗”
            else
            {
                JOptionPane.showMessageDialog(jf, "登錄失敗");
            }
        });
        jf.add(userField , BorderLayout.NORTH);
        jf.add(passField);
        jf.add(loginButton , BorderLayout.SOUTH);
        jf.pack();
        jf.setVisible(true);
    }
    private boolean validate(String userName, String userPass)
    {
        // 執(zhí)行查詢的SQL語句
        String sql = "select * from jdbc_test "
            + "where jdbc_name="" + userName
            + "" and jdbc_desc="" + userPass + """;
        System.out.println(sql);
        try(
            Connection conn = DriverManager.getConnection(url , user ,pass);
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql))
        {
            // 如果查詢的ResultSet里有超過一條的記錄,則登錄成功
            if (rs.next())
            {
                return true;
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception
    {
        new LoginFrame().init();
    }
}

如果用戶正常輸入其用戶名、密碼,輸入正確時可以正常登陸,輸入錯誤將提示輸入失敗。但如果這個用戶是一個Cracker,可以輸入"or true or",也會顯示登陸成功。運行的后臺可以看到如下SQL語句

# 利用SQL注入后生成的SQL語句
select * from jdbc_test where jdbc_name = "" or true or "" and jdbc_desc = ""

如果換成使用PreparedStatement來執(zhí)行驗證,而不是直接使用Statement

private boolean validate(String userName, String userPass)
{
    try(
        Connection conn = DriverManager.getConnection(url
            , user ,pass);
        PreparedStatement pstmt = conn.prepareStatement(
            "select * from jdbc_test where jdbc_name=? and jdbc_desc=?"))
    {
        pstmt.setString(1, userName);
        pstmt.setString(2, userPass);
        try(
            ResultSet rs = pstmt.executeQuery())
        {
            //如果查詢的ResultSet里有超過一條的記錄,則登錄成功
            if (rs.next())
            {
                return true;
            }
        }
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
    return false;
}

PreparedStatement預(yù)編譯SQL語句,性能更好

PreparedStatement無須“拼接”SQL字符串,編程更簡單

使用PreparedStatement可防止SQL注入,安全性更好

使用PreparedStatement執(zhí)行帶占位符參數(shù)的SQL語句時,SQL語句中的占位符參數(shù)只能代替普通值,不要使用占位符參數(shù)代替表名、列名等數(shù)據(jù)庫對象,更不要用占位符參數(shù)來代替SQL語句中的insert、select等關(guān)鍵字

使用CallableStatement調(diào)用存儲過程

MySQL數(shù)據(jù)庫中創(chuàng)建一個簡單的存儲過程的SQL語句

delimiter //
create procedure add_pro(a int, b int ,out sum int)
begin
set sum = a + b;
end;
//

上面的SQL語句將MySQL的語句結(jié)束符改為雙斜線(//),這樣就可以在創(chuàng)建存儲過程中使用分號作為分隔符(默認使用分號作為語句結(jié)束符)程序創(chuàng)建了名為add_pro的存儲過程,該存儲過程包含三個參數(shù):a、b是傳入?yún)?shù),而sum使用out修飾,是傳出參數(shù)

調(diào)用存儲過程使用CallableStatement,通過Connection的prepareCall()方法來創(chuàng)建CallableStatement對象,創(chuàng)建該對象時需要傳入調(diào)用存儲過程的SQL語句。調(diào)用存儲過程的SQL語句總是這種格式:{call 過程名(?,?,?...)},其中的問號作為存儲過程參數(shù)的占位符

// 使用Connection來創(chuàng)建一個CallableStatement對象
cstmt = conn.prepareCall("{call add_pro(?,?,?)}");

存儲過程的參數(shù)既有傳入?yún)?shù),也有傳出參數(shù),所謂傳入?yún)?shù)就是Java程序必須為這些參數(shù)傳入值,可以通過CallableStatement的setXxx()方法為傳入?yún)?shù)設(shè)置值;所謂傳出參數(shù)就是Java程序可以通過該參數(shù)獲取存儲過程里的值,CallableStatement需要調(diào)用registerOutParameter()方法來注冊該參數(shù)

// 注冊CallableStatement的第三個參數(shù)是int類型
cstmt.registerOutParameter(3, Types.INTEGER);

之后調(diào)用CallableStatement的execute()方法來執(zhí)行存儲過程,執(zhí)行結(jié)束后通過CallableStatement對象的getXxx(int index)方法來獲取指定傳出參數(shù)的值

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.io.*;
import java.sql.*;

public class CallableStatementTest
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile)throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }
    public void callProcedure()throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url
                , user , pass);
            // 使用Connection來創(chuàng)建一個CallableStatment對象
            CallableStatement cstmt = conn.prepareCall(
                "{call add_pro(?,?,?)}"))
        {
            cstmt.setInt(1, 4);
            cstmt.setInt(2, 5);
            // 注冊CallableStatement的第三個參數(shù)是int類型
            cstmt.registerOutParameter(3, Types.INTEGER);
            // 執(zhí)行存儲過程
            cstmt.execute();
            // 獲取,并輸出存儲過程傳出參數(shù)的值。
            System.out.println("執(zhí)行結(jié)果是: " + cstmt.getInt(3));
        }
    }
    public static void main(String[] args) throws Exception
    {
        CallableStatementTest ct = new CallableStatementTest();
        ct.initParam("mysql.ini");
        ct.callProcedure();
    }
}
管理結(jié)果集

JDBC使用ResultSet來封裝執(zhí)行查詢得到的查詢結(jié)果,然后通過移動ResultSet的記錄指針來取出結(jié)果集的內(nèi)容。除此之外,JDBC還允許通過ResultSet來更新記錄,并提供了ResultSetMetaData來獲得ResultSet對象的相關(guān)信息

可滾動、可更新的結(jié)果集

可滾動的結(jié)果集:可以使用absolute()、previous()、afterLast()等方法只有移動指針記錄的ResultSet

以默認形式打開的ResultSet是不可更新的,如果希望創(chuàng)建可更新的ResultSet,則必須在Connection在創(chuàng)建Statement或PreparedStatement時,傳入額外的參數(shù):

resultSetType:控制ResultSet的類型,該參數(shù)可以取如下三個值

ResultSet.TYPE_FORWARD_ONLY:該常量控制記錄指針只能向前移動

ResultSet.TYPE_SCROLL_INSENSITIVE:該常量控制記錄指針自由移動(可滾動結(jié)果集),但底層的數(shù)據(jù)改變不影響結(jié)果集ResultSet的內(nèi)容

ResultSet.TYPE_SCROLL_SENSITIVE:該常量控制記錄指針自由移動,但底層數(shù)據(jù)的影響會改變結(jié)果集ResultSet的內(nèi)容

resultSetConcurrency:控制ResultSet的并發(fā)類型,該參數(shù)可以接收如下兩個值

ResultSet.CONCUR_READ_ONLY:該常量表示ResultSet是只讀并發(fā)模式(默認)

ResultSet.CONCUR_UPDATABLE:該常量表示ResultSet是更新并發(fā)模式

// 使用Connection創(chuàng)建一個PreparedStatement對象
// 傳入控制結(jié)果集可滾動、可更新的參數(shù)
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

可更新的結(jié)果集還需要滿足如下兩個條件:

所有數(shù)據(jù)都應(yīng)該來自一個表

選出的數(shù)據(jù)集必須包含主列鍵

通過該PreparedStatement創(chuàng)建的ResultSet就是可滾動的、可更新的,程序可調(diào)用的updateXxx(int columnIndex, Xxx value)方法來修改記錄指針?biāo)赣涗?、特定列的值,最后調(diào)用ResultSet的updateRow()方法來提交修改

Java8為ResultSet添加了updateObject(String columnLabel, Object x, SQLType targetSqlType)和updateObject(int columnIndex, Object x, SQLType targetSqlType)兩個默認方法,這兩個方法可以直接用Object來修改記錄指針?biāo)赣涗洝⑻囟械闹?,其中SQLType用于指定該數(shù)據(jù)列的類型

import java.util.*;
import java.io.*;
import java.sql.*;

public class ResultSetTest
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile)throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }
    public void query(String sql)throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        try(
            // 獲取數(shù)據(jù)庫連接
            Connection conn = DriverManager.getConnection(url , user , pass);
            // 使用Connection來創(chuàng)建一個PreparedStatement對象
            // 傳入控制結(jié)果集可滾動,可更新的參數(shù)。
            PreparedStatement pstmt = conn.prepareStatement(sql
                , ResultSet.TYPE_SCROLL_INSENSITIVE
                , ResultSet.CONCUR_UPDATABLE);
            ResultSet rs = pstmt.executeQuery())
        {
            rs.last();
            int rowCount = rs.getRow();
            for (int i = rowCount; i > 0 ; i-- )
            {
                rs.absolute(i);
                System.out.println(rs.getString(1) + "	"
                    + rs.getString(2) + "	" + rs.getString(3));
                // 修改記錄指針?biāo)杏涗?、?列的值
                rs.updateString(2 , "學(xué)生名" + i);
                // 提交修改
                rs.updateRow();
            }
        }
    }
    public static void main(String[] args) throws Exception
    {
        ResultSetTest rt = new ResultSetTest();
        rt.initParam("mysql.ini");
        rt.query("select * from student_table");
    }
}
處理Blob類型數(shù)據(jù)

Blob——Binary long Object——二進制長對象,Blob列通常用于存儲大文件,典型的Blob內(nèi)容是一張圖片或者一個聲音文件,由于他們的特殊性,必須使用特殊的方式來存儲。使用Blob列可以把照片聲音等文件的二進制數(shù)據(jù)保存在數(shù)據(jù)庫里,并可以從數(shù)據(jù)庫里恢復(fù)指定文件

如果需要將圖片插入數(shù)據(jù)庫,顯然不能通過普通的SQL語句來完成,因為有一個關(guān)鍵的問題,Blob常量無法表示,所以將Blob數(shù)據(jù)插入數(shù)據(jù)庫需要使用PreparedStatement。該對象有一個方法:setBinaryStream(int parameterIndex, InputStream x)該方法可以為指定參數(shù)傳入二進制流,從而可以實現(xiàn)將Blob數(shù)據(jù)保存到數(shù)據(jù)庫的功能

當(dāng)需要從ResultSet里取出Blob數(shù)據(jù)時,可以調(diào)用ResultSet的getBlob(int columnIndex)方法,該方法將返回一個Blob對象Blob對象提供了getBinaryStream()方法獲取該獲取該Blob數(shù)據(jù)的輸入流,也可以使用Blob對象的getBytes()方法直接取出該Blob對象封裝的二進制數(shù)據(jù)

為了把圖片放入數(shù)據(jù)庫,本程序先使用如下SQL語句來建立一個數(shù)據(jù)表:

create table img_table 
{ 
img_id int auto_increment primary key, 
img_name varchar(255), 
# 創(chuàng)建一個mediumblob類型的數(shù)據(jù)列,用于保存圖片數(shù)據(jù) 
ima_data mediumblob 
};

img_data列使用mediumblob類型,而不是blob類型。因為MySQL數(shù)據(jù)庫里的blob類型最多只能存儲64kb的內(nèi)容,所以使用mediumblob類型,該類型可以存儲16M內(nèi)容

// ---------將指定圖片放入數(shù)據(jù)庫---------
public void upload(String fileName)
{
    // 截取文件名
    String imageName = fileName.substring(fileName.lastIndexOf("") + 1, fileName.lastIndexOf("."));
    File f = new File(fileName);
    try(
        InputStream is = new FileInputStream(f)
        )
    {
        // 設(shè)置圖片名參數(shù)
        insert.setString(1, imageName);
        // 設(shè)置二進制流參數(shù)
        insert.setBinaryStream(2, is, (int)f.length());
        int affect = insert.executeUpdate();
        if (affect == 1)
        {
            // 重新更新ListModel,將會讓JList顯示最新的圖片列表
            fillListModel();
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}
// ---------根據(jù)圖片ID來顯示圖片----------
public void showImage(int id)throws SQLException
{
    // 設(shè)置參數(shù)
    query.setInt(1, id);
    try(
        // 執(zhí)行查詢
        ResultSet rs = query.executeQuery()
        )
    {
        if (rs.next())
        {
            // 取出Blob列
            Blob imgBlob = rs.getBlob(1);
            // 取出Blob列里的數(shù)據(jù)
            ImageIcon icon=new ImageIcon(imgBlob.getBytes(1L, (int)imgBlob.length()));
            imageLabel.setIcon(icon);
        }
    }
}
使用resultsetmetaData分析結(jié)果集

描述ResultSet信息的數(shù)據(jù)——ResultSetMetaData

MetaData即元數(shù)據(jù),即描述其它數(shù)據(jù)的數(shù)據(jù),因此ResultSetMetaData封裝了描述ResultSet對象的數(shù)據(jù)

ResultSet的getMetaData()方法返回該ResultSet對應(yīng)的ResultSetMetaData對象,就可通過ResultSetMetaData提供的大量方法返回ResultSet的描述信息

int getColumnCount():返回該ResultSet的列數(shù)量

String getColumnName(int column):返回指定索引的列名

int getColumnType(int column):返回指定索引的列類型

Java7的RowSet1.1

RowSet接口繼承了ResultSet接口,RowSet接口下包含JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet和WebRowSet常用子接口。除了JdbcRowSet需要保持與數(shù)據(jù)庫的連接之外,其余4個子接口都是離線的RowSet,無須保持與數(shù)據(jù)庫的連接

RowSet默認是一個可滾動,可更新,可序列化的結(jié)果集,而且它作為JavaBeans,可以方便地在網(wǎng)絡(luò)間傳輸,用于兩端的數(shù)據(jù)同步。對于離線RowSet而言,程序在創(chuàng)建RowSet時已把數(shù)據(jù)從底層數(shù)據(jù)庫讀取到了內(nèi)存,因此可以充分利用計算機的內(nèi)存,從而降低數(shù)據(jù)庫服務(wù)器的負載,提供程序性能

RowSet規(guī)范的接口類圖

Java7新增的RowSetFactory與RowSet

RowSet接口中定義的常用方法:

setUrl(String url):設(shè)置該RowSet要訪問的數(shù)據(jù)庫的URL

setUsername(String name):設(shè)置該RowSet要訪問的數(shù)據(jù)庫的用戶名

setPassword(String password):設(shè)置該RowSet要訪問的數(shù)據(jù)庫的密碼

setCommand(String sql):設(shè)置使用該sql語句的查詢結(jié)果來裝填該RowSet

execute():執(zhí)行查詢

populate(ResultSet rs):讓該RowSet直接包裝給定的ResultSet對象

Java7新增了RowSetProvider類和RowSetFactory接口,其中RowSetProvider負載創(chuàng)建RowSetFactory,而RowSetFactory則提供了如下方法來創(chuàng)建RowSet實例:

CachedRowSet createCachedRowSet():創(chuàng)建一個默認的CachedRowSet

FilteredRowSet createFilteredRowSet():創(chuàng)建一個默認的FilteredRowSet

JoinRowSet createJoinRowSet():創(chuàng)建一個默認的JoinRowSet

WebRowSet createWebRowSet():創(chuàng)建一個默認的WebRowSet

JdbcRowSet createJdbcRowSet():創(chuàng)建一個默認的JdbcRowSet

提供使用RowSetFactory,就可以把應(yīng)用程序與RowSet實現(xiàn)類分離開,避免直接使用JdbcRow SetImpl等非公開的API,也更有利于后期的升級、擴展

import java.util.*;
import java.io.*;
import java.sql.*;
import javax.sql.rowset.*;

public class RowSetFactoryTest
{
    private String driver;
    private String url;
    private String user;
    private String pass;
    public void initParam(String paramFile) throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }

    public void update(String sql)throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        // 使用RowSetProvider創(chuàng)建RowSetFactory
        RowSetFactory factory = RowSetProvider.newFactory();
        try(
            // 使用RowSetFactory創(chuàng)建默認的JdbcRowSet實例
            JdbcRowSet jdbcRs = factory.createJdbcRowSet()
            )
        {
            // 設(shè)置必要的連接信息
            jdbcRs.setUrl(url);
            jdbcRs.setUsername(user);
            jdbcRs.setPassword(pass);
            // 設(shè)置SQL查詢語句
            jdbcRs.setCommand(sql);
            // 執(zhí)行查詢
            jdbcRs.execute();
            jdbcRs.afterLast();
            // 向前滾動結(jié)果集
            while (jdbcRs.previous())
            {
                System.out.println(jdbcRs.getString(1)
                    + "	" + jdbcRs.getString(2)
                    + "	" + jdbcRs.getString(3));
                if (jdbcRs.getInt("student_id") == 3)
                {
                    // 修改指定記錄行
                    jdbcRs.updateString("student_name", "源博雅");
                    jdbcRs.updateRow();
                }
            }
        }
    }
    public static void main(String[] args)throws Exception
    {
        RowSetFactoryTest jt = new RowSetFactoryTest();
        jt.initParam("mysql.ini");
        jt.update("select * from student_table");
    }
}
離線RowSet

離線RowSet會直接將底層數(shù)據(jù)讀入內(nèi)存中,封裝成RowSet對象,而RowSet對象則完全可以當(dāng)成Java Bean來使用。因此不僅安全,而且編程簡單。CachedRowSet是所有離線RowSet的父接口

如下程序①處調(diào)用了RowSet的populate(ResultSet rs)方法來包裝給的的ResultSet,接著關(guān)閉了ResultSet、Statement、Connection等數(shù)據(jù)庫資源。如果程序直接返回ResultSet,那么這個Result無法使用——因為底層的Connection已經(jīng)關(guān)閉;但程序返回的是CachedRowSet,一個離線RowSet,因此程序依然可以讀取、修改RowSet中的記錄

為了將程序?qū)﹄x線RowSet所做的修改同步到底層數(shù)據(jù)庫,程序在調(diào)用RowSet的acceptChanges()方法時必須傳入Connection

public class CachedRowSetTest
{
    private static String driver;
    private static String url;
    private static String user;
    private static String pass;
    public void initParam(String paramFile) throws Exception
    {
        // 使用Properties類來加載屬性文件
        Properties props = new Properties();
        props.load(new FileInputStream(paramFile));
        driver = props.getProperty("driver");
        url = props.getProperty("url");
        user = props.getProperty("user");
        pass = props.getProperty("pass");
    }

    public CachedRowSet query(String sql) throws Exception
    {
        // 加載驅(qū)動
        Class.forName(driver);
        // 獲取數(shù)據(jù)庫連接
        Connection conn = DriverManager.getConnection(url, user, pass);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(sql);
        // 使用RowSetProvider創(chuàng)建RowSetFactory
        RowSetFactory factory = RowSetProvider.newFactory();
        // 創(chuàng)建默認的CachedRowSet實例
        CachedRowSet cachedRs = factory.createCachedRowSet();
        // 使用ResultSet裝填RowSet
        cachedRs.populate(rs);    // ①
        // 關(guān)閉資源
        rs.close();
        stmt.close();
        conn.close();
        return cachedRs;
    }
    public static void main(String[] args)throws Exception
    {
        CachedRowSetTest ct = new CachedRowSetTest();
        ct.initParam("mysql.ini");
        CachedRowSet rs = ct.query("select * from student_table");
        rs.afterLast();
        // 向前滾動結(jié)果集
        while (rs.previous())
        {
            System.out.println(rs.getString(1)
                + "	" + rs.getString(2)
                + "	" + rs.getString(3));
            if (rs.getInt("student_id") == 3)
            {
                // 修改指定記錄行
                rs.updateString("student_name", "安倍晴明");
                rs.updateRow();
            }
        }
        // 重新獲取數(shù)據(jù)庫連接
        Connection conn = DriverManager.getConnection(url, user, pass);
        conn.setAutoCommit(false);
        // 把對RowSet所做的修改同步到底層數(shù)據(jù)庫
        rs.acceptChanges(conn);
    }
}
離線RowSet的查詢分頁

CachedRowSet的分頁功能:一次只裝載ResultSet里的某幾條記錄,這樣就可以避免CachedRowSet占用內(nèi)存過大的問題

CachedRowSet提供了如下方法來控制分頁:

populate(ResultSet rs, int startRow):使用給定的Result裝填RowSet,從ResultSet的第startRow條記錄可是裝填

setPageSize(int pageSize):設(shè)置CachedRowSet每次返回記錄條數(shù)

previousPage():在底層ResultSet可用情況下,讓CachedRowSet讀取上一頁記錄

nextPage():在底層ResultSet可用情況下,讓CachedRowSet讀取下一頁記錄

public class CachedRowSetPage
{

private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
    // 使用Properties類來加載屬性文件
    Properties props = new Properties();
    props.load(new FileInputStream(paramFile));
    driver = props.getProperty("driver");
    url = props.getProperty("url");
    user = props.getProperty("user");
    pass = props.getProperty("pass");
}

public CachedRowSet query(String sql, int pageSize, int page) throws Exception
{
    // 加載驅(qū)動
    Class.forName(driver);
    try(
        // 獲取數(shù)據(jù)庫連接
        Connection conn = DriverManager.getConnection(url , user , pass);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(sql)
        )
    {
        // 使用RowSetProvider創(chuàng)建RowSetFactory
        RowSetFactory factory = RowSetProvider.newFactory();
        // 創(chuàng)建默認的CachedRowSet實例
        CachedRowSet cachedRs = factory.createCachedRowSet();
        // 設(shè)置每頁顯示pageSize條記錄
        cachedRs.setPageSize(pageSize);
        // 使用ResultSet裝填RowSet,設(shè)置從第幾條記錄開始
        cachedRs.populate(rs, (page - 1) * pageSize + 1);
        return cachedRs;
    }
}
public static void main(String[] args)throws Exception
{
    CachedRowSetPage cp = new CachedRowSetPage();
    cp.initParam("mysql.ini");
    CachedRowSet rs = cp.query("select * from student_table", 3, 2);   // ①
    // 向后滾動結(jié)果集
    while (rs.next())
    {
        System.out.println(rs.getString(1)
            + "	" + rs.getString(2)
            + "	" + rs.getString(3));
    }
}

}

事務(wù)處理 事務(wù)的概念和MySQL事務(wù)支持

事務(wù)是由一步或幾步數(shù)據(jù)庫操作序列組成的邏輯執(zhí)行單元,這系列操作要么全部執(zhí)行,要么全部放棄執(zhí)行。

事務(wù)具有四個特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持續(xù)性(Durability)。這四個特性也簡稱ACID性

原子性:事務(wù)是應(yīng)用中最小的執(zhí)行單位,就如原子是自然界最小顆粒,具有不可再分的特征一樣。事務(wù)是應(yīng)用中不可再分的最小邏輯執(zhí)行體

一致性:事務(wù)執(zhí)行的結(jié)果,必須使數(shù)據(jù)庫從一個一致性狀態(tài),變到另一個一致性狀態(tài)。當(dāng)數(shù)據(jù)庫中只包含事務(wù)成功提交的結(jié)果時,數(shù)據(jù)庫處于一致性狀態(tài)。一致性是通過原子性來保證的

隔離性:各個事務(wù)的執(zhí)行互不干擾,任意一個事務(wù)的內(nèi)部操作對其他并發(fā)的事務(wù),都是隔離的。也就是說:并發(fā)執(zhí)行的事務(wù)之間不能看到對方的中間狀態(tài),并發(fā)執(zhí)行的事務(wù)之間不能相互影響

持續(xù)性:持續(xù)性也稱為持久性,指事務(wù)一旦提交,對數(shù)據(jù)所做的任何改變,都要記錄到永久存儲器中,通常是保存進物理數(shù)據(jù)庫

數(shù)據(jù)庫的事務(wù)有下列語句組成:

一組DML(Data Manipulate Language,即數(shù)據(jù)操作語言),經(jīng)過這組DML修改后數(shù)據(jù)將保持較好的一致性

一個DDL(Data Definition Language,即數(shù)據(jù)定義語言)語句

一個DCL(Data control Language,即數(shù)據(jù)控制語言)語句

DDL和DCL語句最多只能有一個,因為DDL和DCL語句都會導(dǎo)致事務(wù)立即提交

當(dāng)事務(wù)所包含的全部數(shù)據(jù)庫操作都成功執(zhí)行后,應(yīng)該提交(commit)事務(wù),使這些修改永久生效。事務(wù)提交有兩種方式:顯式提交和自動提交

顯式提交:使用commit

自動提交:執(zhí)行DDL或DCL,或者程序正常退出

當(dāng)事務(wù)所包含的任意一個數(shù)據(jù)庫操作執(zhí)行失敗后,應(yīng)該回滾(rollback)事務(wù),使該事務(wù)中所做的修改全部失效。事務(wù)回滾的方式有兩種:顯式回滾和自動回滾

顯式回滾:使用rollback關(guān)鍵字

隱式回滾:系統(tǒng)錯誤或者強行退出

MySQL默認關(guān)閉事務(wù)(即打開自動提交事務(wù)),在默認情況下,在MySQL控制臺輸入一條DML語句,該語句會立刻保存到數(shù)據(jù)庫中。可以使用下面的語句來開啟事務(wù)(即關(guān)閉自動提交事務(wù)):

// 關(guān)閉自動提交,即開啟事務(wù)
set autocommit = 0; 
// 開啟自動提交,即關(guān)閉事務(wù)
set autocommit = 1;

調(diào)用 set autocommit = 0; 命令后,該命令行窗口里的所有DML語句都不會立即生效,上一個事務(wù)結(jié)束后第一條DML語句將開始一個新的事務(wù),而后續(xù)執(zhí)行的所有SQL語句都處于該事務(wù)中。除非使用commit提交事務(wù)、或正常退出、或運行DDL語句或DCL語句導(dǎo)致事務(wù)隱式提交。也可以使用rollback回滾來結(jié)束事務(wù),使用rollback結(jié)束事務(wù)將會使此事務(wù)中的DML語句所做的修改全部失效

一個MySQL命令行窗口代表一個Session,在該窗口里設(shè)置set autocommit = 0; 相當(dāng)于關(guān)閉了該連接Session的自動提交,對其他連接不會有任何影響

如果不想使得整個Session都打開事務(wù),可以使用start transaction或begin這兩個命令,它們都表示臨時性地開始一次事務(wù)。處于start transaction或begin后的DML語句不會立即生效,除非使用commit顯式提交事務(wù),或者使用DDL語句或DCL語句隱式提交事務(wù)

如下SQL將不會對數(shù)據(jù)庫有任何影響

# 臨時開始事務(wù)
begin;
# 向player_table表插入3條數(shù)據(jù)
insert into player_table
values(null, "Westbrook", 1);
insert into player_table
values(null, "Harden", 2); 
insert into player_table
values(null, "Durant", 3); 
# 查詢player_table表的記錄
select * from player_table;    # ①
# 回滾事務(wù)
rollback;
# 再次查詢
select * from player_table;    # ②   

通過使用savepoint設(shè)置事務(wù)的中間點可以讓事務(wù)回滾到指定中間點,而不是回滾全部事務(wù)。普通的提交、回滾都會結(jié)束當(dāng)前事務(wù),但回滾到指定中間點因為依然處于事務(wù)之中,所以不會結(jié)束當(dāng)前事務(wù)

savepoint a;
# 回滾到指定中間點
rollback to a;
JDBC的事務(wù)支持

JDBC連接的事務(wù)支持由Connection提供,Connection默認打開自動提交,即關(guān)閉事務(wù),在這種情況下,每條SQL語句一旦執(zhí)行,便會立即提交到數(shù)據(jù)庫,永久生效,無法對其進行回滾操作

可以調(diào)用Connection的setAutoCommit()方法來關(guān)閉自動提交,開啟事務(wù)

//關(guān)閉自動提交,開啟事務(wù)
conn.setAutoCommit(false);

一旦事務(wù)開始之后,程序可以像平常一樣創(chuàng)建Statement對象,創(chuàng)建了Statement對象之后,可以執(zhí)行任意多條DML語句,這些SQL語句雖然被執(zhí)行了,但這些SQL語句所作的修改不會生效,因為事務(wù)還沒有結(jié)束。如果所有SQL語句執(zhí)行成功,程序可以調(diào)用Connection的commit方法來提交事務(wù)

//提交事務(wù)
conn.commit();

如果任意一條SQL語句執(zhí)行失敗,應(yīng)該用Connection的rollback來回滾事務(wù)

//回滾事務(wù)
conn.rollback();

當(dāng)Connection遇到一個未處理的SQLException異常時,系統(tǒng)將會非正常退出,事務(wù)也會自動回滾。但如果程序捕獲了該異常,則需要在異常處理塊中顯式地回滾事務(wù)

Connection設(shè)置中間點的方法:

Savepoint setSavepoint():在當(dāng)前事務(wù)中創(chuàng)建一個未命名的中間點,并返回代表該中間點的Savepoint對象

Savepoint setSavepoint(String name):在當(dāng)前事務(wù)中創(chuàng)建一個具有指定名稱的中間點,并返回代表該中間點的Savepoint對象

通常來說,設(shè)置中間點時沒有太大的必要指定名稱,因為Connection回滾到指定中間點時,并不是根據(jù)名字回滾的,而是根據(jù)中間點對象回滾的。Connection提供了rollback(Savepoint savepoint)方法來回滾到指定中間點

Java8增強的批量更新

批量更新必須得到底層數(shù)據(jù)庫的支持,可通過調(diào)用DatabaseMetaData的supportsBatchUpdates()方法來查看底層數(shù)據(jù)庫是否支持批量更新

批量更新需要先創(chuàng)建一個Statement對象,然后利用該對象的addBatch()方法將多條SQL語句同時收集起來,最后調(diào)用Statement對象的executeBatch()(或executeLargeBatch())方法同時執(zhí)行這些SQL語句

批量更新代碼:

Statement stmt = conn.createStatement();  
//使用Statement同時收集多個SQL語句  
stmt.addBatch(sql1);  
stmt.addBatch(sql2);  
stmt.addBatch(sql3);  
...  
//同時執(zhí)行所有的SQL語句  
stmt.executeBatch();  

為了讓批量操作可以正確地處理錯誤,必須把批量執(zhí)行的操作視為單個事務(wù),如果批量更新在執(zhí)行過程中失敗,則讓事務(wù)回滾到批量操作開始之前的狀態(tài)。程序應(yīng)該在開始批量操作之前先關(guān)閉自動提交,然后開始收集更新語句,當(dāng)批量操作結(jié)束之后,提交事務(wù),并恢復(fù)之前的自動提交模式

//保存當(dāng)前的自動的提交模式  
Boolean autoCommit = conn.getAutoCommit();  
//關(guān)閉自動提交  
conn.setAutoCommit(false);  
Statement stmt = conn.createStatement();  
//使用Statement同時收集多條SQL語句  
stmt.addBatch(sql1);  
stmt.addBatch(sql2);  
stmt.addBatch(sql3);  
...  
//同時提交所有的SQL語句  
stmt.executeBatch();  
//提交修改  
conn.commit();  
//恢復(fù)原有的自動提交模式  
conn.setAutoCommit(autoCommit);  
分析數(shù)據(jù)庫信息 使用DatabaseMetaData分析數(shù)據(jù)庫信息

JDBC提供了DatabaseMetaData來封裝數(shù)據(jù)庫連接對應(yīng)數(shù)據(jù)庫的信息,通過Connection提供的getMetaData()方法就可以獲取數(shù)據(jù)庫對應(yīng)的DatabaseMetaData對象

DatabaseMetaData接口通常由驅(qū)動程序供應(yīng)商提供實現(xiàn),其目的是讓用戶了解底層數(shù)據(jù)庫的相關(guān)信息。使用該接口的目的是發(fā)現(xiàn)如何處理底層數(shù)據(jù)庫,尤其是對于試圖與多個數(shù)據(jù)庫一起使用的應(yīng)用程序

許多DatabaseMetaData方法以ResultSet對象的形式返回查詢信息,然后使用ResultSet的常規(guī)方法(如getString()和getInt())即可從這些ResultSet對象中獲取數(shù)據(jù)。如果查詢的信息不可用,則將返回一個空ResultSet對象

DatabaseMetaData的很多方法都需要傳入一個xxxPattern模式字符串,這里的xxxPattern不是正則表達式,而是SQL里的模式字符串,即用百分號(%)代表任意多個字符,使用下劃線(_)代表一個字符。在通常情況下,如果把該模式字符串的參數(shù)值設(shè)置為null,即表明該參數(shù)不作為過濾條件

import java.sql.*;  
import java.io.*;  
import java.util.*;  
public class DatabaseMetaDataTest{  
    private String driver;  
    private String url;  
    private String user;  
    private String pass;  
    public void initParam(String paramFile) throws Exception{  
        //使用Properties類來加載屬性文件  
        Properties props = new Properties();  
        props.load(new FileInputStream(paramFile));  
        driver = props.getProperty("driver");  
        url = props.getProperty("url");  
        user = props.getProperty("user");  
        pass = props.getProperty("pass");  
    }  
    public void info() throws Exception{  
        //加載驅(qū)動  
        Class.forName(driver);  
        try(  
            //獲取數(shù)據(jù)庫連接  
            Connection conn = DriverManager.getConnection(url, user, pass);  
        ){  
            //獲取DatabaseMetaData對象  
            DatabaseMetaData dbmd = conn.getMetaData();  
            //獲取MySQL支持的所有表類型  
            ResultSet rs = dbmd.getTableTypes();  
            System.out.println("---MySQL支持的表類型信息---");  
            printResultSet(rs);  
            //獲取當(dāng)前數(shù)據(jù)庫的全部數(shù)據(jù)表  
            rs = dbmd.getTables(null, null, "%", new String[]{"TABLE"});  
            System.out.println("---當(dāng)前數(shù)據(jù)庫里的數(shù)據(jù)表信息---");  
            printResultSet(rs);  
            //獲取student_table表的主鍵  
            rs = dbmd.getPrimaryKeys(null, null, "student_table");  
            System.out.println("---student_table表的主鍵信息---");  
            printResultSet(rs);  
            //獲取當(dāng)前數(shù)據(jù)庫的全部存儲過程  
            rs = dbmd.getProcedures(null, null, "%");  
            System.out.println("---當(dāng)前數(shù)據(jù)庫里的存儲過程信息---");  
            printResultSet(rs);  
            //獲取teacher_table表和student_table表之間的外鍵約束  
            rs = dbmd.getCrossReference(null, null, "teacher_table", null, null, "student_table");  
            System.out.println("---teacher_table表和student_table表之間的外鍵約束---");  
            printResultSet(rs);  
            //獲取student_table表的全部數(shù)據(jù)列  
            rs = dbmd.getColumns(null, null, "student_table", "%");  
            System.out.println("---student_table表的全部數(shù)據(jù)列---");  
            printResultSet(rs);  
        }  
    }  
    public void printResultSet(ResultSet rs) throws SQLException{  
        ResultSetMetaData rsmd = rs.getMetaData();  
        //打印ResultSet的所有列標(biāo)題  
        for(int i = 0; i < rsmd.getColumnCount(); i++){  
            System.out.print(rsmd.getColumnName(i + 1) + "	");  
        }  
        System.out.print("
");  
        //打印ResultSet的全部數(shù)據(jù)  
        while(rs.next()){  
            for(int i = 0; i < rsmd.getColumnCount(); i ++){  
                System.out.print(rs.getString(i + 1) + "	");  
            }  
            System.out.print("
");  
        }  
        rs.close();  
    }  
    public static void main(String args[]) throws Exception{  
        DatabaseMetaDataTest dmdt = new DatabaseMetaDataTest();  
        dmdt.initParam("sql.ini");  
        dmdt.info();  
    }  
}  
使用系統(tǒng)表分析數(shù)據(jù)庫信息

如已確定應(yīng)用程序所使用的數(shù)據(jù)庫系統(tǒng),則可以通過數(shù)據(jù)庫的系統(tǒng)表來分析數(shù)據(jù)庫信息。系統(tǒng)表又稱為數(shù)據(jù)字典,數(shù)據(jù)字典的數(shù)據(jù)通常由數(shù)據(jù)庫系統(tǒng)負責(zé)維護,用戶通常只能查詢數(shù)據(jù)字典,而不能修改數(shù)據(jù)字典的內(nèi)容

MySQL數(shù)據(jù)庫使用information_schema數(shù)據(jù)庫來保存系統(tǒng)表,在該數(shù)據(jù)庫里包含了大量系統(tǒng)表,常用系統(tǒng)表的簡單介紹如下:

tables:存放數(shù)據(jù)庫里所有數(shù)據(jù)表的信息

schemata:存放數(shù)據(jù)庫里所有數(shù)據(jù)庫(與MySQL的Schema對應(yīng))的信息

views:存放數(shù)據(jù)庫里所有視圖的信息

columns:存放數(shù)據(jù)庫里所有列的信息

triggers:存放數(shù)據(jù)庫里所有觸發(fā)器的信息

routines:存放數(shù)據(jù)庫里所有存儲過程和函數(shù)的信息

key_column_usage:存放數(shù)據(jù)庫里所有具有約束的鍵信息

table_constraints:存放數(shù)據(jù)庫里全部約束的表信息

statistics:存放數(shù)據(jù)庫里全部索引的信息

select * from schemata;  
select * from tables where table_schema = "select_test";  
select * from columns where table_name = "student_table";  
選擇合適的分析方式

通常而言,如果使用DatabaseMetaData來分析數(shù)據(jù)庫信息,則具有更好的跨數(shù)據(jù)庫特性,應(yīng)用程序可以做到數(shù)據(jù)庫無關(guān);但可能無法準(zhǔn)確獲取數(shù)據(jù)庫的更多細節(jié)

使用數(shù)據(jù)庫系統(tǒng)表來分析數(shù)據(jù)庫系統(tǒng)信息會更加準(zhǔn)確,但使用系統(tǒng)表也有壞處——這種方式與底層數(shù)據(jù)庫耦合嚴(yán)重,采用這種方式將會導(dǎo)致程序只能運行在特定的數(shù)據(jù)庫之上

通常來說,如果需要獲得數(shù)據(jù)庫信息,包括該數(shù)據(jù)庫驅(qū)動提供了哪些功能,則應(yīng)該利用DatabaseMetaData來了解該數(shù)據(jù)庫支持哪些功能。完全可能出現(xiàn)這樣一種情況:對于底層數(shù)據(jù)庫支持的功能,但數(shù)據(jù)庫驅(qū)動沒有提供該功能,程序還是不能使用該功能。使用DatabaseMetaData則不會出現(xiàn)這種問題

如果需要純粹地分析數(shù)據(jù)庫的靜態(tài)對象,例如分析數(shù)據(jù)庫系統(tǒng)里包含多少數(shù)據(jù)庫、數(shù)據(jù)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66469.html

相關(guān)文章

  • JDBC系列】從源碼角度理解JDBC和Mysql的預(yù)編譯特性

    摘要:我們對語句做適當(dāng)改變,就完成了注入,因為普通的不會對做任何處理,該例中單引號后的生效,拉出了所有數(shù)據(jù)。查詢資料后,發(fā)現(xiàn)還要開啟一個參數(shù),讓端緩存,緩存是級別的。結(jié)論是個好東西。 背景 最近因為工作調(diào)整的關(guān)系,都在和數(shù)據(jù)庫打交道,增加了許多和JDBC親密接觸的機會,其實我們用的是Mybatis啦。知其然,知其所以然,是我們工程師童鞋們應(yīng)該追求的事情,能夠幫助你更好的理解這個技術(shù),面對問題...

    longshengwang 評論0 收藏0
  • 幾個數(shù)據(jù)持久化框架Hibernate、JPA、Mybatis、JOOQ和JDBC Template的

    摘要:不管是還是,表之間的連接查詢,被映射為實體類之間的關(guān)聯(lián)關(guān)系,這樣,如果兩個實體類之間沒有實現(xiàn)關(guān)聯(lián)關(guān)系,你就不能把兩個實體或者表起來查詢。 因為項目需要選擇數(shù)據(jù)持久化框架,看了一下主要幾個流行的和不流行的框架,對于復(fù)雜業(yè)務(wù)系統(tǒng),最終的結(jié)論是,JOOQ是總體上最好的,可惜不是完全免費,最終選擇JDBC Template。 Hibernate和Mybatis是使用最多的兩個主流框架,而JOO...

    xietao3 評論0 收藏0
  • 學(xué)Java編程需要注意的地方

    摘要:學(xué)編程真的不是一件容易的事不管你多喜歡或是多會編程,在學(xué)習(xí)和解決問題上總會碰到障礙。熟練掌握核心內(nèi)容,特別是和多線程初步具備面向?qū)ο笤O(shè)計和編程的能力掌握基本的優(yōu)化策略。   學(xué)Java編程真的不是一件容易的事,不管你多喜歡或是多會Java編程,在學(xué)習(xí)和解決問題上總會碰到障礙。工作的時間越久就越能明白這個道理。不過這倒是一個讓人進步的機會,因為你要一直不斷的學(xué)習(xí)才能很好的解決你面前的難題...

    leanxi 評論0 收藏0
  • Java學(xué)習(xí)】JDBC的學(xué)習(xí)(了解CLass等)

    摘要:同時也有一些兒高級的處理,比如批處理更新事務(wù)隔離和可滾動結(jié)果集等。連接對象表示通信上下文,即,與數(shù)據(jù)庫中的所有的通信是通過此唯一的連接對象。因為是針對類的關(guān)系而言,所以一個對象對應(yīng)多個類的實例化。返回表示查詢返回表示其它操作。 JDBC是什么? JDBC是一個Java API,用中文可以通俗的解釋為,使用Java語言訪問訪問數(shù)據(jù)庫的一套接口集合。這是調(diào)用者(程序員)和實行者(數(shù)據(jù)庫廠商...

    cjie 評論0 收藏0
  • Java編程基礎(chǔ)33——JDBC

    摘要:是訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)規(guī)范提供了一種基準(zhǔn)據(jù)此可以構(gòu)建更高級的工具和接口,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應(yīng)用程序。在將此值發(fā)送到數(shù)據(jù)庫時,驅(qū)動程序?qū)⑺D(zhuǎn)換成一個類型值。 1.JDBC概念和數(shù)據(jù)庫驅(qū)動程序 A: JDBC概述 JDBC(Java Data Base Connectivity,java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的Java API,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問,...

    KitorinZero 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<