【手把手】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才幾個月啊,博主待的小城市的房價就已經遠遠超出承受範圍了。
說多了全是淚。。。
踏入社會後,才知道賺錢的不易。說實話,博主根本沒有想到普通小城市的房價也能長成這樣。
我當初就是顧慮到房價,也不想每天擠數個小時的地鐵去上班,這才沒有去北上廣。
原本計劃在自己的家鄉安安穩穩的找份工作,過平靜的日子。不料今年的房價猛地一竄,把所有的計劃都打亂了。
哎,隨遇而安吧。