1. 程式人生 > >【手把手】JavaWeb 入門級專案實戰 -- 文章釋出系統 (第五節)

【手把手】JavaWeb 入門級專案實戰 -- 文章釋出系統 (第五節)

在上一節中,我們成功將資料從前臺的JSP頁面傳遞到了controller層,但是還沒有寫service層,老實說還有很多工作沒有,尤其是和資料庫的連結方面的,所以,這一節,我們專門來處理一下關於資料庫連線方面的東西。

01 序言

你可能之前聽過了很多新名詞,比如資料來源,連線池,還有c3p0等等。作為新手很容易被這些名詞給嚇到,因為一般的培訓機構不會告訴你這些,他們僅僅是給你講了最基本的jdbc,一般來說,就是告訴你用Java程式碼來操作資料庫的幾個步驟。

首先,載入驅動類(媽了個雞蛋糕,對初學者而言,很可能連反射都不知道,就在那學習載入什麼驅動類了,天知道學了些什麼,越聽越糊塗)。

我有很多去培訓機構的朋友就是如此,培訓都結束了,也不知道為什麼要學jdbc

,只會課堂上那幾個乾巴巴的案例。去企業面試都只有被嘲笑的份。

然後,載入好了驅動類,需要用一個什麼 DriverManager 來獲取連結。再接著,就寫預處理語句,執行一下,拿到結果集 ResultSet 。(媽了個雞蛋糕,對初學者而言,都不知道基本的迭代器,迴圈,就在那寫什麼 while(rs.next()){ ... } 了,最終的結果可想而知。

我個人是不太主張去那些不太出名的,或者口碑很差的培訓機構,如果一定要去培訓,那麼起碼找個靠譜的吧,畢竟花這麼多錢呢!如果花了錢,大部分知識竟然還是要去企業裡面從0學起,到那時候你真的是要多鬱悶有多鬱悶。

不過也沒辦法,因為就算是再差的培訓機構,教的也總是要比學校實用一些。每年都有那麼多畢業生畢業後不知道幹嘛,說起來真是挺鬱悶的。

去找工作吧,屢屢碰壁,好不容易找到了一個面試的,過去一問,原來是培訓的。

然後想想自己確實沒有什麼技術,所以只好咬咬牙花一萬多塊錢去培訓了。

這樣的例子太多了,博主的很多朋友和同學就是這樣走上了Java程式開發的道路。

我不反對去培訓機構學習,只是我想說,如果自制力好的話,還是儘量自學比較划算。

如果實在沒有自控力,去培訓機構也是一個不錯的選擇。

總之,視你的具體情況而定吧。

OK,回到正題。

最後,在jdbc操作的末尾,總歸少不了關閉連結,否則會導致資源的浪費。

當然,這些東西的確很重要,jdbc是Java程式碼訪問和操作資料庫的方式。這些基本的API方法也形成了所有JDBC框架的根,比如Spring-jdbc

就是對這些相對底層的程式碼的封裝。

這就是所謂的jdbc1.0規範,但這樣有一個缺點,就是我每次進行資料庫操作都要去獲取一個連線,然後再自己關掉。這樣很麻煩,人總是聰明的,所以為了改進,就有了jdbc2.0規範。

那些資料來源,連線池的概念就屬於jdbc2.0規範,就是說,我先建立好一大堆連線,誰要用誰就去拿。這樣就解決了jdbc1.0的弊端,不需要使用者每次都去建立連線,最後再關閉連結了。

因為這個小專案畢竟是以基礎優先的,所以我還是介紹一下jdbc1.0的使用方法吧。我會逐步封裝出一個JDBC工具類出來,我以前也沒寫過,都是直接用框架的,這是我的第一次嘗試,難免會有不恰當的地方,還希望各位多多包涵。

如有疑問或者不同的見解,可以在評論區指出,我會虛心改正的,謝謝。

02 預備知識

最好對以下知識有一個初步的瞭解後,再來看本篇文章,當然,不看也沒關係啦。。。

1、反射
2、泛型
3、JDBC API簡單使用
4、properties 檔案的讀取
5、 IO流

03 準備好mysql

好了,正式開始吧。我在本地已經安裝好了mysql。mysql安裝的話,隨便百度一下就可以了。

在上一節中,我們新建了一個數據庫Article,使用者名稱就是root,密碼沒有。root使用者享有本地mysql的最高許可權。

win + R,輸入cmd

輸入 mysql -uroot (密碼不需要填寫)

輸入 use Article

好了,資料庫方面的工作就做好了。

如果無法進入mysql,可能是沒有啟動。在命令列輸入:net start mysql即可。

04 編寫jdbc.properties

我們要連線資料庫,最基本的資訊有使用者名稱,密碼,還有資料庫名。

於是,我們在src目錄上新建一個config原始檔夾(注意,是原始檔夾,不是普通的資料夾,所有的原始檔夾不會真的產生一個資料夾,它裡面的檔案預設在 CLASSPATH 根目錄下),再建立一個jdbc.properties檔案:

內容

db.username=root
db.password=
db.dataBaseName=article

OK,這一步也完成了,接下來就是如何讀取這個檔案裡的資訊。

05 讀取properties檔案

在test包下新建一個測試類,來試著讀取properties檔案。

package test;
/**
 * 讀取Properties檔案的測試類
 * @author 剽悍一小兔
 */
public class TestProperties {
    public static void main(String[] args) {
        
    }
}

properties檔案(配置檔案)就是一個類似於Map的東西,為什麼要把連線資訊放在檔案裡呢?那是因為,如果你寫在Java類中,這固然是可以的。但是,不利於維護啊。

比如,專案一旦上線,基本上就是專門的維護人員在跟進了,一旦要改個什麼配置資訊,作為開發人員,你只需要和對方講,找到一個什麼什麼properties檔案,然後將某一行改掉就好了。如果你不用配置檔案,直接將資訊寫在Java類中,那麼你就很難描述清楚了,改起來也特別麻煩。

這就是原因。

以下是讀取的程式碼,流程就是我先把這個檔案變成一個輸入流InputStream,然後new一個Properties ,再去載入之前獲得的輸入流。

jdbc.properties是一個實實在在的檔案。而且,它還是一個特殊的檔案,因為它裡面的內容都是 key=value 的形式。現在的問題就在於怎麼用Java程式碼來讀取裡面的資訊呢?

InputStream inputStream =  TestProperties.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties p = new Properties();
try {
    p.load(inputStream);
    System.out.println(p);
} catch (IOException e) {
    e.printStackTrace();
}

你可以把InputStream想象成一根吸管

InputStream inputStream =  TestProperties.class.getClassLoader().getResourceAsStream("jdbc.properties");

這行程式碼就相當於插了一根吸管在 jdbc.properties 檔案上面,準備抽取裡面的資訊。

p.load(inputStream);

這行程式碼表示將吸管和 Properties 物件接通。

額,如果實在沒有Java IO的基礎,就暫時這麼想象一下吧。。。

 System.out.println(p);

輸出:
{db.password=, db.dataBaseName=article, db.username=root}

分開來列印:

System.out.println(p.getProperty("db.username"));
System.out.println(p.getProperty("db.password"));
System.out.println(p.getProperty("db.dataBaseName"));

輸出:
root

article

(因為密碼為空,所以沒顯示出來)

這樣的話,載入資原始檔也沒有問題了。

06 開始封裝自己的DataBaseUtils

DataBaseUtils的意思就是資料庫工具類,你可以把這個看成是一個自己的小框架。

我們已經知道要訪問資料庫的話,需要有username,password,還有dataBaseName。所以,這三個資料就作為工具類的屬性吧。

private static String username; //使用者名稱
private static String password; //密碼
private static String dataBaseName; //資料庫名

接下來,專門定義一個方法來載入properties。

/**
 * 配置資料庫的基本資訊
 * @return void
 */
public static void config(String path){
    InputStream inputStream = DataBaseUtils.class.getClassLoader().getResourceAsStream(path);
    Properties p = new Properties();
    try {
        p.load(inputStream);
        username = p.getProperty("db.username");
        password = p.getProperty("db.password");
        dataBaseName = p.getProperty("db.dataBaseName");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

一旦呼叫了這個方法,那麼就會給私有屬性賦值。

為了方便起見,我們讓DataBaseUtils類被載入的時候就自動配置 jdbc.properties,比較容易想到的一個方法就是定義一個static塊,然後在裡面呼叫一下 config 方法:

static {
    config("jdbc.properties");
}

這樣一來,只要你呼叫了這個DataBaseUtils中的方法,就會自動配置連線資訊了。

獲取連線的方法:

/**
 * 獲取資料庫連結
 * @return Connection 
 */
public static Connection getConnection(){
    Connection connection = null;
    try {
        Class.forName("com.mysql.jdbc.Driver");
        connection  = DriverManager.getConnection("jdbc:mysql://localhost:3306/"+dataBaseName+"?useUnicode=true&characterEncoding=utf8",username,password);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return connection;
}

測試:

DataBaseUtils.config("jdbc.properties");
Connection conn = DataBaseUtils.getConnection();
System.out.println(conn);

這就說明成功獲取連線了。

關閉連線和其他資源的方法

/**
 * 關閉資源
 * @param connection
 * @param statement
 * @param rs
 */
public static void closeConnection(Connection connection,PreparedStatement statement,ResultSet rs){
    try {
        if(rs!=null)rs.close();
        if(statement!=null)statement.close();
        if(connection!=null)connection.close();
    } catch (Exception e) {
        e.fillInStackTrace();
    }
}

07 DML操作的實現

DML表示—資料操縱語言,也就是SELECT,DELETE,UPDATE,INSERT。

現在我們開始封裝DML的操作。

上程式碼:

/**
 * DML操作
 * @param sql
 * @param objects
 */
public static void update(String sql,Object...objects){
    Connection connection = getConnection();
    PreparedStatement statement = null;
    try {
        statement = (PreparedStatement) connection.prepareStatement(sql);
        for (int i = 0; i < objects.length; i++) {
            statement.setObject(i+1, objects[i]);
        }
        statement.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally{
        closeConnection(connection, statement, null);
    }
}

Object...objects是變長引數,你可以把它理解成一個Object陣列。

測試:

String id = UUID.randomUUID() + "";
String createTime = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
update("INSERT INTO t_user(id,username,password,sex,create_time,is_delete,address,telephone) "
        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", id,"張三",123456,0,createTime,0,"保密","保密");

執行結果:

com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'id' at row 1
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2983)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1631)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1723)
    at com.mysql.jdbc.Connection.execSQL(Connection.java:3283)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1332)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1604)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1519)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1504)
    at util.DataBaseUtils.update(DataBaseUtils.java:147)
    at util.DataBaseUtils.main(DataBaseUtils.java:165)

發現報錯了,不慌,看看它說什麼。

因為是直播,所以如果我的程式碼報錯了,也會把錯誤放上來,然後糾正。我也是普通人,很難保證程式碼一次性就寫對。

我知道,初學的時候看到報錯就害怕,這是很正常的。沒事,慢慢來。

好的,讓我們看看它說什麼。

Data truncation: Data too long for column 'id' at row 1

哦,這說明id的欄位長度太短了,而我們生成的UUID太長。

找到User類

我們把id的長度調大一點,換成varchar(100)吧。

接著,修改一下TableUtils,將建表語句加一個刪除舊錶的判斷。

public static String getCreateTableSQl(Class<?> clazz){
    StringBuilder sb = new StringBuilder();
    
    //獲取表名
    Table table = (Table) clazz.getAnnotation(Table.class);
    String tableName = table.tableName();
    sb.append("DROP TABLE IF EXISTS ").append(tableName).append(";\n");
    sb.append("create table ");
    sb.append(tableName).append("(\n");
    
    Field[] fields = clazz.getDeclaredFields();
    String primaryKey = "";
    //遍歷所有欄位
    for (int i = 0; i < fields.length; i++) {
        Column column = (Column) fields[i].getAnnotations()[0];
        String field = column.field();
        String type = column.type();
        boolean defaultNull = column.defaultNull();
        
        sb.append("\t" + field).append(" ").append(type);
        if(defaultNull){
            if(type.toUpperCase().equals("TIMESTAMP")){
                sb.append(",\n");
            }else{
                sb.append(" DEFAULT NULL,\n");
            }
        }else{
            sb.append(" NOT NULL,\n");
            if(column.primaryKey()){
                primaryKey = "PRIMARY KEY ("+field+")";
            }
        }
    }
    
    if(!StringUtils.isEmpty(primaryKey)){
        sb.append("\t").append(primaryKey);
    }
    sb.append("\n) DEFAULT CHARSET=utf8");
    
    return sb.toString();
}

然後,重新生成一下sql語句。

String sql = TableUtils.getCreateTableSQl(User.class);
System.out.println(sql);

加分號,回車。

查看錶結構

成功改變了。

好的,回到DataBaseUtils,執行新增操作

String id = UUID.randomUUID() + "";
String createTime = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
update("INSERT INTO t_user(id,username,password,sex,create_time,is_delete,address,telephone) "
        + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", id,"張三",123456,0,createTime,0,"保密","保密");

讓我們查詢一下現在表裡有幾條資料。

嗯,應該是成功了呢。

時間關係,本章先介紹到這裡。國慶放假正好有時間,過幾天還會有一章。

原始碼的話,從下一章開始,還是以網盤的形式分享出來吧。每一章我都會提供一個版本的。

本文結束。

最近被瘋漲的房價弄得有點鬱悶,唉,這TM才幾個月啊,博主待的小城市的房價就已經遠遠超出承受範圍了。

說多了全是淚。。。

踏入社會後,才知道賺錢的不易。說實話,博主根本沒有想到普通小城市的房價也能長成這樣。

我當初就是顧慮到房價,也不想每天擠數個小時的地鐵去上班,這才沒有去北上廣。

原本計劃在自己的家鄉安安穩穩的找份工作,過平靜的日子。不料今年的房價猛地一竄,把所有的計劃都打亂了。

哎,隨遇而安吧。