實戰(zhàn) - 業(yè)務(wù)實現(xiàn) 1
上一小節(jié)我們完成了數(shù)據(jù)庫的設(shè)計和創(chuàng)建,也向數(shù)據(jù)表中插入了一些初始數(shù)據(jù),本小節(jié)我們將開始具體業(yè)務(wù)代碼的實現(xiàn),如果大家還沒有完成上一小節(jié)的任務(wù),請務(wù)必先完成再來學(xué)習本節(jié)內(nèi)容。
1. 準備工作
在開始正式編碼之前,我們要做一些準備工作,主要是環(huán)境的搭建和工具類的引入。
1.1 創(chuàng)建 Maven 工程
打開 idea
,點擊Create new Project
按鈕:

在左側(cè)欄選擇Maven
,Project SDK
選擇14
,勾選Create from archetype
復(fù)選框,再選擇maven-archetype-quickstart
,表示創(chuàng)建一個簡單 Java 應(yīng)用,點擊next
按鈕:

輸入項目名稱goods
,將項目路徑設(shè)置為本地桌面,GroupId
可根據(jù)實際情況自定義,此處我設(shè)置為com.colorful
,其余輸入框無需修改,采用默認即可,設(shè)置完成后,點擊next
按鈕:

這一步來到Maven
配置,idea
自帶了Maven
,我們使用默認的即可,直接點擊Finish
按鈕完成項目創(chuàng)建:

此時,Maven
會進行一些初始化配置,右下角對話框選擇Enable Auto-import
按鈕,表示允許自動導(dǎo)入依賴:

稍等片刻,待看到左側(cè)項目的目錄結(jié)構(gòu)已經(jīng)生成好了,及表示已完成項目的初始化工作:

1.2 引入 MySQL 驅(qū)動
接下來引入mysql-connector-java
驅(qū)動,由于我本地安裝的MySQL
版本為8.0.21
,因此mysql-connector-java
的版本號也選擇8.0.21
,大家根據(jù)自己實際情況選擇對應(yīng)版本。
打開pom.xml
文件,在<dependencies></dependencies>
節(jié)點內(nèi)插入如下xml
:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>

由于我們已經(jīng)配置了允許自動導(dǎo)入依賴,稍等片刻,mysql-connector-java 8.0.21
就會被成功導(dǎo)入??稍?code>idea右側(cè)點擊Maven
按鈕查看項目的依賴關(guān)系:

1.3 引入 JDBC 工具類
JDBC 相關(guān)操作是本項目的最常用的操作,我封裝了一個 JDBC 的工具類,主要通過 Java 的 JDBC API 去訪問數(shù)據(jù)庫,提供了加載配置、注冊驅(qū)動、獲得資源以及釋放資源等接口。
大家可以到我的 Github
倉庫下載這個 JDBCUtil
類;也可以直接復(fù)制下面的代碼:
package com.colorful.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @author colorful@TaleLin
*/
public class JDBCUtil {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
static {
// 加載屬性文件并解析
Properties props = new Properties();
// 使用類的加載器的方式進行獲取配置
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
assert inputStream != null;
props.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = props.getProperty("driverClass");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
}
/**
* 注冊驅(qū)動
*/
public static void loadDriver() throws ClassNotFoundException{
Class.forName(driverClass);
}
/**
* 獲得連接
*/
public static Connection getConnection() throws Exception{
loadDriver();
return DriverManager.getConnection(url, username, password);
}
/**
* 資源釋放
*/
public static void release(PreparedStatement statement, Connection connection){
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
/**
* 釋放資源 重載方法
*/
public static void release(ResultSet rs, PreparedStatement stmt, Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
我本地將這個類放在了 com.colorful.util
包下,大家可根據(jù)自身情況隨意放置。另外,由于該類在靜態(tài)代碼塊中加載了配置文件jdbc.properties
,需要在resource
下面新建一個 jdbc.properties
文件,并寫入一下內(nèi)容:
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///imooc_goods_cms?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
username=root
password=123456
我將數(shù)據(jù)放到了本地系統(tǒng)中,并且啟動端口是默認 3306,大家根據(jù)自己的MySQL
實際配置自行修改。
1.4 測試代碼
為了測試我們的數(shù)據(jù)庫配置以及 JDBCUtil
類是否成功引入,現(xiàn)在到 test 目錄下,新建一個 JDBCTest
類:
package com.colorful;
import com.colorful.util.JDBCUtil;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;
public class JDBCTest {
@Test
public void testJDBC() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 獲得鏈接
connection = JDBCUtil.getConnection();
// 編寫 SQL 語句
String sql = "SELECT * FROM `imooc_user` where `id` = ?";
// 預(yù)編譯 SQL
preparedStatement = connection.prepareStatement(sql);
// 設(shè)置參數(shù)
preparedStatement.setInt(1, 1);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
int id = resultSet.getInt("id");
String nickname = resultSet.getString("nickname");
Timestamp createTime = resultSet.getTimestamp("create_time");
System.out.println("id=" + id);
System.out.println("nickname=" + nickname);
System.out.println("createTime=" + createTime);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
JDBCUtil.release(resultSet, preparedStatement, connection);
}
}
}
如果配置成功,運行單元測試,將得到如下運行結(jié)果:
id=1
nickname=小慕
createTime=2020-07-20 16:53:19.0
下面為運行截圖:

2. 系統(tǒng)架構(gòu)
本商品管理系統(tǒng)的包結(jié)構(gòu)如下:
src
├── main
│ ├── java # 源碼目錄
│ │ └── com
│ │ └── colorful
│ │ ├── App.java # 入口文件
│ │ ├── dao # 數(shù)據(jù)訪問對象(Data Access Object,提供數(shù)據(jù)庫操作的一些方法)
│ │ ├── model # 實體類(類字段和數(shù)據(jù)表字段一一對應(yīng))
│ │ ├── service # 服務(wù)層(提供業(yè)務(wù)邏輯層服務(wù))
│ │ └── util # 一些幫助類
│ └── resources
│ ├── imooc_goods_cms.sql # 建表的 SQL 文件
│ └── jdbc.properties # jdbc 配置文件
└── test # 單元測試目錄
└── java
└── com
└── colorful
├── AppTest.java
└── JDBCTest.java
大家可以提前熟悉一下本項目的項目結(jié)構(gòu),下面我們會一一講解。
3. 實體類
實體類的作用是存儲數(shù)據(jù)并提供對這些數(shù)據(jù)的訪問。在我們這個項目中,實體類統(tǒng)一被放到了model
包下,通常情況下,實體類中的屬性與我們的數(shù)據(jù)表字段一一對應(yīng)。當我們編寫這些實體類的時候,建議對照著數(shù)據(jù)表的字段以防疏漏。
3.1 BaseModel
在我們數(shù)據(jù)表中,有幾個公共的字段,可以提取出一個實體類的父類 BaseModel
,并提供 getter
和 setter
,源碼如下:
package com.colorful.model;
import java.sql.Timestamp;
public class BaseModel {
private Integer id;
private Timestamp createTime;
private Timestamp updateTime;
private Timestamp deleteTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
public Timestamp getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Timestamp updateTime) {
this.updateTime = updateTime;
}
public Timestamp getDeleteTime() {
return deleteTime;
}
public void setDeleteTime(Timestamp deleteTime) {
this.deleteTime = deleteTime;
}
}
值得注意的是,Timestamp
是java.sql
下的類。
3.2 實體類編寫
接下來,再在model
包下新建 3 個類:User
、Goods
和 Category
,并提供getter
和 setter
。如下是每個類的代碼:
package com.colorful.model;
public class User extends BaseModel {
private String userName;
private String nickName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.colorful.model;
public class Goods extends BaseModel {
private String name;
private String description;
private Integer categoryId;
private Double price;
private Integer stock;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
}
package com.colorful.model;
public class Category extends BaseModel {
private String name;
private String description;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
4. 實現(xiàn)用戶鑒權(quán)
4.1 登錄方式
想要使用系統(tǒng)進行商品管理,第一步要做的就是登錄。
我們的系統(tǒng)使用用戶名和密碼進行登錄校驗,上一小節(jié)我們已經(jīng)建立了imooc_user
表,并向表中插入了一個用戶 admin
,其密碼為 123456
。顯然,通過如下SQL
就可以查詢到該用戶:
SELET * FROM `imooc_user` WHERE `username` = 'admin' AND password = '123456';
如果查詢到這個用戶,就表示用戶名密碼通過校驗,用戶可執(zhí)行后續(xù)操作,如果沒有查到,就要提示用戶重新輸入賬號和密碼。
4.2 數(shù)據(jù)訪問對象
我們先不管用戶是如何輸入賬號密碼的,接下來要編寫的業(yè)務(wù)代碼就是根據(jù)用戶名和密碼去查詢用戶。那么涉及到數(shù)據(jù)庫查詢的代碼應(yīng)該放到哪里呢?參考上面的系統(tǒng)架構(gòu)圖,DAO
是數(shù)據(jù)訪問對象,我們可以在dao
包下面新建一個UserDAO
,并寫入如下代碼:
package com.colorful.dao;
import com.colorful.model.User;
import com.colorful.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class UserDAO {
public User selectByUserNameAndPassword(String username, String password) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
User user = new User();
try {
// 獲得鏈接
connection = JDBCUtil.getConnection();
// 編寫 SQL 語句
String sql = "SELECT * FROM `imooc_user` where `username` = ? AND `password` = ? AND `delete_time` is null ";
// 預(yù)編譯 SQL
preparedStatement = connection.prepareStatement(sql);
// 設(shè)置參數(shù)
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
user.setId(resultSet.getInt("id"));
String nickname = resultSet.getString("nickname");
if (nickname.equals("")) {
nickname = "匿名";
}
user.setNickName(nickname);
user.setUserName(resultSet.getString("username"));
} else {
user = null;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
JDBCUtil.release(resultSet, preparedStatement, connection);
}
return user;
}
}
UserDAO
類下面有一個 selectByUserNameAndPassword()
方法, 接收兩個參數(shù) username
和 password
,返回值類型是實體類 User
,如果沒有查詢到,返回的是一個 null
。
完成了 UserDAO
的編寫,我們需要到服務(wù)層 service
包下,新建一個 UserService
,并寫入如下代碼:
package com.colorful.service;
import com.colorful.dao.UserDAO;
import com.colorful.model.User;
public class UserService {
private final UserDAO userDAO = new UserDAO();
// 登陸
public User login(String username, String password) {
return userDAO.selectByUserNameAndPassword(username, password);
}
}
到這里大家可能有些疑問,這個類下面的login()
方法,直接調(diào)用了我們剛剛編寫的 DAO
下面的 selectByUserNameAndPassword()
方法,為什么還要嵌套這么一層么?這不是多此一舉么?
要討論 service
層的封裝是不是過度設(shè)計,就要充分理解設(shè)計服務(wù)層的概念和意義,服務(wù)層主要是對業(yè)務(wù)邏輯的封裝,對于更為復(fù)雜的項目,用戶登錄會有更多的方式,因此在服務(wù)層,會封裝更多的業(yè)務(wù)邏輯。如果沒有服務(wù)層,這些復(fù)雜的邏輯不得不都寫在數(shù)據(jù)訪問層,顯然這是不合理的。我們現(xiàn)在這個項目沒有使用任何框架,等到后面大家學(xué)習了Spring
這種框架,一定會對這樣的分層的好處有所體會。
4.3 使用 Scanner 類與用戶交互
完成了上面一系列的封裝,就剩下我們和用戶的交互了,本項目中,我們使用 Scanner
類來接收用戶的輸入,并使用print()
方法向屏幕輸出。
打開 App.java
入口文件,創(chuàng)建UserService
實例,編寫一個主流程方法 run()
,并在入口方法 main()
中調(diào)用該方法:
package com.colorful;
import com.colorful.model.User;
import com.colorful.service.UserService;
import java.util.Scanner;
/**
* @author colorful@TaleLin
* Imooc Goods
*/
public class App {
private static final UserService userService = new UserService();
/**
* 主流程方法
*/
public static void run() {
User user = null;
System.out.println("歡迎使用商品管理系統(tǒng),請輸入用戶名和密碼:");
do {
Scanner scanner = new Scanner(System.in);
// 登錄
System.out.println("用戶名:");
String username = scanner.nextLine();
System.out.println("密碼:");
String password = scanner.nextLine();
user = userService.login(username, password);
if (user == null) {
System.out.println("用戶名密碼校驗失敗,請重新輸入!");
}
} while (user == null);
System.out.println("歡迎您!" + user.getNickName());
// TODO 登錄成功,編寫后續(xù)邏輯
}
public static void main( String[] args )
{
run();
}
}
run()
方法中有一個 do ... while
循環(huán),循環(huán)的條件是 user
對象為 null
。
我們知道,do... while
循環(huán)會首先執(zhí)行 do
中的循環(huán)體,循環(huán)體中創(chuàng)建了一個 Scanner
類的實例,獲取到用戶的輸入后,我們會調(diào)用用戶服務(wù)層的login()
方法,該方法返回實體類對象User
,如果其為 null
表示用戶名密碼校驗失敗,需要用戶重新輸入, user == null
,滿足循環(huán)的條件,會一直執(zhí)行循環(huán)體中的代碼。直到循環(huán)體中的 user
不為 null
(也就是用戶登錄校驗成功后)才終止循環(huán)。
下面運行App.java
的main()
方法,如下為登錄失敗的截圖:

如果用戶名密碼檢驗錯誤,就要反復(fù)輸入用戶名密碼重新登錄。
如下為登錄成功的截圖:

5. 小結(jié)
在本小節(jié),我們成功搭建了項目工程,通過實現(xiàn)一個用戶鑒權(quán)模塊,介紹了整體的系統(tǒng)架構(gòu)。我們在編寫實體類的同時,復(fù)習了面向?qū)ο蟮睦^承性;在數(shù)據(jù)訪問層,也復(fù)習了 JDBC API
的使用;在編寫程序入口文件的同時,也復(fù)習了 Scanner
類的使用和循環(huán)的使用。
關(guān)于系統(tǒng)鑒權(quán),這里還有一個待優(yōu)化的地方,大家下去之后可以思考一下,在下一小節(jié)的開頭,我將帶領(lǐng)大家一起來優(yōu)化。下一小節(jié)也將主要講解最后剩余的商品模塊和分類模塊的實現(xiàn),也會復(fù)習到很多其他方面的基礎(chǔ)知識。