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

1. 准备工作

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

1.1 创建 Maven 工程

打开 idea,点击Create new Project按钮
3.jpg
在左侧栏选择Maven,Project SDK选择14,勾选Create from archetype复选框,再选择maven-archetype-quickstart,表示创建一个简单 Java 应用,点击next按钮
4.jpg
输入项目名称goods,将项目路径设置为本地桌面,GroupId可根据实际情况自定义,此处我设置为com.ouchaochao,其余输入框无需修改,采用默认即可,设置完成后,点击next按钮
1.png
这一步来到Maven配置,idea自带了Maven,我们使用默认的即可,直接点击Finish按钮完成项目创建
2.png
此时,Maven会进行一些初始化配置,右下角对话框选择Enable Auto-import按钮,表示允许自动导入依赖
6.png
稍等片刻,待看到左侧项目的目录结构已经生成好了,及表示已完成项目的初始化工作
1.png

1.2 引入 MySQL 驱动

接下来引入mysql-connector-java驱动,由于我本地安装的MySQL版本为8.0.21,因此mysql-connector-java的版本号也选择8.0.21,大家根据自己实际情况选择对应版本。
打开pom.xml文件,在节点内插入如下xml:

  1. <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  2. <dependency>
  3. <groupId>mysql</groupId>
  4. <artifactId>mysql-connector-java</artifactId>
  5. <version>8.0.21</version>
  6. </dependency>

3.jpg
由于我们已经配置了允许自动导入依赖,稍等片刻,mysql-connector-java 8.0.21就会被成功导入。可在idea右侧点击Maven按钮查看项目的依赖关系:
3.jpg

1.3 引入 JDBC 工具类

JDBC 相关操作是本项目的最常用的操作,我封装了一个 JDBC 的工具类,主要通过 Java 的 JDBC API 去访问数据库,提供了加载配置、注册驱动、获得资源以及释放资源等接口。
大家可以到我的Github仓库下载这个 JDBCUtil类;也可以直接复制下面的代码:

  1. package com.ouchaochao.util;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.sql.*;
  5. import java.util.Properties;
  6. /**
  7. * @author colorful@TaleLin
  8. */
  9. public class JDBCUtil {
  10. private static final String driverClass;
  11. private static final String url;
  12. private static final String username;
  13. private static final String password;
  14. static {
  15. // 加载属性文件并解析
  16. Properties props = new Properties();
  17. // 使用类的加载器的方式进行获取配置
  18. InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
  19. try {
  20. assert inputStream != null;
  21. props.load(inputStream);
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. driverClass = props.getProperty("driverClass");
  26. url = props.getProperty("url");
  27. username = props.getProperty("username");
  28. password = props.getProperty("password");
  29. }
  30. /**
  31. * 注册驱动
  32. */
  33. public static void loadDriver() throws ClassNotFoundException{
  34. Class.forName(driverClass);
  35. }
  36. /**
  37. * 获得连接
  38. */
  39. public static Connection getConnection() throws Exception{
  40. loadDriver();
  41. return DriverManager.getConnection(url, username, password);
  42. }
  43. /**
  44. * 资源释放
  45. */
  46. public static void release(PreparedStatement statement, Connection connection){
  47. if(statement != null){
  48. try {
  49. statement.close();
  50. } catch (SQLException e) {
  51. e.printStackTrace();
  52. }
  53. statement = null;
  54. }
  55. if(connection != null){
  56. try {
  57. connection.close();
  58. } catch (SQLException e) {
  59. e.printStackTrace();
  60. }
  61. connection = null;
  62. }
  63. }
  64. /**
  65. * 释放资源 重载方法
  66. */
  67. public static void release(ResultSet rs, PreparedStatement stmt, Connection conn){
  68. if(rs!= null){
  69. try {
  70. rs.close();
  71. } catch (SQLException e) {
  72. e.printStackTrace();
  73. }
  74. rs = null;
  75. }
  76. if(stmt != null){
  77. try {
  78. stmt.close();
  79. } catch (SQLException e) {
  80. e.printStackTrace();
  81. }
  82. stmt = null;
  83. }
  84. if(conn != null){
  85. try {
  86. conn.close();
  87. } catch (SQLException e) {
  88. e.printStackTrace();
  89. }
  90. conn = null;
  91. }
  92. }
  93. }

我本地将这个类放在了 com.ouchaochao.util包下,大家可根据自身情况随意放置。另外,由于该类在静态代码块中加载了配置文件jdbc.properties,需要在resource下面新建一个 jdbc.properties文件,并写入一下内容:

  1. driverClass=com.mysql.cj.jdbc.Driver
  2. url=jdbc:mysql:///im_goods_cms?serverTimezone=Asia/Shanghai&characterEncoding=UTF8
  3. username=root
  4. password=123456

我将数据放到了本地系统中,并且启动端口是默认 3306,大家根据自己的MySQL实际配置自行修改。

1.4 测试代码

为了测试我们的数据库配置以及 JDBCUtil 类是否成功引入,现在到 test 目录下,新建一个 JDBCTest 类:

  1. package com.ouchaochao;
  2. import com.ouchaochao.util.JDBCUtil;
  3. import org.junit.Test;
  4. import java.sql.Connection;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7. import java.sql.Timestamp;
  8. public class JDBCTest {
  9. @Test
  10. public void testJDBC() {
  11. Connection connection = null;
  12. PreparedStatement preparedStatement = null;
  13. ResultSet resultSet = null;
  14. try {
  15. // 获得链接
  16. connection = JDBCUtil.getConnection();
  17. // 编写 SQL 语句
  18. String sql = "SELECT * FROM `im_user` where `id` = ?";
  19. // 预编译 SQL
  20. preparedStatement = connection.prepareStatement(sql);
  21. // 设置参数
  22. preparedStatement.setInt(1, 1);
  23. resultSet = preparedStatement.executeQuery();
  24. if (resultSet.next()) {
  25. int id = resultSet.getInt("id");
  26. String nickname = resultSet.getString("nickname");
  27. Timestamp createTime = resultSet.getTimestamp("create_time");
  28. System.out.println("id=" + id);
  29. System.out.println("nickname=" + nickname);
  30. System.out.println("createTime=" + createTime);
  31. }
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. } finally {
  35. // 释放资源
  36. JDBCUtil.release(resultSet, preparedStatement, connection);
  37. }
  38. }
  39. }

如果配置成功,运行单元测试,将得到如下运行结果:

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

下面为运行截图:
1.png

2. 系统架构

本商品管理系统的包结构如下:

  1. src
  2. ├── main
  3. ├── java # 源码目录
  4. └── com
  5. └── ouchaochao
  6. ├── App.java # 入口文件
  7. ├── dao # 数据访问对象(Data Access Object,提供数据库操作的一些方法)
  8. ├── model # 实体类(类字段和数据表字段一一对应)
  9. ├── service # 服务层(提供业务逻辑层服务)
  10. └── util # 一些帮助类
  11. └── resources
  12. ├── im_goods_cms.sql # 建表的 SQL 文件
  13. └── jdbc.properties # jdbc 配置文件
  14. └── test # 单元测试目录
  15. └── java
  16. └── com
  17. └── ouchaochao
  18. ├── AppTest.java
  19. └── JDBCTest.java

大家可以提前熟悉一下本项目的项目结构,下面我们会一一讲解。

3. 实体类

实体类的作用是存储数据并提供对这些数据的访问。在我们这个项目中,实体类统一被放到了model包下,通常情况下,实体类中的属性与我们的数据表字段一一对应。当我们编写这些实体类的时候,建议对照着数据表的字段以防疏漏。

3.1 BaseModel

在我们数据表中,有几个公共的字段,可以提取出一个实体类的父类 BaseModel ,并提供 getter 和 setter,源码如下:

  1. package com.ouchaochao.model;
  2. import java.sql.Timestamp;
  3. public class BaseModel {
  4. private Integer id;
  5. private Timestamp createTime;
  6. private Timestamp updateTime;
  7. private Timestamp deleteTime;
  8. public Integer getId() {
  9. return id;
  10. }
  11. public void setId(Integer id) {
  12. this.id = id;
  13. }
  14. public Timestamp getCreateTime() {
  15. return createTime;
  16. }
  17. public void setCreateTime(Timestamp createTime) {
  18. this.createTime = createTime;
  19. }
  20. public Timestamp getUpdateTime() {
  21. return updateTime;
  22. }
  23. public void setUpdateTime(Timestamp updateTime) {
  24. this.updateTime = updateTime;
  25. }
  26. public Timestamp getDeleteTime() {
  27. return deleteTime;
  28. }
  29. public void setDeleteTime(Timestamp deleteTime) {
  30. this.deleteTime = deleteTime;
  31. }
  32. }

值得注意的是,Timestamp是java.sql下的类。

3.2 实体类编写

接下来,再在model包下新建 3 个类:User、Goods 和 Category,并提供getter 和 setter 。如下是每个类的代码:

  1. package com.ouchaochao.model;
  2. public class User extends BaseModel {
  3. private String userName;
  4. private String nickName;
  5. private String password;
  6. public String getUserName() {
  7. return userName;
  8. }
  9. public void setUserName(String userName) {
  10. this.userName = userName;
  11. }
  12. public String getNickName() {
  13. return nickName;
  14. }
  15. public void setNickName(String nickName) {
  16. this.nickName = nickName;
  17. }
  18. public String getPassword() {
  19. return password;
  20. }
  21. public void setPassword(String password) {
  22. this.password = password;
  23. }
  24. }
  1. package com.ouchaochao.model;
  2. public class Goods extends BaseModel {
  3. private String name;
  4. private String description;
  5. private Integer categoryId;
  6. private Double price;
  7. private Integer stock;
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public String getDescription() {
  15. return description;
  16. }
  17. public void setDescription(String description) {
  18. this.description = description;
  19. }
  20. public Integer getCategoryId() {
  21. return categoryId;
  22. }
  23. public void setCategoryId(Integer categoryId) {
  24. this.categoryId = categoryId;
  25. }
  26. public Double getPrice() {
  27. return price;
  28. }
  29. public void setPrice(Double price) {
  30. this.price = price;
  31. }
  32. public Integer getStock() {
  33. return stock;
  34. }
  35. public void setStock(Integer stock) {
  36. this.stock = stock;
  37. }
  38. }
  1. package com.ouchaochao.model;
  2. public class Category extends BaseModel {
  3. private String name;
  4. private String description;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public String getDescription() {
  12. return description;
  13. }
  14. public void setDescription(String description) {
  15. this.description = description;
  16. }
  17. }

4. 实现用户鉴权

4.1 登录方式

想要使用系统进行商品管理,第一步要做的就是登录。
我们的系统使用用户名和密码进行登录校验,上一小节我们已经建立了im_user表,并向表中插入了一个用户 admin,其密码为 123456 。显然,通过如下SQL就可以查询到该用户:

  1. SELET * FROM `im_user` WHERE `username` = 'admin' AND password = '123456';

如果查询到这个用户,就表示用户名密码通过校验,用户可执行后续操作,如果没有查到,就要提示用户重新输入账号和密码。

4.2 数据访问对象

我们先不管用户是如何输入账号密码的,接下来要编写的业务代码就是根据用户名和密码去查询用户。那么涉及到数据库查询的代码应该放到哪里呢?参考上面的系统架构图,DAO是数据访问对象,我们可以在dao包下面新建一个UserDAO,并写入如下代码:

  1. package com.ouchaochao.dao;
  2. import com.ouchaochao.model.User;
  3. import com.ouchaochao.util.JDBCUtil;
  4. import java.sql.Connection;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7. public class UserDAO {
  8. public User selectByUserNameAndPassword(String username, String password) {
  9. Connection connection = null;
  10. PreparedStatement preparedStatement = null;
  11. ResultSet resultSet = null;
  12. User user = new User();
  13. try {
  14. // 获得链接
  15. connection = JDBCUtil.getConnection();
  16. // 编写 SQL 语句
  17. String sql = "SELECT * FROM `im_user` where `username` = ? AND `password` = ? AND `delete_time` is null ";
  18. // 预编译 SQL
  19. preparedStatement = connection.prepareStatement(sql);
  20. // 设置参数
  21. preparedStatement.setString(1, username);
  22. preparedStatement.setString(2, password);
  23. resultSet = preparedStatement.executeQuery();
  24. if (resultSet.next()) {
  25. user.setId(resultSet.getInt("id"));
  26. String nickname = resultSet.getString("nickname");
  27. if (nickname.equals("")) {
  28. nickname = "匿名";
  29. }
  30. user.setNickName(nickname);
  31. user.setUserName(resultSet.getString("username"));
  32. } else {
  33. user = null;
  34. }
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. } finally {
  38. // 释放资源
  39. JDBCUtil.release(resultSet, preparedStatement, connection);
  40. }
  41. return user;
  42. }
  43. }

UserDAO 类下面有一个 selectByUserNameAndPassword()方法, 接收两个参数 username 和 password,返回值类型是实体类 User,如果没有查询到,返回的是一个 null。
完成了 UserDAO 的编写,我们需要到服务层 service包下,新建一个 UserService ,并写入如下代码:

  1. package com.ouchaochao.service;
  2. import com.ouchaochao.dao.UserDAO;
  3. import com.ouchaochao.model.User;
  4. public class UserService {
  5. private final UserDAO userDAO = new UserDAO();
  6. // 登陆
  7. public User login(String username, String password) {
  8. return userDAO.selectByUserNameAndPassword(username, password);
  9. }
  10. }

到这里大家可能有些疑问,这个类下面的login()方法,直接调用了我们刚刚编写的 DAO 下面的 selectByUserNameAndPassword() 方法,为什么还要嵌套这么一层么?这不是多此一举么?
要讨论 service 层的封装是不是过度设计,就要充分理解设计服务层的概念和意义,服务层主要是对业务逻辑的封装,对于更为复杂的项目,用户登录会有更多的方式,因此在服务层,会封装更多的业务逻辑。如果没有服务层,这些复杂的逻辑不得不都写在数据访问层,显然这是不合理的。我们现在这个项目没有使用任何框架,等到后面大家学习了Spring这种框架,一定会对这样的分层的好处有所体会。

4.3 使用 Scanner 类与用户交互

完成了上面一系列的封装,就剩下我们和用户的交互了,本项目中,我们使用 Scanner 类来接收用户的输入,并使用print()方法向屏幕输出。
打开 App.java 入口文件,创建UserService实例,编写一个主流程方法 run(),并在入口方法 main()中调用该方法:

  1. package com.ouchaochao;
  2. import com.ouchaochao.model.User;
  3. import com.ouchaochao.service.UserService;
  4. import java.util.Scanner;
  5. /**
  6. * Im Goods
  7. */
  8. public class App {
  9. private static final UserService userService = new UserService();
  10. /**
  11. * 主流程方法
  12. */
  13. public static void run() {
  14. User user = null;
  15. System.out.println("欢迎使用商品管理系统,请输入用户名和密码:");
  16. do {
  17. Scanner scanner = new Scanner(System.in);
  18. // 登录
  19. System.out.println("用户名:");
  20. String username = scanner.nextLine();
  21. System.out.println("密码:");
  22. String password = scanner.nextLine();
  23. user = userService.login(username, password);
  24. if (user == null) {
  25. System.out.println("用户名密码校验失败,请重新输入!");
  26. }
  27. } while (user == null);
  28. System.out.println("欢迎您!" + user.getNickName());
  29. // TODO 登录成功,编写后续逻辑
  30. }
  31. public static void main( String[] args )
  32. {
  33. run();
  34. }
  35. }

run()方法中有一个 do … while循环,循环的条件是 user 对象为 null。
我们知道,do… while循环会首先执行 do 中的循环体,循环体中创建了一个 Scanner 类的实例,获取到用户的输入后,我们会调用用户服务层的login()方法,该方法返回实体类对象User,如果其为 null表示用户名密码校验失败,需要用户重新输入, user == null,满足循环的条件,会一直执行循环体中的代码。直到循环体中的 user不为 null (也就是用户登录校验成功后)才终止循环。
3.jpg
如果用户名密码检验错误,就要反复输入用户名密码重新登录。
如下为登录成功的截图:
4.jpg

5. 小结

在本小节,我们成功搭建了项目工程,通过实现一个用户鉴权模块,介绍了整体的系统架构。我们在编写实体类的同时,复习了面向对象的继承性;在数据访问层,也复习了 JDBC API 的使用;在编写程序入口文件的同时,也复习了 Scanner 类的使用和循环的使用。
关于系统鉴权,这里还有一个待优化的地方,大家下去之后可以思考一下,在下一小节的开头,我将带领大家一起来优化。下一小节也将主要讲解最后剩余的商品模块和分类模块的实现,也会复习到很多其他方面的基础知识。