夯基礎(chǔ):涮一遍Java JDBC

JDBC是Java連接數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)魏保,為了兼容大部分?jǐn)?shù)據(jù)庫(kù)澡为,Java提出了JDBC標(biāo)準(zhǔn)漂坏,通過(guò)這個(gè)標(biāo)準(zhǔn),讓各個(gè)數(shù)據(jù)庫(kù)提供實(shí)現(xiàn)支持媒至,這樣實(shí)現(xiàn)一處編碼顶别,處處運(yùn)行的Java特性。

習(xí)慣了ORM框架拒啰,卻忘記了原本的JDBC驯绎,所以我覺(jué)得有必要復(fù)習(xí)來(lái)夯實(shí)一下基礎(chǔ)。

0x00 JDBC 歷史

JDBC是Sun公司為了能夠讓SQL訪(fǎng)問(wèn)統(tǒng)一的一套純JAVA API設(shè)計(jì)的一套接口谋旦,這種接口是遵循了微軟的ODBC API模式剩失。其驅(qū)動(dòng)實(shí)現(xiàn)是各家數(shù)據(jù)庫(kù)供應(yīng)商編寫(xiě)的,通過(guò)JDBC API可以通過(guò)驅(qū)動(dòng)實(shí)現(xiàn)數(shù)據(jù)庫(kù)通信蛤织。

0x01 鏈接數(shù)據(jù)庫(kù)回顧

基本W(wǎng)eb常用的數(shù)據(jù)庫(kù)都是有供Java鏈接的驅(qū)動(dòng)赴叹,

三層結(jié)構(gòu)

那么如何使用JDBC鸿染?
寫(xiě)個(gè)Demo

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/**
 * Created by zing on 2017/3/7.
 */
public class JDBCDemo {

  private  void testJDBC() throws ClassNotFoundException, IOException, SQLException {
        //注冊(cè)驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");
        //JDBC使用類(lèi)似URL的數(shù)據(jù)源描述
        String url = "jdbc:mysql://localhost:3306/demo";//忽略
        //但是我們一般不會(huì)直接這樣寫(xiě)死指蚜。而是使用配置來(lái)描述數(shù)據(jù)源,用戶(hù)名涨椒,密碼
        Properties props = new Properties();
        FileInputStream propertiesFile = new FileInputStream("JDBC.properties");
        props.load(propertiesFile);
        propertiesFile.close();

        String DriverStr = props.getProperty("jdbc.Driver");
        String urlStr = props.getProperty("jdbc.url");
        String userName = props.getProperty("jdbc.name");
        String passcode = props.getProperty("jdbc.passworld");

        //打開(kāi)數(shù)據(jù)庫(kù)鏈接
        Connection connection = DriverManager.getConnection(urlStr,userName,passcode);
        //執(zhí)行SQL
        Statement sta = connection.createStatement();
        //executeUpdate可以返回?cái)?shù)據(jù)庫(kù)更新的行數(shù)
        int efactRow = sta.executeUpdate("UPDATE USER SET Permition = 'admin' WHERE username = 'Zing'");
        //executeQuery可以返回一個(gè)查詢(xún)的結(jié)果集摊鸡,這個(gè)集合的迭代器略有不同Iterator,沒(méi)有hasNext方法,初始是蚕冬,指針在數(shù)據(jù)前免猾,必須調(diào)用next方法才能讀取第一行數(shù)據(jù)
        ResultSet resultSet = sta.executeQuery("SELECT * FROM USER ;");
        while (resultSet.next()){
            //當(dāng)前行獲取第一欄的值,具體類(lèi)型需要看數(shù)據(jù)庫(kù)實(shí)現(xiàn)
            resultSet.getString(1);
        }
        //關(guān)閉語(yǔ)句
        sta.close();
        //關(guān)閉結(jié)果集
        resultSet.close();
        //關(guān)閉數(shù)據(jù)庫(kù)連接
        connection.close();

        /*
        一般情況下囤热,關(guān)閉的操作會(huì)放在catch語(yǔ)句的finally塊中猎提,catch處理數(shù)據(jù)庫(kù)異常,finally來(lái)關(guān)閉連接
        */
    }
}

上面寫(xiě)的是大雜燴旁蔼,一般會(huì)將獲取連接抽取成一個(gè)方法锨苏,異常也會(huì)捕獲,并在try/catch/finally中的finally塊中棺聊,關(guān)閉數(shù)據(jù)庫(kù)連接伞租。

API用法可以看看java.sql.Connectionjava.sql.Statement限佩,java.sql.ResultSet葵诈,這樣裸弦,基本的操作就可以了然了。

boolean execute(String sql) throws SQLException;這個(gè)方法可以執(zhí)行任何SQL作喘,返回執(zhí)行是否成功理疙。

0x02 預(yù)編譯SQL

PrepareStatement,一個(gè)可以讓數(shù)據(jù)庫(kù)預(yù)編譯SQL的API泞坦。
并不是所有的SQL都是寫(xiě)死的沪斟,例如:

  SELECT * FROM UserAccount Where Name =

根據(jù)名稱(chēng)來(lái)查找用戶(hù),這里的名字自然是用戶(hù)自己定義的暇矫,如果用Statement主之,則應(yīng)該這么寫(xiě)

public void findUserByName(String name){
  Statement  sta = connection.createStatement();
  String findByName = "SELECT * FROM USER WHERE Name=' "+name+" ';";
  ResultSet resultSet = sta.executeQuery(findByName);
}

如果將name交給普通用戶(hù)來(lái)輸入,則沒(méi)什么問(wèn)題李根,但是 如果交給黑客槽奕,name他會(huì)輸入 小明' OR '1' = '1,這樣語(yǔ)句拼接后就會(huì)變成

SELECT * FROM USER WHERE Name=' 小明' OR '1' = '1';

這一句就會(huì)把數(shù)據(jù)庫(kù)所有的用戶(hù)全部查出來(lái)了,很?chē)?yán)重的注入漏洞房轿,基本就會(huì)被脫庫(kù)了粤攒。

所以Java JDBC定義的預(yù)編譯SQL的API。
上例子:


import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

public class JDBCDemo {

    private void testJDBC() {

        Connection connection = null;
        Statement sta = null;
        ResultSet resultSet = null;
        PreparedStatement preSta = null;
        try {
            connection = getConnection();
            //執(zhí)行SQL
            
            String findByName = "SELECT * FROM USER WHERE Name=?;";
            preSta = connection.prepareStatement(findByName);
            preSta.setString(1,"Zing");
            resultSet = preSta.executeQuery();


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            /*
                一般情況下囱持,關(guān)閉的操作會(huì)放在catch語(yǔ)句的finally塊中夯接,catch處理數(shù)據(jù)庫(kù)異常,finally來(lái)關(guān)閉連接
            */
            //關(guān)閉語(yǔ)句
            try {
                preSta.close();
                //關(guān)閉結(jié)果集
                resultSet.close();
                //關(guān)閉數(shù)據(jù)庫(kù)連接
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }

    public Connection getConnection() throws ClassNotFoundException, IOException, SQLException {
        //注冊(cè)驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");
        //JDBC使用類(lèi)似URL的數(shù)據(jù)源描述
        //但是我們一般不會(huì)直接這樣寫(xiě)死纷妆。而是使用配置來(lái)描述數(shù)據(jù)源盔几,用戶(hù)名,密碼
        Properties props = new Properties();
        FileInputStream propertiesFile = new FileInputStream("JDBC.properties");
        props.load(propertiesFile);
        propertiesFile.close();

        String DriverStr = props.getProperty("jdbc.Driver");
        String urlStr = props.getProperty("jdbc.url");
        String userName = props.getProperty("jdbc.name");
        String passcode = props.getProperty("jdbc.passworld");
        return DriverManager.getConnection(urlStr, userName, passcode);

    }
}

順便重構(gòu)了之前的代碼掩幢。
我們用 ?占位逊拍,留下可變參數(shù)的位置,后來(lái)再用setString(int parameterIndex, String x)這個(gè)方法將數(shù)據(jù)填充進(jìn)SQL际邻,這樣芯丧,如果參數(shù)含有SQL關(guān)鍵字時(shí),就不能通過(guò)編譯世曾,查不到結(jié)果缨恒。可以避免SQL注入轮听。

preSta.setString(1,"Zing");表示骗露,在第一個(gè)?處設(shè)置參數(shù)為Zing
當(dāng)然參數(shù)是數(shù)字,日期時(shí)蕊程,可以使用椒袍,
void setDouble(int parameterIndex, double x) throws SQLException
setDate(int parameterIndex, java.sql.Date x) throws SQLException;

等方法,根據(jù)不同類(lèi)型設(shè)置參數(shù)藻茂。

0x03 數(shù)據(jù)庫(kù)類(lèi)型與轉(zhuǎn)義

數(shù)據(jù)庫(kù)類(lèi)型和Java類(lèi)型是有一點(diǎn)不一樣的驹暑,但是JDBC定義了其中的大部分類(lèi)型玫恳,這里不一一列舉

MySQL部分類(lèi)型對(duì)照表,有興趣可以查一查

JDBC中的轉(zhuǎn)義是為了讓Java訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)時(shí)优俘,得到普遍的支持京办。一般用于下列特性

  • 時(shí)間日期的字面常亮
  • 標(biāo)量函數(shù)調(diào)用
  • 存儲(chǔ)過(guò)程調(diào)用
  • 外連接查詢(xún)
  • LIKE子句中轉(zhuǎn)義字符

數(shù)據(jù)庫(kù)的日期轉(zhuǎn)換成Java的日期,是通過(guò)ISO8601標(biāo)準(zhǔn)衡定并相互轉(zhuǎn)換的

d表示DATE帆焕、t表示TIME惭婿、ts表示TIMESTANP

{d '2017-01-22'}
{t '19:30:29'}
{ts '2017-01-22 19:30:29.989'}

標(biāo)量函數(shù)是獲取一個(gè)數(shù)值的函數(shù),一般調(diào)用時(shí)嵌入標(biāo)準(zhǔn)函數(shù)名和參數(shù)叶雹,這個(gè)很少見(jiàn)到有人使用的财饥,就不舉例了。

存儲(chǔ)過(guò)程折晦,是數(shù)據(jù)庫(kù)自建的存儲(chǔ)方式钥星,不同的數(shù)據(jù)庫(kù)存儲(chǔ)過(guò)程基本不一樣,要調(diào)用存儲(chǔ)過(guò)程满着,需要用call來(lái)進(jìn)行轉(zhuǎn)義

{call PROC01(?,?)}
{call PROC02}

如果你不明白什么存儲(chǔ)過(guò)程谦炒,可以看看數(shù)據(jù)庫(kù)相關(guān)的資料。

外連接风喇,就是Outter Join宁改,借用核心卷II中的例子

SELECT * FROM {oj Books LEFT OUTER JOIN Publishers ON Books.Publish_ID = Publishers.Publish_ID }

這條語(yǔ)句表示查詢(xún)找不到出版商的書(shū),相反如果是RIGHT OUTER JOIN則會(huì)查詢(xún)出沒(méi)有出版書(shū)的出版商魂莫,如果需要查到全部还蹲,則用FULL OUTER JOIN
。這里用轉(zhuǎn)義是因?yàn)橛行?shù)據(jù)庫(kù)實(shí)現(xiàn)不太統(tǒng)一豁鲤。

Like子句轉(zhuǎn)義秽誊,是因?yàn)橄聞澗€(xiàn)和百分號(hào)在Like條件里是特殊的含義鲸沮,需要用轉(zhuǎn)義來(lái)表示

SELECT * FROM User WHERE Name LIKE %!_%ming {escape '!'}

{escape '!'}表示將琳骡!定義為轉(zhuǎn)義符號(hào),!_表示字面量下劃線(xiàn)

0x04 事務(wù)

為了保證數(shù)據(jù)和業(yè)務(wù)邏輯的完整性讼溺,我們可以將一系列的SQL語(yǔ)句構(gòu)建成一個(gè)事物楣号,當(dāng)所有語(yǔ)句都順利執(zhí)行的時(shí)候,事務(wù)可以被提交怒坯。但是如果中途被阻礙炫狱,則數(shù)據(jù)會(huì)被回滾,將數(shù)據(jù)恢復(fù)成執(zhí)行前的樣子剔猿。

首先需要關(guān)閉數(shù)據(jù)庫(kù)自動(dòng)提交

connection.setAutoCommit(false);

然后根據(jù)實(shí)際業(yè)務(wù)執(zhí)行多條UPDATE INSERT DELETE語(yǔ)句

statement.executeUpdate("SQL1");
statement.executeUpdate("SQL2");
statement.executeUpdate("SQL3");

當(dāng)所有語(yǔ)句順利執(zhí)行后视译,調(diào)用

connection.commit();

如果遇到異常或錯(cuò)誤归敬,則可以調(diào)用

connection.rollback();

其中JDBC支持事務(wù)保存點(diǎn)和批量更新
保存點(diǎn):將事務(wù)的某一階段設(shè)置為保存點(diǎn)后酷含,可以控制回滾時(shí)鄙早,恢復(fù)到這個(gè)保存點(diǎn)的數(shù)據(jù)。從而更加精確的控制回滾操作
批量更新就是將大量數(shù)據(jù)一次性存入椅亚,或修改大量數(shù)據(jù)時(shí)使用的限番。兩個(gè)??:

statement.executeUpdate("SQL1");
Savepoint step1 = connection.setSavepoint();
statement.executeUpdate("SQL2");
if(something==false){
  connection.rollback(step1);
}
String updateSQL = "……";
statement.addBatch(updateSQL);
while(needUpdate){
  command = "……"+"updateSQL2"
  statement.addBatch(updateSQL);
}
//批量執(zhí)行
int effectRows = statement.executeBatch();

批量執(zhí)行中一定不能有查詢(xún)語(yǔ)句,否則會(huì)拋出異常呀舔。

0x05 文件查詢(xún)和存入數(shù)據(jù)庫(kù)

不建議這么搞弥虐,數(shù)據(jù)庫(kù)存入太多大文件會(huì)導(dǎo)致數(shù)據(jù)庫(kù)龐大,備份和恢復(fù)的成本將增加媚赖。
在數(shù)據(jù)庫(kù)中霜瘪,二進(jìn)制大對(duì)象稱(chēng)為Blob,字符型大對(duì)象為Clob
這里演示一下查詢(xún)和存儲(chǔ)

       //讀取
        PreparedStatement preparedStatement01 = connection.prepareStatement("SELECT picture FROM PictureTab WHERE picName=?;");
        preparedStatement01.setString(1,"superman");
        ResultSet rs = preparedStatement01.executeQuery();
        if(rs.next()){
            Blob picBlob = rs.getBlob(1);
            Image pic = ImageIO.read(picBlob.getBinaryStream());
        }

        //存儲(chǔ)
        Blob pictureBlob = connection.createBlob();
        int offset = 0;
        OutputStream outStram = pictureBlob.setBinaryStream(offset);
        ImageIO.write(pictureBlob,"PNG",outStram);
        PreparedStatement preparedStatement02 = connection.prepareStatement("INSERT INTO PictureTab VALUE (?,?);");
        preparedStatement02.setString(1, "SuperMan");
        preparedStatement02.setBlob(2,pictureBlob);
        preparedStatement02.executeUpdate();

0x06 其他一些概念

  • 元數(shù)據(jù):數(shù)據(jù)庫(kù)的結(jié)構(gòu)和表信息等描述數(shù)據(jù)庫(kù)結(jié)構(gòu)和組成部分的數(shù)據(jù)
  • 多結(jié)果集:一次查詢(xún)惧磺,使用多個(gè)Select SQL語(yǔ)句是粥庄,會(huì)得到一個(gè)多結(jié)果集
  • 可滾動(dòng)結(jié)果集:可以向前,向后查詢(xún)的結(jié)果集豺妓,之前的只能用Next向后查詢(xún)惜互,使
Statement stat = Connection.createStatement(ResultSet.TYPE_SCROLL_INSENSTIVE , ResultSet.CONCUR_READ_ONLY )

在獲取結(jié)果集的時(shí)候,會(huì)變成一個(gè)可滾動(dòng)集琳拭。

  • 獲取數(shù)據(jù)庫(kù)生成鍵值statemwnt.getGeneratedKeys();
  • 行集 RowSet接口繼承了ResultSet训堆,但不需要長(zhǎng)時(shí)間占用數(shù)據(jù)庫(kù)鏈接。

love&peace
我的博客:https://micorochio.github.io/
轉(zhuǎn)載請(qǐng)注明出處:https://micorochio.github.io/2017/03/10/basic-of-java-JDBC/白嘁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坑鱼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子絮缅,更是在濱河造成了極大的恐慌鲁沥,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耕魄,死亡現(xiàn)場(chǎng)離奇詭異画恰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)吸奴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)允扇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人则奥,你說(shuō)我怎么就攤上這事考润。” “怎么了读处?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵糊治,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我罚舱,道長(zhǎng)井辜,這世上最難降的妖魔是什么揖赴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮抑胎,結(jié)果婚禮上燥滑,老公的妹妹穿的比我還像新娘。我一直安慰自己阿逃,他們只是感情好铭拧,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著恃锉,像睡著了一般搀菩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上破托,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天肪跋,我揣著相機(jī)與錄音,去河邊找鬼土砂。 笑死州既,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萝映。 我是一名探鬼主播吴叶,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼序臂!你這毒婦竟也來(lái)了蚌卤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奥秆,失蹤者是張志新(化名)和其女友劉穎逊彭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體构订,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侮叮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鲫咽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片签赃。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖分尸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情歹嘹,我是刑警寧澤箩绍,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站尺上,受9級(jí)特大地震影響材蛛,放射性物質(zhì)發(fā)生泄漏圆到。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一卑吭、第九天 我趴在偏房一處隱蔽的房頂上張望芽淡。 院中可真熱鬧,春花似錦豆赏、人聲如沸挣菲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)白胀。三九已至,卻和暖如春抚岗,著一層夾襖步出監(jiān)牢的瞬間或杠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工宣蔚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留向抢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓胚委,卻偏偏與公主長(zhǎng)得像笋额,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子篷扩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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