1. Spring复习
Spring主要是创建对象和管理对象的框架。
Spring通过DI实现了IoC。
Spring能很大程度的实现解耦。
需要掌握SET方式注入属性的值。
需要理解自动装配。
需要掌握Spring表达式。
需要掌握AOP(暂时没学)。
2. Spring MVC复习
Spring MVC框架是解决了V-C交互的问题,即:服务器端如何接收客户端的请求,并如何给予响应。
需要掌握如何接收请求参数。
需要掌握如何转发数据。
需要掌握转发与重定向。
需要掌握响应JSON数据。
需要掌握统一处理异常的做法。
需要掌握拦截器的使用。
3. MyBatis复习
执行增删改的操作的方法应该返回Integer,表示受影响的行数;
执行查询方法的<select>
节点必须配置resultType
或resultMap
;
执行查询时如果列名与字段名不一致,在查询时需要自定义别名,以保证名称统一;
掌握<resultMap>
的配置。
--------------------------------------
1. 项目开发流程
关于项目的开发,首先应该确定需要处理的数据有哪些:商品,用户,收藏,订单,购物车,商品分类,收货地址……
然后,确定这些数据的开发、管理的先后顺序,因为某些数据是必须建立在其它数据基础之上的,例如必须先有用户数据,才可以有订单数据或收货地址数据,另外,不同的数据功能,开发的难度也有差异,应该尽可能的先开发简单的、熟悉的数据功能,然后再开发相对较难的数据功能,所以,以上数据的开发顺序大概可以是:用户 > 收货地址 > 商品分类 > 商品 > 收藏 > 购物车 > 订单……
每种类型的数据的处理,都应该遵循:增 > 查 > 删 > 改。
每个功能的处理,应该:持久层 > 业务层 > 控制器层 > 界面。
核心原则:一次只解决一个问题!
2. 用户-注册-持久层
关于持久层,应该先检查有没有对应的数据库/表,及对应的实体类。
关于数据库:
CREATE DATABASE tedu_store; USE tedu_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 COMMENT '性别,0-女,1-男', avatar VARCHAR(50) COMMENT '头像', phone VARCHAR(20) COMMENT '手机号码', email VARCHAR(30) COMMENT '电子邮箱', is_delete INT 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=UTF8;
然后,下载本次项目store.zip
,解压到Workspace中,通过Import > Existing Maven Projects导入项目。(可以在spring boot 官网自己生成)
由于以上数据表中关于日志的4个字段是后续每张表都应该有的,则后续的每张表对应的实体类中也应该有4个对应的属性,所以,应该创建实体类的基类来封装这4个字段对应的属性,且,当前项目中的所有实体类都应该继承自该基类:
/** * 实体类的基类 */ public abstract class BaseEntity implements Serializable { private static final long serialVersionUID = -6185124879935579311L; private String createdUser; private Date createdTime; private String modifiedUser; private Date modifiedTime; // SET/GET ... }
创建与数据表对应的实体类cn.tedu.store.entity.User
:
/** * 用户数据的实体类 */ public class User extends BaseEntity { private static final long serialVersionUID = 8777086855777796877L; private Integer uid; private String username; private String password; private String salt; private Integer gender; private String avatar; private String phone; private String email; private Integer isDelete; // SET/GET ... }
持久层的开发重点应该分为3个步骤:
1. 分析当前功能所需要执行的SQL语句
当前执行“注册”功能,必然需要执行插入数据操作:
INSERT INTO t_user ( username, password ... modified_time ) VALUES ( ?, ?, ... ? )
为了保证“用户名唯一”,还应该有“根据用户名查询数据”的操作:
SELECT uid FROM t_user WHERE username=?
2. 创建接口(如果必要的话),并设计抽象方法
创建cn.tedu.store.mapper.UserMapper
接口文件,并在其中添加抽象方法:
/** * 处理用户数据的持久层接口 */ public interface UserMapper { /** * 插入用户数据 * @param user 用户数据 * @return 受影响的行数 */ Integer addnew(User user); /** * 根据用户名查询用户信息 * @param username 用户名 * @return 匹配的用户数据,如果没有匹配的数据,则返回null */ User findByUsername(String username); }
3. 在XML中配置抽象方法的映射
在src/main/resources
下创建mappers
文件夹,然后复制此前的项目得到UserMapper.xml
,配置好该文件中根节点的namespace
对应的接口,然后,再配置以上2个抽象方法对应的映射:
<mapper namespace="cn.tedu.store.mapper.UserMapper"> <!-- 插入用户数据 --> <!-- Integer addnew(User user) --> <insert id="addnew"> 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" resultType="cn.tedu.store.entity.User"> SELECT uid FROM t_user WHERE username=#{username} </select> </mapper>
注意:此次并没有在接口文件之前添加@Mapper
注解,由于这个注解是添加在接口之前的,则项目中可能出现的多个持久层接口都需要添加该注解,管理起来比较麻烦,所以,改为在执行程序StoreApplication
之前添加@MapperScan("cn.tedu.store.mapper")
注解,以指定持久层接口所在的包,则后续每个持久层接口都不必再添加@Mapper
注解。
最后,在src/test/java
下,创建cn.tedu.store.mapper.UserMapperTestCase
测试类:
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTestCase { @Autowired private UserMapper mapper; @Test public void addnew() { User user = new User(); user.setUsername("root"); user.setPassword("1234"); Integer rows = mapper.addnew(user); System.err.println("rows=" + rows); } @Test public void findByUsername() { String username = "root"; User user = mapper.findByUsername(username); System.err.println(user); } }
3. 用户-注册-业务层
业务层的开发通常也是3个步骤来完成!
1. 规划异常
业务层中的方法的返回值,仅以操作成功为标准,判断是否需要返回某种数据。
由于返回值并不体现操作成功与否,则还需要考虑失败的情况,并抛出对应的异常,通常,建议自定义异常,针对不同的操作错误(操作失败的原因)抛出不同的异常,并且,这些异常都应该继承自RuntimeException
(原因后续再讲)。实际做法是自定义ServiceException
,是继承自RuntimeException
的,而其它自定义的异常都继承自ServiceException
:
/** * 业务异常,当前项目中自定义异常类的基类 */ 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); } }
然后,本次的“注册”功能可能抛出“用户名被占用”和“插入数据失败”错误,则需要创建对应的异常UserConflictException
和InsertException
以备抛出。
2. 在业务层接口中声明抽象方法
在业务层中声明的方法是被控制器层(Controller)调用的,且在实现业务层功能时,需要调用持久层对象中的方法,所以,在参数方面,应该是承上启下的,即:能满足调用和被调用的需求。
创建业务层接口cn.tedu.store.service.IUserService
接口,并添加抽象方法:
void reg(User user) throws UserConflictException, InsertException;
3. 实现接口中的抽象方法
创建cn.tedu.store.service.impl.UserServiceImpl
类,实现IUserService
接口,在类中声明持久层对象@Autowired private UserMapper userMapper;
,在类之前添加@Service
注解:
@Service public class UserServiceImpl implements IUserService { @Autowired private UserMapper userMapper; @Override public void reg(User user) throws UserConflictException, InsertException { // TODO Auto-generated method stub } }
通常,应该把持久层中声明的抽象方法复制到业务层的实现类中,并且通过持久层对象直接调用来实现方法的功能,这些方法应该是私有的,如果是查询类的方法,应该直接返回调用持久层方法的返回结果,如果是增删改的方法,应该将方法的返回值修改为void
,并且,在方法体中,判断调用时的返回结果,如果结果不符合预期,则抛出异常。
基于以上原则,应该在业务层的实现类中添加:
/** * 插入用户数据 * @param user 用户数据 * @return 受影响的行数 */ private void addnew(User user) { Integer rows = userMapper.addnew(user); if (rows != 1) { throw new InsertException("增加用户数据时出现未知错误!请联系系统管理员!"); } } /** * 根据用户名查询用户信息 * @param username 用户名 * @return 匹配的用户数据,如果没有匹配的数据,则返回null */ private User findByUsername(String username) { return userMapper.findByUsername(username); }
然后,重写接口中定义的抽象方法,在编写时,应该先分析过程,然后再编写代码:
@Override public void reg(User user) throws UserConflictException, InsertException { // 根据user.getUsername()获取用户名匹配的数据 // 检查数据是否为null // 是:为null,用户名未被占用,则应该补全参数中的属性值 // - 1. 密码加密,并封装 // - 2. 封装salt // - 3. 封装isDelete,固定为0 // - 4. 封装4项日志数据 // - 执行注册:addnew(user) // 否:非null,用户名被占用,则抛出UserConflictException }
分析完成,编写可以完成的部分的代码(暂不包括密码加密与salt的处理):
@Override public void reg(User user) throws UserConflictException, InsertException { // 根据user.getUsername()获取用户名匹配的数据 String username = user.getUsername(); User data = findByUsername(username); // 检查数据是否为null if (data == null) { // 是:为null,用户名未被占用,则应该补全参数中的属性值 // TODO - 1. 密码加密,并封装 // TODO - 2. 封装salt // - 3. 封装isDelete,固定为0 user.setIsDelete(0); // - 4. 封装4项日志数据 Date now = new Date(); user.setCreatedTime(now); user.setModifiedTime(now); user.setCreatedUser(username); user.setModifiedUser(username); // - 执行注册:addnew(user) addnew(user); } else { // 否:非null,用户名被占用,则抛出UserConflictException throw new UserConflictException( "注册失败!您尝试注册的用户名(" + username + ")已经被占用!"); } }
完成后,仍编写对应的单元测试:
@RunWith(SpringRunner.class) @SpringBootTest public class UserServiceTestCase { @Autowired private IUserService service; @Test public void reg() { try { User user = new User(); user.setUsername("admin"); user.setPassword("8888"); user.setPhone("13800138001"); user.setEmail("[email protected]"); user.setGender(1); user.setAvatar("http://www.tedu.cn/logo.png"); service.reg(user); System.err.println("OK."); } catch (ServiceException e) { System.err.println(e.getMessage()); } } }