实战 - 业务实现 1
上一小节我们完成了数据库的设计和创建,也向数据表中插入了一些初始数据,本小节我们将开始具体业务代码的实现,如果大家还没有完成上一小节的任务,请务必先完成再来学习本节内容。
在开始正式编码之前,我们要做一些准备工作,主要是环境的搭建和工具类的引入。
打开 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
按钮,表示允许自动导入依赖:
稍等片刻,待看到左侧项目的目录结构已经生成好了,及表示已完成项目的初始化工作:
接下来引入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
按钮查看项目的依赖关系:
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
实际配置自行修改。
为了测试我们的数据库配置以及 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
代码块预览 复制
下面为运行截图:
本商品管理系统的包结构如下:
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
代码块预览 复制
大家可以提前熟悉一下本项目的项目结构,下面我们会一一讲解。
实体类的作用是存储数据并提供对这些数据的访问。在我们这个项目中,实体类统一被放到了model
包下,通常情况下,实体类中的属性与我们的数据表字段一一对应。当我们编写这些实体类的时候,建议对照着数据表的字段以防疏漏。
在我们数据表中,有几个公共的字段,可以提取出一个实体类的父类 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
下的类。
接下来,再在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; } }
代码块预览 复制
想要使用系统进行商品管理,第一步要做的就是登录。
我们的系统使用用户名和密码进行登录校验,上一小节我们已经建立了imooc_user
表,并向表中插入了一个用户 admin
,其密码为 123456
。显然,通过如下SQL
就可以查询到该用户:
SELET * FROM `imooc_user` WHERE `username` = 'admin' AND password = '123456';
代码块预览 复制
如果查询到这个用户,就表示用户名密码通过校验,用户可执行后续操作,如果没有查到,就要提示用户重新输入账号和密码。
我们先不管用户是如何输入账号密码的,接下来要编写的业务代码就是根据用户名和密码去查询用户。那么涉及到数据库查询的代码应该放到哪里呢?参考上面的系统架构图,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()
方法, 接收两个参数 username
和 password
,返回值类型是实体类 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
这种框架,一定会对这样的分层的好处有所体会。
完成了上面一系列的封装,就剩下我们和用户的交互了,本项目中,我们使用 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.java
的main()
方法,如下为登录失败的截图:
如果用户名密码检验错误,就要反复输入用户名密码重新登录。
如下为登录成功的截图:
在本小节,我们成功搭建了项目工程,通过实现一个用户鉴权模块,介绍了整体的系统架构。我们在编写实体类的同时,复习了面向对象的继承性;在数据访问层,也复习了 JDBC API
的使用;在编写程序入口文件的同时,也复习了 Scanner
类的使用和循环的使用。
关于系统鉴权,这里还有一个待优化的地方,大家下去之后可以思考一下,在下一小节的开头,我将带领大家一起来优化。下一小节也将主要讲解最后剩余的商品模块和分类模块的实现,也会复习到很多其他方面的基础知识。