赞
踩
在设计一款软件时,在编写代码之前,应该先分析这个项目中需要处理哪些类型的数据!例如,本项目中需要处理的数据种类有:收藏,购物车,用户,收货地址,订单,商品,商品类别。
当确定了需要处理的数据的种类之后,就应该确定这些数据的处理先后顺序:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单。
在具体开发某个数据的管理功能之前,还应该分析该数据需要开发哪些管理功能,以用户数据为例,需要开发的有:修改密码,上传头像,修改资料,登录,注册。
分析出功能之后,也需要确定这些功能的开发顺序,一般先开发简单的,也依据增、查、删、改的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。
在开发某个数据的任何功能之前,还应该先创建这种数据对应的数据表,然后,创建对应的实体类,再开发某个功能!
在开发某个功能时,还应该遵循顺序:持久层(数据库编程) > 业务层 > 控制器层 > 前端页面。
先创建数据库:
CREATE DATABASE db_store;
USE db_store;
然后,在数据库中创建数据表:
CREATE TABLE t_user ( uid INT AUTO_INCREMENT COMMENT '用户id', username VARCHAR(20) UNIQUE NOT NULL COMMENT '用户名', password CHAR(32) NOT NULL COMMENT '密码', salt CHAR(36) COMMENT '盐值', gender INT(1) COMMENT '性别:0-女,1-男', phone VARCHAR(20) COMMENT '手机号码', email VARCHAR(50) COMMENT '电子邮箱', avatar VARCHAR(100) COMMENT '头像', is_delete INT(1) COMMENT '是否删除:0-否,1-是', created_user VARCHAR(20) COMMENT '创建人', created_time DATETIME COMMENT '创建时间', modified_user VARCHAR(20) COMMENT '最后修改人', modified_time DATETIME COMMENT '最后修改时间', PRIMARY KEY (uid) ) DEFAULT CHARSET=utf8mb4;
完成后,可以通过desc t_user;
和show create table t_user;
进行查看。
创建SpringBoot项目,所以,先打开https://start.spring.io
创建项目,创建时,使用的版本选择2.1.12
,Group为cn.demo,Artifact为
store,Packaging为
war,添加
Mybatis Framework和
MySQL Driver` 依赖,在网站生成项目后,将解压得到的项目文件夹剪切到Workspace中,并在Eclipse中导入该项目。
在src/main/java下,在现有的cn.demo.store
包中,创建子级entity
包,用于存放实体类,先在entity
包中创建所有实体类的基类:
/**
* 实体类的基类
*/
abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = -3122958702938259476L;
private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date modifiedTime;
// 自行添加SET/GET方法,toString()
}
并在entity
包中创建User
类,继承自以上基类:
/** * 用户数据的实体类 */ public class User extends BaseEntity { private static final long serialVersionUID = -3302907460554699349L; private Integer uid; private String username; private String password; private String salt; private Integer gender; private String phone; private String email; private String avatar; private Integer isDelete; // 自行添加SET/GET方法,基于uid的equals()和hashCode()方法,toString()方法 }
持久层:持久化保存数据的层。
刚创建好的SpringBoot项目,由于添加了数据库相关的依赖,在没有配置数据库连接信息之前,将无法启动!所以,应该先在application.properties中添加配置:
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/db_store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
mybatis.mapper-locations=classpath:mappers/*.xml
然后,在cn.demo.store
包中,创建mapper
子级包,用于存放使用MyBatis编程时的接口文件,并在mapper
包中创建UserMapper
接口,在接口中添加抽象方法:
/** * 处理用户数据的持久层接口 */ public interface UserMapper { /** * 插入用户数据 * @param user 用户数据 * @return 受影响的行数 */ Integer insert(User user); /** * 根据用户名查询用户数据 * @param username 用户名 * @return 匹配的用户数据,如果没有匹配的数据,则返回null */ User findByUsername(String username); }
然后,需要在启动类的声明之前补充@MapperScan
注解,以配置接口文件的位置:
@SpringBootApplication
@MapperScan("cn.demo.store.mapper")
public class StoreApplication {
public static void main(String[] args) {
SpringApplication.run(StoreApplication.class, args);
}
}
在src/main/resources下创建mappers文件夹,该文件夹的名称应该与复制的配置信息中保持一致!并在该文件夹中创建UserMapper.xml文件,以配置2个抽象方法的SQL映射:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="cn.demo.store.mapper.UserMapper"> <resultMap type="cn.demo.store.entity.User" id="UserEntityMap"> <id column="uid" property="uid"/> <result column="is_delete" property="isDelete"/> <result column="created_user" property="createdUser"/> <result column="created_time" property="createdTime"/> <result column="modified_user" property="modifiedUser"/> <result column="modified_time" property="modifiedTime"/> </resultMap> <!-- 插入用户数据 --> <!-- Integer insert(User user); --> <insert id="insert" useGeneratedKeys="true" keyProperty="uid"> INSERT INTO t_user ( username, password, salt, gender, phone, email, avatar, is_delete, created_user, created_time, modified_user, modified_time ) VALUES ( #{username}, #{password}, #{salt}, #{gender}, #{phone}, #{email}, #{avatar}, #{isDelete}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime} ) </insert> <!-- 根据用户名查询用户数据 --> <!-- User findByUsername(String username) --> <select id="findByUsername" resultMap="UserEntityMap"> SELECT * FROM t_user WHERE username=#{username} </select> </mapper>
在src/test/java中的cn.demo.store
包中创建子级的mapper
包,并在mapper
包中创建UserMapperTests
测试类,并在测试类的声明之前添加@RunWith(SpringRunner.class)
和@SpringBootTest
注解:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {
}
如果使用的是SpringBoot 2.2.x系列的版本,只需要添加1个注解即可,具体使用什么样的注解,请参考默认就存在那个单元测试类。
然后,在单元测试类中编写并执行单元测试:
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTests { @Autowired private UserMapper mapper; @Test public void insert() { User user = new User(); user.setUsername("project"); user.setPassword("1234"); user.setSalt("salt"); user.setGender(0); user.setPhone("13800138002"); user.setEmail("project@163.com"); user.setAvatar("avatar"); user.setIsDelete(0); user.setCreatedUser("系统管理员"); user.setCreatedTime(new Date()); user.setModifiedUser("超级管理员"); user.setModifiedTime(new Date()); Integer rows = mapper.insert(user); System.err.println("rows=" + rows); System.err.println(user); } @Test public void findByUsername() { String username = "project"; User result = mapper.findByUsername(username); System.err.println(result); } }
业务,在普通用户眼里就是“1个功能”,例如“注册”就是一个业务,在开发人员看来,它可能是由多个数据操作所组成的,例如“注册”就至少由“查询用户名对应的用户数据”和“插入用户数据”这2个数据操作组成,多个数据操作组成1个业务,在组织过程中,可能涉及一些相关的检查,及数据安全、数据完整性的保障,所以,业务层的代码主要是组织业务流程,设计业务逻辑,以保障数据的完整性和安全性。
在开发领域中,数据安全指的是:数据是由开发人员所设定的规则而产生或发生变化的!
在业务层的开发中,应该先创建业务层的接口,因为,在实际项目开发中,强烈推荐“使用接口编程”的效果!
所以,先在cn.demo.store
包中创建service
子包,并在service
包中创建UserService
业务接口,并在接口中声明“注册”这个业务的抽象方法:
/**
* 处理用户数据的业务接口
*/
public interface UserService {
/**
* 用户注册
* @param user 客户端提交的用户数据
*/
void reg(User user);
}
在设计抽象方法时,仅以操作成功(例如注册成功、登录成功等)为前提来设计抽象方法的返回值,涉及的操作失败将通过抛出异常来表示!
**创建异常处理:**在cn.demo.store
下创建ex
子包,并创建异常的父类(ServiceException)
由于需要使用异常来表示错误,所以,在实现抽象方法的功能之前,还应该先定义相关的异常,有哪些“错误”(导致操作失败的原因),就创建哪些异常类,例如,注册时,用户名可能已经被占用,则需要创建对应的异常,当用户名没有被占用,允许注册时,执行的INSERT操作也可能失败,导致相应的异常,为了便于统一管理这些异常,还应该创建自定义异常的基类,这个基类异常应该继承自RuntimeException
:
/** * 业务异常的基类 */ public class ServiceException extends RuntimeException { private static final long serialVersionUID = 980104530291206274L; public ServiceException() { super(); } public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public ServiceException(String message, Throwable cause) { super(message, cause); } public ServiceException(String message) { super(message); } public ServiceException(Throwable cause) { super(cause); } } ----------------------------------------------------------------------------- /** * 用户名冲突的异常 */ public class UsernameDuplicateException extends ServiceException { private static final long serialVersionUID = -1224474172375139228L; public UsernameDuplicateException() { super(); } public UsernameDuplicateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public UsernameDuplicateException(String message, Throwable cause) { super(message, cause); } public UsernameDuplicateException(String message) { super(message); } public UsernameDuplicateException(Throwable cause) { super(cause); } } ------------------------------------------------------------------------------- /** * 插入数据异常 */ public class InsertException extends ServiceException { private static final long serialVersionUID = 7991875652328476596L; public InsertException() { super(); } public InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public InsertException(String message, Throwable cause) { super(message, cause); } public InsertException(String message) { super(message); } public InsertException(Throwable cause) { super(cause); } }
接下来,就需要编写接口的实现类,并实现接口中的抽象方法!所以,在cn.demo.store.service
包创建子级的impl
包,并在impl
包中创建UserServiceImpl
类,实现UserService
接口,在类的声明之前添加@Service
注解,使得Spring框架能够创建并管理这个类的对象!并且,由于在实现过程中,必然用到持久层开发的数据操作,所以,还应该声明UserMapper
对象,该对象的值应该是自动装配的:
/**
* 处理用户数据的业务层实现类
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void reg(User user) {
}
}
接下来,分析实现过程:
public void reg(User user) { // 通过参数user获取尝试注册的用户名 String username = user.getUsername(); // 调用userMapper.findByUsername()方法执行查询 User result = userMapper.findByUsername(username); // 判断查询结果是否不为null if (result != null) { // 是:查询到了数据,表示用户名已经被占用,则抛出UsernameDuplicationException throw new UsernameDuplicateException(); } // 如果代码能执行到这一行,则表示没有查到数据,表示用户名未被注册,则允许注册 // 创建当前时间对象: Date now = new Date(); // 向参数user中补全数据:salt, password,涉及加密处理,暂不处理 // 向参数user中补全数据:is_delete(0) user.setIsDelete(0); // 向参数user中补全数据:4项日志(now, user.getUsername()) user.setCreaser(username); user.setCreatedTime(now); user.setModifiedUser(username); user.setModifiedTime(now); // 调用userMapper.insert()执行插入数据,并获取返回的受影响行数 Integer rows = userMapper.insert(user); // 判断受影响的行数是否不为1 if (rows != 1) { // 是:插入数据失败,则抛出InsertException throw new InsertException(); } }
然后,应该在src/test/java下的cn.demo.store
包中创建子级的service
包,并在这个包中创建UserServiceTests
测试类,专门用于测试UserService
接口中定义的功能:
@RunWith(SpringRunner.class) @SpringBootTest public class UserServiceTests { @Autowired private UserService service; @Test public void reg() { try { User user = new User(); user.setUsername("service"); user.setPassword("1234"); user.setGender(0); user.setPhone("13800138003"); user.setEmail("service@163.com"); user.setAvatar("avatar"); service.reg(user); System.err.println("OK."); } catch (ServiceException e) { System.err.println(e.getClass().getName()); } } }
最后,还应该处理密码加密(添加commons-codec依赖),完整业务代码例如:
/** * 处理用户数据的业务层实现类 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void reg(User user) { // 日志 System.err.println("UserServiceImpl.reg()"); // 通过参数user获取尝试注册的用户名 String username = user.getUsername(); // 调用userMapper.findByUsername()方法执行查询 User result = userMapper.findByUsername(username); // 判断查询结果是否不为null if (result != null) { // 是:查询到了数据,表示用户名已经被占用,则抛出UsernameDuplicationException throw new UsernameDuplicateException(); } // 如果代码能执行到这一行,则表示没有查到数据,表示用户名未被注册,则允许注册 // 创建当前时间对象: Date now = new Date(); // 向参数user中补全数据:salt, password String salt = UUID.randomUUID().toString(); user.setSalt(salt); String md5Password = getMd5Password(user.getPassword(), salt); user.setPassword(md5Password); // 向参数user中补全数据:is_delete(0) user.setIsDelete(0); // 向参数user中补全数据:4项日志(now, user.getUsername()) user.setCr
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。