目录
1.在开发某个项目之前,应先分析这个项目中可能涉及哪些种类的数据。本项目中涉及的数据:用户、商品、商品类别、收藏、订单、购物车、收货地址。
2.关于数据,还应该要确定这些数据的开发顺序。设计开发顺序的原则是:先开发基础、简单或熟悉的数据。以上需要处理的数据的开发流程是:用户-收货地址-商品类别-商品-收藏-购物车-订单。
3.在开发每种数据的相关功能时,先分析该数据涉及哪些功能。在本项目中以用户数据为例,需要开发的功能有:登录、注册、修改密码、修改资料、上传头像。
4.然后,在确定这些功能的开发顺序。原则上,应先做基础功能,并遵循增查删改的顺序来开发。则用户相关功能的开发顺序应该是:注册-登录-修改密码-修改个人资料-上传头像。
5.在实际开发中,应先创建该项目的数据库,当每次处理一种新的数据时,应先创建该数据在数据库中的数据表,然后在项目中创建该数据表对应的实体类。
6.在开发某个具体的功能时,应遵循开发顺序:持久层-业务层-控制器-前端页面。
1.给项目添加Web->Spring Web、SQL->MyBatis Framework、SQL->MySQL Driver的依赖。点击【Next】按钮完成项目创建。




2.Maven配置
其中在maven的配置上,点击File->Setting->搜索maven->如下配置。
但在User settings file 配置不大对,但能项目能正常启动(不知道为什么,后面需要再说),因为我在G:\Maven\apache-maven-3.6.3-bin\目录下没看到/conf目录,可能IDEA自动给我默认选了C盘下的目录了吧

3.首次创建完Spring Initializr项目时,解析项目依赖需消耗一定时间(Resolving dependencies of store...)

首先先保证Springboot项目能够启动起来,找到程序的入口,即被@SpringBootApplication注解修饰的入口启动类,运行入口启动类,如果观察到图形化的界面,即项目成功启动。
- ```java
- package com.cy.store;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class StoreApplication {
- public static void main(String[] args) {
- SpringApplication.run(StoreApplication.class, args);
- }
- }

检查数据库是否成功连接,在/test包中写个测试代码:
- @SpringBootTest
- class StoreApplicationTests {
- @Autowired //自动注入/自动装配
- private DataSource dataSource;
-
- @Test
- void contextLoads() {
- }
-
- @Test
- void getConnection() throws SQLException {
- System.out.println(dataSource.getConnection());
- }
-
- }
如果启动项目时提示:“配置数据源失败:'url'属性未指定,无法配置内嵌的数据源”。有如下的错误提示。

解决以上操作提示的方法:在resources文件夹下的application.properties文件中添加数据源的配置。
配置数据源,数据库信息。这一步建议在创建项目完后立马检查配置。步骤:按照上面测试代码的方式获取数据源连接观察是否能成功连接。
- spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
- spring.datasource.username=root
- spring.datasource.password=123456
1.为了方便查询JSON数据,隐藏没有值的属性,减少流量消耗,服务器不应该向客户端响应为null的属性。解决方式有两种:
方式一:在属性或类之前添加@JsonInclude(value=Include.NON_NULL)
方式二:可以在application.propertise配置文件中添加全局配置:
- # 服务器向客户端不响应为null的属性
- spring.jackson.default-property-inclusion=NON_NULL
2.SpringBoot项目的默认访问路径是“/”,如果需要修改的话可以在配置文件中指定SpringBoot访问项目路径的项目名。但是不建议修改。
server.servlet.context-path=/store
创建store数据库,创建t_user用户数据表。
1.使用use命令先选中store数据库:use store;
2.在store数据库中创建t_user表。(直接复制到Navicat中,点击查询,执行脚本)
- CREATE TABLE t_user (
- uid INT AUTO_INCREMENT COMMENT '用户id',
- username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
- password CHAR(32) NOT NULL COMMENT '密码',
- salt CHAR(36) COMMENT '盐值',
- phone VARCHAR(20) COMMENT '电话号码',
- email VARCHAR(30) COMMENT '电子邮箱',
- gender INT COMMENT '性别:0-女,1-男',
- avatar VARCHAR(50) 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)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1.项目中许多实体类都会有日志相关的四个属性,所以在创建实体类之前,应先创建这些实体类的基dsdi 类,将4个日志属性声明在基类中。在com.cy.store.entity包下创建BaseEntity,作为实体类的基类。
- package com.cy.store.entity;
- import java.io.Serializable;
- import java.util.Date;
- /** 实体类的基类 */
- public class BaseEntity implements Serializable {
- private String createdUser;
- private Date createdTime;
- private String modifiedUser;
- private Date modifiedTime;
- // Generate: Getter and Setter、toString()
- }
因为这个基类的作用就是用于被其他实体类继承的,所以应声明为抽象类。
为什么要继承sealization接口?因为要在网络中以流的形式传输,所以需要serialize序列化
2.创建com.cy.store.entity.User用户数据的实体类,继承自BaseEntity类,在中声明相关属性。
- package com.cy.store.entity;
- import java.io.Serializable;
- import java.util.Objects;
- /** 用户数据的实体类 */
- public class User extends BaseEntity implements Serializable {
- private Integer uid;
- private String username;
- private String password;
- private String salt;
- private String phone;
- private String email;
- private Integer gender;
- private String avatar;
- private Integer isDelete;
- // Generate: Getter and Setter、Generate hashCode() and equals()、toString()
- }
Tips:实体类可使用lombok插件,类上声明几个注解@Data @AllArgConsturctor等注解表示自动生成get/set方法,以及有参和无参构造函数。
(1)准备工作
1.在src/test/java下的com.store.StoreApplicationTest测试类中编写并执行获取数据库连接的单元测试,以检查数据库连接的配置是否正确。参加上面环境配置的数据源的代码。
2.执行src/test/java下的com.cy.storeApplicationTests测试类中的contextLoad观察是否执行成功。
(2)规划需要执行的SQL语句
1.用户注册的本质就是向用户表中插入数据,需要执行的SQL语句大致是:
insert into t_user(除uid之外的字段列表) values(匹配的值列表)
2.由于数据库表中的用户名字段被设计为UNIQUE,在执行插入数据之前,还应该检查该用户名是否被注册,因此需要有“根据用户名查询用户数据”的功能。需要执行的SQL语句大致是:
select * from t_user where username = ?
(3)接口与抽象方法
1.在com.cy.store包下创建一个mapper包,接着创建com.cy.store.mapper.UserMapper接口,并在接口中添加抽象方法。
- /** 处理用户数据操作的持久层接口 */
- public interface UserMapper {
- /**
- * 插入用户数据
- * @param user 用户数据
- * @return 受影响的行数
- */
- Integer insert(User user);
- /**
- * 根据用户名查询用户数据
- * @param username 用户名
- * @return 匹配的用户数据,如果没有匹配的数据,则返回null
- */
- User findByUsername(String username);
- }
2.由于这是项目中第一次创建持久层接口,还应在StoreApplication启动类之前添加@MapperScan("com.cy.store.mapper")注解,表示在项目一启动自动扫描这个包下的mapper接口。因为SpringBoot一开始不知道mapper接口放在哪,你得在项目启动入口告诉它在那个地方,通过注解的方式配置包扫描路径。
- @SpringBootApplication
- @MapperScan("com.cy.store.mapper")
- public class StoreApplication {
- public static void main(String[] args) {
- SpringApplication.run(StoreApplication.class, args);
- }
- }
Mybats与Spring整合后需要实现实体类和数据表的映射关系。实现实体类和数据表的映射关系可以在Mapper接口添加@Mapper注解。但建议以后直接在SpringBoot启动类中加@MapperScan("mapper包")注解,这样比较方便,不需要对每个Mapper都添加@Mapper注解。
复习一下:一个数据表对应一个实体类,每个实体类都有一个Mapper接口,每个Mapper自动生成一个Mapper实现类,每个Mapper接口对应一个Mapper配置文件。
eg: t_user --> User --> UserMapper.java --> UserMapper.xml
(4)配置SQL映射
1.在src/main/resource下创建mapper文件夹,并在文件夹下创建UserMapper.xml配置文件,进行以上两个抽象方法的映射配置。
- <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="com.cy.store.Mapper.UserMapper">
-
- <!--实体类属性和数据库表字段创建映射关系,映射的属性:把有下划线改成驼峰的
- 映射关系用于查询语句select,增删改不需要
- -->
- <resultMap id="userEntityMap" type="com.cy.store.Entity.User">
- <id column="uid" property="uid"></id>
- <result column="is_delete" property="isDelete"></result>
- <result column="created_user" property="createdUser"></result>
- <result column="created_time" property="createdTime"></result>
- <result column="modified_user" property="modifiedUser"></result>
- <result column="modified_time" property="modifiedTime"></result>
- </resultMap>
-
- <!--插入语句 使用主键自增userGenerateKeys keyProperty-->
- <!-- 插入用户数据:Integer insert(User user) -->
- <insert id="insert" useGeneratedKeys="true" keyProperty="uid" >
- insert into t_user(
- username,password, salt,phone,email,gender,
- avatar,is_delete,created_user, created_time,modified_user,modified_time
- )values(
- #{username},#{password},#{salt},#{phone},#{email},#{gender},
- #{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>
几点说明:
1.插入/删除/修改语句会有返回值(int),返回受影响的行数;
2.实体类属性和表字段创建映射关系,映射方式:字段下划线变为属性驼峰形式。自己手动改吧,字段复制过来自己去掉下划线改成驼峰命名
3.插入标签里往往需要主键自增,开启主键自增的几个核心属性: userGeneratedKeys keyProperty
userGeneratedKeys:表示开启的某个属性的字段递增,值为true
keyProperty:将表中的哪个属性自增
4.映射关系仅仅出现在select语句中,需要使用到reseltMap属性,来建立属性和字段的映射。也可以使用resultType,但必须实体类实行和表字段属性完全一致;增删改语句不存在属性字段映射的,因为看语句就知道了。
5.resultMap标签里的几个属性:
id表示唯一标识,select标签需要指定resultMap属性为这个id;
type标识映射那个实体类的全类名。在
标签中指定字段column和属性property的映射。字段名属性名一致的就不用指定映射了,长的一样的Mybatis可以自动映射成功。 注意:主键一定得指定映射,无论字段名和属性名是否一致。
标签里 6.#{}和¥{}的区别 ?
程序执行流程分析:
比如执行insertUser(User user)方法,业务层调用insertUser(User user),业务层里的userMapper已经自动注入了,然后业务层调用持久层的userMapper接口的insertUser(User user)方法,因为userMapper配置文件有个SQL语句的id=“insertUser”(方法名),因此执行这个SQL,把方法里的参数user对象里的所有属性注入到
2.由于这是项目中第一次使用SQL映射(配置文件中SQL语句和Mapper中的抽象方法映射),所哟需要在application.properties中添加mybatis.mapper-locations属性的配置,来指定XML的位置,告诉SpringBoot配置文件在哪个目录下,它方便和mapper接口抽象方法映射上。
mybatis.mapper-locations=classpath:mapper/*.xml
前面顺便提了一下啊,使用@MapperScan告诉SpringBoot把mapper接口都放在com.cy.store.mapper接口下,项目自动给给mapper下的接口当作mapper接口。然后SpringBoot知道了配置文件的的目录,再通过配置文件里的命名空间就实现了mapper接口和xml配置文件文件的映射了。
3.完成后及时执行单元测试,检查以上开发的功能是否正确运行。在src/test/java下创建com.cy.store.mapper.UserMapperTest单元测试类。在测试类的声明之前添加@RunWith(SpringRunner)和@SpringBootTest注解,并在测试类中声明持久化对象,通过自动装配注入值。
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
-
- import java.util.Date;
-
-
- @RunWith(SpringRunner.class) //注解是一个测试启动器,可以加载SpringBoot测试注解
- @SpringBootTest
- public class UserMapperTest {
- @Autowired
- private UserMapper userMapper;
- }
4.如果在第四步自动装配userMapper时,报“Could not autowire. No beans of 'UserMapper' type
5.然后编写两个测试方法,对以上完成的两个功能进行单元测试。
单元测试必须为public修饰,方法的返回值必须是void,方法不能有参数列表,并且方法被@Test注解修饰。
- @Test
- public void insertUser(){
- User user = new User();
- user.setUsername("user01");
- user.setPassword("1328478917");
- Integer row = userMapper.insert(user);
- System.out.println(row);
- }
-
- @Test
- public void findUserByUsername(){
- String username ="user01";
- User result = userMapper.findByUsername(username);
- System.out.println(result);
- }
如果出现现org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)异常,可能原因:
1.在resources文件检查下创建的mapper文件夹类型没有正确选择(eclipse选择Folder,IDEA选择Directory)
2.映射文件的mapper标签的namespace属性没有正确映射到dao层接口,或者application.properties中的属性mybatis.mapper-locations没有正确配置xml映射文件。。
(一)关于异常
1.列举至少十种异常:
ThrowableErrorOutOfMemoryError(OOM)ExceptionSQLExceptionIOExceptionFileNotFoundExceptionRuntimeExceptionNullPointerExceptionArithmeticExceptionClassCastExceptionIndexOutOfBoundsExceptionArrayIndexOutOfBoundsExceptionStringIndexOutOfBoundsException
2.异常的处理方法和处理原则:
(二)异常规划
1.为了便于统一管理自定义异常,应先创建com.cy.store.service.ex.ServiceException自定义异常的基类异常,继承自RuntimeException,并从父类生成子类的五个构造方法。
- package com.cy.store.service.ex;
-
- /*业务异常的基类*/
- public class ServiceException extends RuntimeException{
- public ServiceException() {
- super();
- }
-
- public ServiceException(String message) {
- super(message);
- }
-
- public ServiceException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ServiceException(Throwable cause) {
- super(cause);
- }
-
- protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
- }
2.当用户进行注册时,可能会因为用户名的占用导致无法正常注册,此时需要抛出用户名被占用的异常,因此可以设计一个用户名重复的com.cy.store.service.ex.UsernameDuplicateException异常类,继承自ServiceException类,并从父类生成子类的五个构造方法。
- package com.cy.store.service.ex;
-
- public class UsernameDuplicatedException extends ServiceException{
- ...
- }
3.在用户进行注册时,会执行数据库的INSERT操作,该操作也有可能失败的,则创建com.cy.store.service.ex.InsertException异常类,继承自ServiceException类,并从父类生成子类的5个构造方法。
- /*注册插入操作出现异常*/
- public class InsertException extends ServiceException{
- ...
- }
4.所有的自定义异常,都应是RuntimeException的子孙类异常。项目中目前异常的继承结构如下。
RuntimeException-- ServiceException-- UsernameDuplicateException-- InsertException
1.先创建com.cy,store,service.IUserService业务层接口,并在接口中添加抽象方法。
- public interface IUserService {
- /**
- * 用户注册
- * @param user
- */
- public void reg(User user);
- }
2.创建业务层接口的目的就是为了解耦。关于业务层的抽象方法的设计原则。
1.仅以操作成功为前提来设计返回值类型,不考虑操作失败的情况;2.方法名称可以自定义,通常与用户操作的功能相关;3.方法的参数列表根据执行的具体业务功能来确定,需要哪些数据就设计哪些数据。通常情况下,参数需要足以调用持久层对应的相关功能;同时还要满足参数是客户端可以传递给控制器的;4.方法中使用抛出异常的方式来表示操作失败。
1.创建建com.cy.store.service.impl.UserServiceImpl业务层实现类,并实现IUserService接口。在类之前添加@Service注解,并在类中添加持久层UserMapper对象。
- public class IUserServiceImpl implements IUserService {
- @Autowired
- private UserMapper userMapper;
-
- @Override
- public void reg(User user) {
-
- }
-
- }
2.UserServiceImpl类需要重写IUserService接口中的抽象方法
- @Override
- public void reg(User user) {
- //根据参数user对象获取注册的用户名
- String username = user.getUsername();
- //调用持久层的User findByUsername(String username)方法,根据用户名查询用户数据
- User result = userMapper.findByUsername(username);
- //判断查询结果是否不为null
- if(result !=null){
- //是,表示用户名已被占用,则抛出UsernameDuplicateException异常
- throw new UsernameDuplicatedException("尝试注册的用户名["+username+"]已经被占用");
- }
- //创建当前时间对象
- Date now = new Date();
- //补全数据:加密后的密码
- String salt = UUID.randomUUID().toString().toUpperCase();
- String md5Password = getMd5Password(user.getPassword(), salt);
- user.setPassword(md5Password);
-
- //补全数据:盐值
- user.setSalt(salt);
- //补全数据:isDelete(0)
- user.setIsDelete(0);
-
- //补全数据:4项日志属性
- user.setCreatedUser(username);
- user.setCreatedTime(now);
- user.setModifiedUser(username);
- user.setModifiedTime(now);
-
- //表示用户名没有被占用,则允许注册
- //调用持久层Integer insert(User user)方法,执行注册并获取返回值(受影响的行数)
- Integer rows = userMapper.insert(user);
-
- //判断受影响的行数是否不为1
- if (rows != 1){
- //是:插入数据时出现某种错误,则抛出InsertException异常
- throw new InsertException("添加用户数据出现未知错误,请联系管理员");
- }
-
- }
-
- /**
- * 执行密码加密
- * @param password 原始密码
- * @param salt 盐值
- * @return 加密后的密文
- */
- private String getMd5Password(String password, String salt) {
- /**
- * 加密规则
- * 1.无视原始密码的强度
- * 2.使用UUID作为盐值,在原始密码的左右两侧拼接
- * 3.循环加密3次
- */
- for(int i = 0 ; i <3 ;i++){
- password=DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
- }
- return password;
- }
3.完成后再src/test/java下创建com.cy.store.service.UserServiceTests测试类,编写并执行用户注册业务层的单元测试。
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class UserMapperTest {
-
- @Autowired
- private IUserService iUserService;
-
- @Test
- public void reg(){
- try{
- User user = new User();
- user.setUsername("1ower");
- user.setPassword("123456");
- user.setGender(1);
- user.setPhone("13953948109");
- user.setEmail("136@qq.com");
- user.setAvatar("xxxx");
- iUserService.reg(user);
- System.out.println("注册成功!!");
- }catch (ServiceException e){
- System.out.println("注册失败!"+e.getClass().getSimpleName());
- System.out.println(e.getMessage());
- }
-
- }
创建com.cy.store.util.jsonResult响应结果类型
结果响应信息(JsonResult):就是控制层需要返回给前端页面所封装的数据类型,自定义的
应包含的内容:状态码state,状态描述信息message,具体数据data
- @NoArgsConstructor
- @Data
- public class JsonResult<E> implements Serializable {
- /* 状态码 */
- private Integer state;
- /* 状态描述信息 */
- private String message;
- /* 数据 */
- private E data;
-
- public JsonResult(Integer state) {
- super();
- this.state = state;
- }
-
- public JsonResult(Integer state, E data) {
- super();
- this.state = state;
- this.data = data;
- }
-
- /* 出现异常时调用*/
- public JsonResult(Throwable e){
- super();
- //获取异常对象中的异常信息
- this.message = e.getMessage();
- }
-
-
- }
设计用户提交的请求,并设计响应的方式:
请求路径:/user/reg
请求参数:User user
请求参数:POST
响应结果:JsonResult
Alt + 鼠标左键 选中多行
1.创建com.cy.store.controller.UserController控制器类,在类的声明之前添加@RestController和@RequestMapping("users")注解,在类中添加IUserService业务对象并使用@Autowired注解修饰。(之所以继承BaseControl后面再解释)
- @RestController
- @RequestMapping("users")
- public class UserController extends BaseController {
- @Autowired
- private IUserService userService;
- }
2.然后在类中添加处理请求的用户注册方法。
- @RequestMapping("reg")
- public JsonResult
reg(User user){ -
- // JsonResult
result = new JsonResult(); - // try{
- // //调用业务对象执行注册
- // userService.reg(user);
- // //响应成功
- // result.setState(200);
- // }catch (UsernameDuplicatedException e){
- // //用户名被占用
- // result.setState(40000);
- // result.setMessage("用户名已经被占用!!!");
- // }catch (InsertException e){
- // //插入数据异常
- // result.setState(5000);
- // result.setMessage("注册失败,请联系管理员!");
- // }
- // return result;
-
- //调用业务对象执行注册
- userService.reg(user);
- //返回
- return new JsonResult
(OK); - }
3.完成后启动项目,打开浏览器访问进行测试
http://localhost:8080/users/reg?username=controller&password=123456
返回的应该是一个JsonResult数据
{
state : 200 ,message : null ,data : null}
@ExceptionHandler 注解用于统一处理方法抛出的异常。当我们使用这个注解时,需要定义一个 异常的处理方法,再给这个方法加上@ExceptionHandler 注解,这个方法就会处理类中其他方法 (被@RequestMapping 注解)抛出的异常。 @ExceptionHandler 注解中可以添加参数,参数是某 个异常类的class ,代表这个方法专门处理该类异常。
- public class BaseController {
- /**操作成功的状态码**/
- public static final int OK = 200;
-
- /** @ExceptionHandle用于处理统一处理方法的异常 **/
- @ExceptionHandler(ServiceException.class)
- public JsonResult
handleException(Throwable e){ - JsonResult
result = new JsonResult(e); - if (e instanceof UsernameDuplicatedException) {
- result.setState(4000);
- } else if (e instanceof InsertException) {
- result.setState(5000);
- }
-
- return result;
- }
- /** 处理用户相关请求的控制器类 */
- @RestController
- @RequestMapping("users")
- public class UserController extends BaseController {
- @Autowired
- private IUserService userService;
-
- @RequestMapping("reg")
- public JsonResult
reg(User user) { - // 调用业务对象执行注册
- userService.reg(user);
- // 返回
- return new JsonResult
(OK); - }
- }
3.完成后启动项目,打开浏览器访问请求进行测试。
http://localhost:8080/users/reg?username=controller&password=123456
3.完成后启动项目,打开浏览器访问http://localhost:8080/web/register.html页面并进行注册。