亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

實戰 - 業務實現 1

上一小節我們完成了數據庫的設計和創建,也向數據表中插入了一些初始數據,本小節我們將開始具體業務代碼的實現,如果大家還沒有完成上一小節的任務,請務必先完成再來學習本節內容。

1. 準備工作

在開始正式編碼之前,我們要做一些準備工作,主要是環境的搭建和工具類的引入。

1.1 創建 Maven 工程

打開 idea,點擊Create new Project按鈕:

在左側欄選擇Maven,Project SDK選擇14,勾選Create from archetype復選框,再選擇maven-archetype-quickstart,表示創建一個簡單 Java 應用,點擊next按鈕:

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

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

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

稍等片刻,待看到左側項目的目錄結構已經生成好了,及表示已完成項目的初始化工作:

1.2 引入 MySQL 驅動

接下來引入mysql-connector-java驅動,由于我本地安裝的MySQL版本為8.0.21,因此mysql-connector-java的版本號也選擇8.0.21,大家根據自己實際情況選擇對應版本。

打開pom.xml文件,在<dependencies></dependencies>節點內插入如下xml

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

由于我們已經配置了允許自動導入依賴,稍等片刻,mysql-connector-java 8.0.21就會被成功導入??稍?code>idea右側點擊Maven按鈕查看項目的依賴關系:

1.3 引入 JDBC 工具類

JDBC 相關操作是本項目的最常用的操作,我封裝了一個 JDBC 的工具類,主要通過 Java 的 JDBC API 去訪問數據庫,提供了加載配置、注冊驅動、獲得資源以及釋放資源等接口。

大家可以到我的 Github 倉庫下載這個 JDBCUtil類;也可以直接復制下面的代碼:

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");
    }

    /**
     * 注冊驅動
     */
    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包下,大家可根據自身情況隨意放置。另外,由于該類在靜態代碼塊中加載了配置文件jdbc.properties,需要在resource下面新建一個 jdbc.properties文件,并寫入一下內容:

driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///imooc_goods_cms?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
username=root
password=123456

我將數據放到了本地系統中,并且啟動端口是默認 3306,大家根據自己的MySQL實際配置自行修改。

1.4 測試代碼

為了測試我們的數據庫配置以及 JDBCUtil 類是否成功引入,現在到 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` = ?";
            // 預編譯 SQL
            preparedStatement = connection.prepareStatement(sql);
            // 設置參數
            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);
        }
    }

}

如果配置成功,運行單元測試,將得到如下運行結果:

id=1
nickname=小慕
createTime=2020-07-20 16:53:19.0

下面為運行截圖:

2. 系統架構

本商品管理系統的包結構如下:

src
├── main       
│   ├── java    # 源碼目錄
│   │   └── com
│   │       └── colorful
│   │           ├── App.java    # 入口文件
│   │           ├── dao         # 數據訪問對象(Data Access Object,提供數據庫操作的一些方法)
│   │           ├── model       # 實體類(類字段和數據表字段一一對應)
│   │           ├── service     # 服務層(提供業務邏輯層服務)
│   │           └── util        # 一些幫助類
│   └── resources
│       ├── imooc_goods_cms.sql # 建表的 SQL 文件
│       └── jdbc.properties     # jdbc 配置文件
└── test       # 單元測試目錄
    └── java
        └── com
            └── colorful
                ├── AppTest.java
                └── JDBCTest.java

大家可以提前熟悉一下本項目的項目結構,下面我們會一一講解。

3. 實體類

實體類的作用是存儲數據并提供對這些數據的訪問。在我們這個項目中,實體類統一被放到了model包下,通常情況下,實體類中的屬性與我們的數據表字段一一對應。當我們編寫這些實體類的時候,建議對照著數據表的字段以防疏漏。

3.1 BaseModel

在我們數據表中,有幾個公共的字段,可以提取出一個實體類的父類 BaseModel ,并提供 gettersetter,源碼如下:

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;
    }

}

值得注意的是,Timestampjava.sql下的類。

3.2 實體類編寫

接下來,再在model包下新建 3 個類:User、GoodsCategory,并提供gettersetter 。如下是每個類的代碼:

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. 實現用戶鑒權

4.1 登錄方式

想要使用系統進行商品管理,第一步要做的就是登錄。

我們的系統使用用戶名和密碼進行登錄校驗,上一小節我們已經建立了imooc_user表,并向表中插入了一個用戶 admin,其密碼為 123456 。顯然,通過如下SQL就可以查詢到該用戶:

SELET * FROM `imooc_user` WHERE `username` = 'admin' AND password = '123456';

如果查詢到這個用戶,就表示用戶名密碼通過校驗,用戶可執行后續操作,如果沒有查到,就要提示用戶重新輸入賬號和密碼。

4.2 數據訪問對象

我們先不管用戶是如何輸入賬號密碼的,接下來要編寫的業務代碼就是根據用戶名和密碼去查詢用戶。那么涉及到數據庫查詢的代碼應該放到哪里呢?參考上面的系統架構圖,DAO是數據訪問對象,我們可以在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 ";
            // 預編譯 SQL
            preparedStatement = connection.prepareStatement(sql);
            // 設置參數
            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()方法, 接收兩個參數 usernamepassword,返回值類型是實體類 User,如果沒有查詢到,返回的是一個 null

完成了 UserDAO 的編寫,我們需要到服務層 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()方法,直接調用了我們剛剛編寫的 DAO 下面的 selectByUserNameAndPassword() 方法,為什么還要嵌套這么一層么?這不是多此一舉么?

要討論 service 層的封裝是不是過度設計,就要充分理解設計服務層的概念和意義,服務層主要是對業務邏輯的封裝,對于更為復雜的項目,用戶登錄會有更多的方式,因此在服務層,會封裝更多的業務邏輯。如果沒有服務層,這些復雜的邏輯不得不都寫在數據訪問層,顯然這是不合理的。我們現在這個項目沒有使用任何框架,等到后面大家學習了Spring這種框架,一定會對這樣的分層的好處有所體會。

4.3 使用 Scanner 類與用戶交互

完成了上面一系列的封裝,就剩下我們和用戶的交互了,本項目中,我們使用 Scanner 類來接收用戶的輸入,并使用print()方法向屏幕輸出。

打開 App.java 入口文件,創建UserService實例,編寫一個主流程方法 run(),并在入口方法 main()中調用該方法:

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("歡迎使用商品管理系統,請輸入用戶名和密碼:");
        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 登錄成功,編寫后續邏輯
    }

    public static void main( String[] args )
    {
        run();
    }
}

run()方法中有一個 do ... while循環,循環的條件是 user 對象為 null。

我們知道,do... while循環會首先執行 do 中的循環體,循環體中創建了一個 Scanner 類的實例,獲取到用戶的輸入后,我們會調用用戶服務層的login()方法,該方法返回實體類對象User,如果其為 null表示用戶名密碼校驗失敗,需要用戶重新輸入, user == null,滿足循環的條件,會一直執行循環體中的代碼。直到循環體中的 user不為 null (也就是用戶登錄校驗成功后)才終止循環。

下面運行App.javamain()方法,如下為登錄失敗的截圖:

如果用戶名密碼檢驗錯誤,就要反復輸入用戶名密碼重新登錄。

如下為登錄成功的截圖:

5. 小結

在本小節,我們成功搭建了項目工程,通過實現一個用戶鑒權模塊,介紹了整體的系統架構。我們在編寫實體類的同時,復習了面向對象的繼承性;在數據訪問層,也復習了 JDBC API 的使用;在編寫程序入口文件的同時,也復習了 Scanner 類的使用和循環的使用。

關于系統鑒權,這里還有一個待優化的地方,大家下去之后可以思考一下,在下一小節的開頭,我將帶領大家一起來優化。下一小節也將主要講解最后剩余的商品模塊和分類模塊的實現,也會復習到很多其他方面的基礎知識。