跳到主要内容

实战 - 业务实现 1

上一小节我们完成了数据库的设计和创建,也向数据表中插入了一些初始数据,本小节我们将开始具体业务代码的实现,如果大家还没有完成上一小节的任务,请务必先完成再来学习本节内容。

1. 准备工作

在开始正式编码之前,我们要做一些准备工作,主要是环境的搭建和工具类的引入。

1.1 创建 Maven 工程

打开 idea,点击Create new Project按钮:

在左侧栏选择MavenProject 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就会被成功导入。可在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 个类:UserGoodsCategory,并提供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 类的使用和循环的使用。

关于系统鉴权,这里还有一个待优化的地方,大家下去之后可以思考一下,在下一小节的开头,我将带领大家一起来优化。下一小节也将主要讲解最后剩余的商品模块和分类模块的实现,也会复习到很多其他方面的基础知识。