SpringBoot15 sell01 项目创建、MySQL数据库连接、日志配置、开发热部署、商品信息模块
项目软件版本说明:
jdk: 1.8
springboot: 2.0.1
mysql: 5.7
1 项目创建
创建一个SpringBoot项目即可,创建是勾选 web jpa mysql 这三个依赖就可
2 MySQL数据库连接
技巧01:如果在创建项目时勾选了MySQL和jpa相关依赖就必须进行mysql连接配置,如果不配置就会报错,错误信息如下
技巧02:连接MySQL数据是时最好设置 useSSL=false
server: port: 9999 servlet: context-path: /sell spring: datasource: driver-class-name: com.mysql.jdbc.Driver username: dev password: Dev-182838 url: jdbc:mysql://47.104.74.9/sell_demo?characterEncoding=utf-8&useSSL=false jpa: show-sql: true
3 日志配置
3.1 引入lombok依赖
引入改以来只是为了在进行编写日志输出时更加方便
3.2 编写yam配置文件或者xml配置文件
参考博文:点击前往
3.3 在类级别添加@Slf4j 注解
4 开发热部署
4.1 引入devtools依赖
<!--项目热部署相关--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
4.2 插件配置
4.3 其他配置
参考博文:点击前往
5 自动生成代码
5.1 导入相关依赖
<!-- Mybatis-Plus 自动生成实体类--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.0.6</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version> </dependency>
5.2 编写代码生成器
package cn.xiangxu.demo.sell_demo.util.genderateCode; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.rules.DbType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; /** * @author 王杨帅 * @create 2018-04-21 11:37 * @desc 根据数据库表自动生成代码 **/ public class AutoGenerateCode { public static void main(String[] args) throws InterruptedException { AutoGenerator mpg = new AutoGenerator(); // 全局配置定义 GlobalConfig gc = new GlobalConfig(); gc.setOutputDir("F:\\javaProgramming\\springBoot\\testDemo\\sell_demo\\src\\main\\java\\cn\\xiangxu\\demo\\sell_demo\\util\\genderateCode"); // 设置存储路径 gc.setFileOverride(true); gc.setActiveRecord(true); // gc.setEnableCache(true);// XML 二级缓存 gc.setBaseResultMap(true);// XML ResultMap gc.setBaseColumnList(true);// XML columList gc.setAuthor("王杨帅"); // 作者信息 // 自定义文件命名,注意 %s 会自动填充表实体属性! gc.setMapperName("%sDao"); gc.setXmlName("%sMapper"); gc.setServiceName("%sService"); gc.setServiceImplName("%sServiceImpl"); gc.setControllerName("%sController"); mpg.setGlobalConfig(gc); // 设置全局配置 // 数据源配置定义 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); /*dsc.setTypeConvert(new MySqlTypeConvert(){ // 自定义数据库表字段类型转换【可选】 @Override public DbColumnType processTypeConvert(String fieldType) { System.out.println("转换类型:" + fieldType); return super.processTypeConvert(fieldType); } });*/ dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUrl("jdbc:mysql://47.104.74.9:3306/sell_demo?useUnicode=true&characterEncoding=UTF-8&generateSimpleParameterMetadata=true&useSSL=false"); dsc.setUsername("dev"); dsc.setPassword("Dev-182838"); mpg.setDataSource(dsc); // 设置数据源 // 策略配置 StrategyConfig strategy = new StrategyConfig(); // strategy.setCapitalMode(true);// 全局大写命名 ORACLE 注意 // strategy.setTablePrefix(new String[] { "tlog_", "tsys_" });// 此处可以修改为您的表前缀 strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略 // strategy.setInclude(new String[] { "user" }); // 需要生成的表 // strategy.setExclude(new String[]{"test"}); // 排除生成的表 mpg.setStrategy(strategy); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent("cn.xiangxu.demo"); pc.setModuleName("sell_demo"); mpg.setPackageInfo(pc); // 执行生成 mpg.execute(); } }
5.3 参考博文
5.4 实体类相关
技巧01:利用该代码生成器生成的实体,使用的注解都是mybatis先关的,开发者必须进行手动更改
技巧02:实体类先关注解
》@Entity -> 设置该类是一个持久实体类
》@Table(name = "order_master") -> 设置数据库表名
》@Column -> 别名设置,如果数据表字段和实体类属性不一致时需要用到这个注解
》@DynamicUpdate -> 设置动态更新;更新一条记录时,更新时间如果是由数据库端进行自动更新,那么就必须在实体类上添加这个注解,否则数据库端的自动更新时间会失效;
坑01:利用JPA更新一条记录时会将该记录更新后的结果返回,但是在客户端进行更新的数据不会及时返回,还是返回上一次操作后的数据
》@Id -> 设置数据库表主键对应的实体类属性,每个持久实体类必须有一个字段拥有 @Id 注解;如果数据库表的主键是设置了自动生成还可一个添加 @GeneratedValue 来实现自动生成(即:在进行数据插入的时候如果标有 @Id 字段没有传值进来就会自动创建一个对应的值)
坑01:@GeneratedValue 注解由4中策略,这一点从源码中可以看出,其中默认是AUTO;如果使用的是MySQL数据库而且在创建数据库表时为主键这顶了自增功能就只能使用 IDENTITY 策略,否则会报错,错误信息如下;参考博文:点击前往
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package javax.persistence; public enum GenerationType { TABLE, SEQUENCE, IDENTITY, AUTO; private GenerationType() { } }
6 商品类目模块
6.0 数据库表
技巧01:主键使用了MySQL的自动生成,所以在创建实体类时必须制定主键生成策略为 IDENTITY
技巧02:创建时间和更新时间都设定了默认值
技巧03:更新时间设定了自动更新,如果想要这个设置生效,那么实体类就必须使用动态更新注解 @DynamicUpdate
DROP TABLE IF EXISTS `product_category`; CREATE TABLE `product_category` ( `category_id` int(11) NOT NULL AUTO_INCREMENT, `category_name` varchar(64) NOT NULL COMMENT '类目名字', `category_type` int(11) NOT NULL COMMENT '类目编号', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`category_id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
6.1 实体类
各种实体的区别:点击前往
具体代码请参见码云:点击前往
package cn.xiangxu.demo.sell_demo.entity.entityPO; import com.baomidou.mybatisplus.annotations.TableField; import com.baomidou.mybatisplus.annotations.TableId; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.*; import java.util.Date; /** * @author 王杨帅 * @create 2018-04-21 13:30 * @desc 商品类型实体类 **/ @Table(name = "product_category") @Data @Entity @DynamicUpdate public class ProductCategoryPO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "category_id") private Integer categoryId; /** * 类目名字 */ @Column(name = "category_name") private String categoryName; /** * 类目编号 */ @Column(name = "category_type") private Integer categoryType; /** * 创建时间 */ @Column(name = "create_time") private Date createTime; /** * 修改时间 */ @Column(name = "update_time") private Date updateTime; public ProductCategoryPO() { } public ProductCategoryPO(String categoryName, Integer categoryType) { this.categoryName = categoryName; this.categoryType = categoryType; } }
6.2 持久层
参考博文:点击前往
package cn.xiangxu.demo.sell_demo.dao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; /** * @author 王杨帅 * @create 2018-04-21 13:43 * @desc 商品类目持久层接口 **/ public interface ProductCategoryDao extends JpaRepository<ProductCategoryPO, Integer> { /** 根据类目列表查询商品类目信息 */ List<ProductCategoryPO> findByCategoryTypeIn(List<Integer> categoryTypeList); }
编程技巧:利用Arrays提供的方法实例化集合;asList 方法支持多参数传入,返回对象时这些参数组成的集合
List<Integer> typeList = Arrays.asList(0,1);
6.2.1 持久层测试类
技巧01:新增记录时不用指定记录ID、创建时间、更新时间,因为他们都是由数据库自动生成
技巧01:必须提供记录ID,因为JPA的save方法时根据记录ID进行更新操作的,如果数据库中存在该记录ID就会进行更新操作,如果不存在就会进行新增操作;但是如果数据库表设定了主键自增,那么这里即使是新增操作也不会使用传入的记录ID
package cn.xiangxu.demo.sell_demo.dao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; 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.Arrays; import java.util.List; import java.util.Optional; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductCategoryDaoTest { @Autowired private ProductCategoryDao productCategoryDao; /** * 新增商品类目 * 技巧01:不用指定记录ID、创建时间、更新时间,因为他们都是由数据库自动生成 */ @Test public void create() { ProductCategoryPO productCategoryPO = new ProductCategoryPO("腌菜食品", 3); ProductCategoryPO result = productCategoryDao.save(productCategoryPO); log.info("===/" + getClass().getName() + "/===新增后返回的结果为:{}", result); Assert.assertNotEquals(null, result); } /** * 根据商品类目ID查询对应的商品类目信息 */ @Test public void findById() { Integer id = 7; Optional<ProductCategoryPO> productCategoryPOOptional = productCategoryDao.findById(id); log.info("/===" + getClass().getName() + "/findById===根据ID获取到结果为:{}", productCategoryPOOptional.get()); Assert.assertNotEquals(null, productCategoryPOOptional.get()); } /** * 更新商品类目 * 技巧01:必须提供记录ID,因为JPA的save方法时根据记录ID进行更新操作的,如果数据库中存在该记录ID就会进行更新操作,如果不存在就会进行 * 新增操作;但是如果数据库表设定了主键自增,那么这里即使是新增操作也不会使用传入的记录ID */ @Test public void update() { Optional<ProductCategoryPO> productCategoryPOOptional = productCategoryDao.findById(7); Assert.assertNotEquals(null, productCategoryPOOptional.get()); ProductCategoryPO old = productCategoryPOOptional.get(); log.info("====原始值:{}", old.toString()); old.setCategoryName("垃圾商品"); log.info("===修改值:{}", old.toString()); ProductCategoryPO result = productCategoryDao.save(old); log.info("===更新结果值:{}", result.toString()); Assert.assertNotEquals(null, result); } /** * 根据类目列表查询商品类目信息 */ @Test public void findByCategoryTypeIn() { List<Integer> typeList = Arrays.asList(0,1); List<ProductCategoryPO> productCategoryPOList = productCategoryDao.findByCategoryTypeIn(typeList); log.info("===/" + getClass().getName() + "/===获取到的数据为:{}", productCategoryPOList.toString()); Assert.assertNotEquals(0, productCategoryPOList.size()); } }
6.3 服务层
6.3.1 服务层接口
package cn.xiangxu.demo.sell_demo.service; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import java.util.List; /** * 商品类目服务层接口 * @author 王杨帅 * @since 2018-04-21 */ public interface ProductCategoryService { /** 新增商品类目信息 */ ProductCategoryPO create(ProductCategoryPO productCategoryPO); /** 更新商品类目信息 */ ProductCategoryPO update(ProductCategoryPO productCategoryPO); /** 根据商品类目类型ID查询商品类目信息 */ ProductCategoryPO findByCategoryType(Integer categoryType); /** 根据记录ID查询 */ ProductCategoryPO findById(Integer id); /** 查询所有商品类目信息 */ List<ProductCategoryPO> findAll(); /** 根据商品类目类型ID列表查询商品类目信息 */ List<ProductCategoryPO> findByCategoryTypeIn(List<Integer> categoryTypeList); }
6.3.2 服务层实现类
编程技巧01:SpringBoot项目从2.0开始后利用JPA通过ID进行查询时返回的数据类型时Optional类型
Optional常用的方法有:
get() -> 获取数据,如果Optional对象中没有数据调用get方法时就会报错
isPresent() -> 判断Optional对象是否包含数据,如果有返回true,否则返回false
ifPresent() -> 判断Optional对象是否有数据,如果有就进行一些操作;这些操作通常是利用接口或者lambda表达式实现
参考文档:点击前往
编程技巧02:如果数据库表的时间字段设置了时间自动更新,那么必须在实体类中添加 @DynamicUpdate , 而且在进行数据插入或者数据更新时设置了时间自动更新的实体类属性值最好设置成null (PS:个人觉得更新时间应该由系统产生,然后再存放到数据库中去;而不是由数据进行时间自动更新操作)
package cn.xiangxu.demo.sell_demo.service.impl; import cn.xiangxu.demo.sell_demo.dao.ProductCategoryDao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import cn.xiangxu.demo.sell_demo.enums.ResultEnum; import cn.xiangxu.demo.sell_demo.exceptions.SellException; import cn.xiangxu.demo.sell_demo.service.ProductCategoryService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * 商品类目服务层实现类 */ @Service public class ProductCategoryServiceImpl implements ProductCategoryService { @Autowired private ProductCategoryDao productCategoryDao; @Override public ProductCategoryPO create(ProductCategoryPO productCategoryPO) { // 01 判断商品类目类型是否存在; List<ProductCategoryPO> result = productCategoryDao.findByCategoryTypeIn(Arrays.asList(productCategoryPO.getCategoryType())); // 02 如果存在就抛出异常:商品类目类型ID已经存在 if (result.size() != 0) { throw new SellException(ResultEnum.PRODUCT_CATEGORY_TYPE_IS_EXIST); } // 03 进行插入或更新操作 ProductCategoryPO createResult = productCategoryDao.save(productCategoryPO); // 04 返回新增对象 return createResult; } @Override public ProductCategoryPO update(ProductCategoryPO productCategoryPO) { // 01 判断商品类目ID是否存在;如果不存在抛出异常:商品类目不存在 ProductCategoryPO result = findById(productCategoryPO.getCategoryId()); // 02 进行更新操作 productCategoryPO.setCreateTime(result.getCreateTime()); // 获取创建时间 // productCategoryPO.setUpdateTime(result.getCreateTime()); BeanUtils.copyProperties(productCategoryPO, result); // 将更新信息复制给result,此时result的更新时间为null;只有为null时数据库才会自动更新时间,否者会按照给定的值进行更新 ProductCategoryPO updateResult = productCategoryDao.save(result); // 03 返回更新结果 return updateResult; } @Override public ProductCategoryPO findByCategoryType(Integer categoryType) { return null; } @Override public ProductCategoryPO findById(Integer id) { // 01 调用持久层方法进行查询 Optional<ProductCategoryPO> result = productCategoryDao.findById(id); // 02 判断查询结果是否为空,如果为空就抛出异常:商品类目信息不存在 if (!result.isPresent()) { throw new SellException(ResultEnum.PRODUCT_CATEGORY_IS_NULL); } // 03 返回查询结果 return result.get(); } @Override public List<ProductCategoryPO> findAll() { return productCategoryDao.findAll(); } @Override public List<ProductCategoryPO> findByCategoryTypeIn(List<Integer> categoryTypeList) { // 01 调用持久层进行查询 List<ProductCategoryPO> result = productCategoryDao.findByCategoryTypeIn(categoryTypeList); // 02 判断查询结果,如果结果为空,就抛出异常:商品类目信息不存在 if (result.size() == 0) { throw new SellException(ResultEnum.PRODUCT_CATEGORY_IS_NULL); } // 03 返回查询结果 return result; } }
6.3.3 服务层测试类
package cn.xiangxu.demo.sell_demo.service.impl; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import cn.xiangxu.demo.sell_demo.service.ProductCategoryService; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; 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.Arrays; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductCategoryServiceImplTest { @Autowired private ProductCategoryService productCategoryService; @Test public void create() throws Exception { ProductCategoryPO productCategoryPO = new ProductCategoryPO("罐头类食品", 5); ProductCategoryPO result = productCategoryService.create(productCategoryPO); log.info("===/" + getClass().getName() + "/create===新增后返回结果为:{}", result); Assert.assertNotEquals(null, result); } @Test public void update() throws Exception { ProductCategoryPO productCategoryPO = new ProductCategoryPO("罐头类食品_修改02", 5); productCategoryPO.setCategoryId(16); ProductCategoryPO result = productCategoryService.update(productCategoryPO); log.info("===/" + getClass().getName() + "/update===更新后返回结果为:{}", result); Assert.assertNotEquals(null, result); } @Test public void findByCategoryType() throws Exception { } @Test public void findById() throws Exception { Integer id = 16; ProductCategoryPO result = productCategoryService.findById(id); log.info("===/" + getClass().getName() + "/findById===根据类目ID查询得到的结果为:{}", result); Assert.assertNotEquals(null, result); } @Test public void findAll() throws Exception { List<ProductCategoryPO> result = productCategoryService.findAll(); log.info("===/" + getClass().getName() + "/findAll===根据类目ID查询得到的结果为:{}", result); Assert.assertNotEquals(0, result.size()); } @Test public void findByCategoryTypeIn() throws Exception { List<ProductCategoryPO> result = productCategoryService.findByCategoryTypeIn(Arrays.asList(0,1,2)); log.info("===/" + getClass().getName() + "/findByCategoryTypeIn===根据类目ID查询得到的结果为:{}", result); Assert.assertNotEquals(0, result.size()); } }
7 商品详情模块
7.1 数据库表
技巧01:主键使用了MySQL的自动生成,所以在创建实体类时必须制定主键生成策略为 IDENTITY
技巧02:创建时间和更新时间都设定了默认值
技巧03:更新时间设定了自动更新,如果想要这个设置生效,那么实体类就必须使用动态更新注解 @DynamicUpdate
技巧04:在进行更新操作时,如果实体对象的 “创建时间字段” 为null,那么就会以当前时间插入到数据库,这样做是不合法的;因为记录的创建时间时不允许更改的,所以进行更新操作时必须先获取之前的创建时间,然后再将其插入到数据库中
技巧05:在进行更新操作时,实体类对象的 ”更新时间字段“ 最好设置成 null,因为数据库端的对应字段设置了时间自动刷新约束
DROP TABLE IF EXISTS `product_info`; CREATE TABLE `product_info` ( `product_id` varchar(32) NOT NULL, `product_name` varchar(64) NOT NULL COMMENT '商品名称', `product_price` decimal(8,2) NOT NULL COMMENT '单价', `product_stock` int(11) NOT NULL COMMENT '库存', `product_description` varchar(64) DEFAULT NULL COMMENT '描述', `product_icon` varchar(512) DEFAULT NULL COMMENT '小图', `product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架', `category_type` int(11) NOT NULL COMMENT '类目编号', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
7.2 实体类
package cn.xiangxu.demo.sell_demo.entity.entityPO; import cn.xiangxu.demo.sell_demo.enums.ProductStatusEnum; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import java.math.BigDecimal; import java.util.Date; /** * @author 王杨帅 * @create 2018-04-21 13:25 * @desc 商品信息实体类 **/ @Data @Table(name = "product_info") @Entity @DynamicUpdate public class ProductInfoPO { /** * 商品ID */ @Id @Column(name = "product_id") private String productId; /** * 商品名称 */ @Column(name = "product_name") private String productName; /** * 单价 */ @Column(name = "product_price") private BigDecimal productPrice; /** * 库存 */ @Column(name = "product_stock") private Integer productStock; /** * 描述 */ @Column(name = "product_description") private String productDescription; /** * 小图 */ @Column(name = "product_icon") private String productIcon; /** * 商品状态,0正常1下架 */ @Column(name = "product_status") private Integer productStatus = ProductStatusEnum.ON_SALE.getCode(); /** * 类目编号 */ @Column(name = "category_type") private Integer categoryType; /** * 创建时间 */ @Column(name = "create_time") private Date createTime; /** * 修改时间 */ @Column(name = "update_time") private Date updateTime; }
7.3 持久层接口
参考博文:点击前往
package cn.xiangxu.demo.sell_demo.dao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; /** * @author 王杨帅 * @create 2018-04-21 13:43 * @desc 商品信息持久层接口 **/ public interface ProductInfoDao extends JpaRepository<ProductInfoPO, String> { /** 根据商品状态查询 */ List<ProductInfoPO> findByProductStatus(Integer status); }
7.3.1 持久层测试类
package cn.xiangxu.demo.sell_demo.dao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import cn.xiangxu.demo.sell_demo.enums.ProductStatusEnum; import cn.xiangxu.demo.sell_demo.enums.ResultEnum; import cn.xiangxu.demo.sell_demo.exceptions.SellException; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.swing.text.html.Option; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.OptionalInt; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductInfoDaoTest { private String className = getClass().getName(); @Autowired private ProductInfoDao productInfoDao; @Test public void create() { ProductInfoPO productInfoPO = new ProductInfoPO(); productInfoPO.setProductId("20180003"); productInfoPO.setProductName("纸上烤鱼"); productInfoPO.setProductPrice(new BigDecimal(88)); productInfoPO.setProductStock(100); productInfoPO.setProductDescription("鲜香"); productInfoPO.setProductIcon("http://sasd.com"); productInfoPO.setCategoryType(4); ProductInfoPO result = productInfoDao.save(productInfoPO); log.info("===/" + className + "/create===新增商品信息后返回的结果为:{}", result); Assert.assertNotEquals(null, result); } @Test public void update() { // 01 创建修改对象 ProductInfoPO productInfoPO = new ProductInfoPO(); productInfoPO.setProductId("20180002"); productInfoPO.setProductName("重庆小面"); productInfoPO.setProductPrice(new BigDecimal(6)); productInfoPO.setProductStock(9999); productInfoPO.setProductDescription("麻辣爽口"); productInfoPO.setProductIcon("http://sasd.com"); productInfoPO.setCategoryType(2); // 02 根据商品ID查询 Optional<ProductInfoPO> old = productInfoDao.findById(productInfoPO.getProductId()); // 03 如果不存在对应商品信息就抛出异常 if (!old.isPresent()) { throw new SellException(ResultEnum.PRODUCT_INFO_IS_NULL); } // 04 更新数据封装 ProductInfoPO newData = old.get(); productInfoPO.setCreateTime(newData.getCreateTime()); BeanUtils.copyProperties(productInfoPO, newData); // 05 更新操作 ProductInfoPO result = productInfoDao.save(newData); // 06 打印更新结果 log.info("===/" + className + "/update===更新后返回的结果为:{}", result); } @Test public void findById() { // 01 模拟一个商品ID String productId = "20180001"; // 02 根据ID查询 Optional<ProductInfoPO> result = productInfoDao.findById(productId); // 03 判断是否有对应的商品信息,如果没有抛出异常:商品信息为空 if (!result.isPresent()) { throw new SellException(ResultEnum.PRODUCT_INFO_IS_NULL); } // 04 打印获取到的数据 log.info("===/" + className + "/findById===根据ID获取到的商品信息为:{}", result.get()); } @Test public void findByProductStatus() throws Exception { // 01 模拟一个商品状态 Integer status = ProductStatusEnum.ON_SALE.getCode(); // 02 根据商品状态查询 List<ProductInfoPO> result = productInfoDao.findByProductStatus(status); // 03 打印获取到的数据 log.info("===/" + className + "/findByProductStatus===根据商品状态获取到数据为:{}", result); Assert.assertNotEquals(0, result.size()); } }
7.4 服务层
7.4.1 服务层接口
package cn.xiangxu.demo.sell_demo.service; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import java.util.List; /** * 商品信息服务层接口 * @author 王杨帅 * @since 2018-04-21 */ public interface ProductInfoService { /** 新增商品信息 */ ProductInfoPO create(ProductInfoPO productInfoPO); /** 更新商品信息 */ ProductInfoPO update(ProductInfoPO productInfoPO); /** 根据商品ID获取数据 */ ProductInfoPO findById(String productId); /** 根据商品状态获取数据 */ List<ProductInfoPO> findByProductStatus(Integer status); }
7.4.2 服务层实现类
package cn.xiangxu.demo.sell_demo.service.impl; import cn.xiangxu.demo.sell_demo.dao.ProductInfoDao; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import cn.xiangxu.demo.sell_demo.enums.ResultEnum; import cn.xiangxu.demo.sell_demo.exceptions.SellException; import cn.xiangxu.demo.sell_demo.service.ProductInfoService; import com.fasterxml.jackson.databind.util.BeanUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; /** * 商品信息服务层实现类 * @author 王杨帅 * @since 2018-04-21 */ @Service public class ProductInfoServiceImpl implements ProductInfoService { @Autowired private ProductInfoDao productInfoDao; @Override public ProductInfoPO create(ProductInfoPO productInfoPO) { // 01 根据商品ID查询 Optional<ProductInfoPO> old = productInfoDao.findById(productInfoPO.getProductId()); // 02 如果查询到数据就抛出异常:商品信息已经存在 if (old.isPresent()) { throw new SellException(ResultEnum.PRODUCT_ID_IS_EXIST); } // 03 进行更新操作 ProductInfoPO result = productInfoDao.save(productInfoPO); // 04 返回更新结果 return result; } @Override public ProductInfoPO update(ProductInfoPO productInfoPO) { // 01 根据ID查询数据 Optional<ProductInfoPO> old = productInfoDao.findById(productInfoPO.getProductId()); // 02 如果查询不到数据就抛出异常:商品信息不存在 if (!old.isPresent()) { throw new SellException(ResultEnum.PRODUCT_INFO_IS_NULL); } // 03 更新数据 ProductInfoPO newData = old.get(); productInfoPO.setCreateTime(newData.getCreateTime()); BeanUtils.copyProperties(productInfoPO, newData); ProductInfoPO result = productInfoDao.save(newData); // 04 返回更新结果 return result; } @Override public ProductInfoPO findById(String productId) { // 01 根据ID查询数据 Optional<ProductInfoPO> old = productInfoDao.findById(productId); // 02 如果查询不到数据就抛出异常:商品信息不存在 if (!old.isPresent()) { throw new SellException(ResultEnum.PRODUCT_INFO_IS_NULL); } // 03 返回更新结果 return old.get(); } @Override public List<ProductInfoPO> findByProductStatus(Integer status) { // 01 根据状态进行查询 List<ProductInfoPO> result = productInfoDao.findByProductStatus(status); // 02 返回查询结果 return result; } }
7.4.3 服务层测试类
package cn.xiangxu.demo.sell_demo.service.impl; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import cn.xiangxu.demo.sell_demo.enums.ProductStatusEnum; import cn.xiangxu.demo.sell_demo.service.ProductInfoService; import lombok.extern.slf4j.Slf4j; 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.math.BigDecimal; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class ProductInfoServiceImplTest { private String className = getClass().getName(); @Autowired private ProductInfoService productInfoService; @Test public void create() throws Exception { // 01 构造商品信息对象 ProductInfoPO productInfoPO = new ProductInfoPO(); productInfoPO.setProductId("20180004"); productInfoPO.setProductName("回锅肉"); productInfoPO.setProductPrice(new BigDecimal(33)); productInfoPO.setProductStock(100); productInfoPO.setProductDescription("香辣可口"); productInfoPO.setProductIcon("http://sasd.com"); productInfoPO.setCategoryType(3); // 02 新增操作 ProductInfoPO result = productInfoService.create(productInfoPO); // 03 打印输出数据 log.info("===/" + className + "/create===新增结果为:{}", result); } @Test public void update() throws Exception { // 01 构造商品信息 ProductInfoPO productInfoPO = new ProductInfoPO(); productInfoPO.setProductId("20180004"); productInfoPO.setProductName("回锅肉——修改"); productInfoPO.setProductPrice(new BigDecimal(33)); productInfoPO.setProductStock(100); productInfoPO.setProductDescription("香辣可口"); productInfoPO.setProductIcon("http://sasd.com"); productInfoPO.setCategoryType(3); // 02 更新数据 ProductInfoPO result = productInfoService.update(productInfoPO); // 03 打印更新数据 log.info("===/" + className + "/update===更新后的结果为:{}", result); } @Test public void findById() throws Exception { // 01 模拟一个商品ID String productId = "20180004"; // 02 根据商品ID获取数据 ProductInfoPO result = productInfoService.findById(productId); // 03 打印获取到的商品信息 log.info("===/" + className + "/findById==={}", result); } @Test public void findByProductStatus() throws Exception { // 01 根据商品状态查询 List<ProductInfoPO> result = productInfoService.findByProductStatus(ProductStatusEnum.ON_SALE.getCode()); // 02 打印获取到的数据 log.info("===/" + className + "/findByProductStatus===根据状态查询到的数据为:{}", result); } }
8 商品信息控制层
8.1 需求
获取所有的在售商品,并按照商品类型进行分类展示
8.2 前端格式要求
商品信息对应一个商品类型列表
每个商品类型中包含的新消息有:类型编码、类型名称、商品信息列表
{ "code": 0, "msg": "成功", "data": [ { "name": "热榜", "type": 1, "foods": [ { "id": "123456", "name": "皮蛋粥", "price": 1.2, "description": "好吃的皮蛋粥", "icon": "http://xxx.com", } ] }, { "name": "好吃的", "type": 2, "foods": [ { "id": "123457", "name": "慕斯蛋糕", "price": 10.9, "description": "美味爽口", "icon": "http://xxx.com", } ] } ] }
8.2.1 响应格式
响应数据格式对最外层包装类,主要包括的信息有:响应编号、响应信息、响应数据
package cn.xiangxu.demo.sell_demo.entity.entityVO; import lombok.Data; /** * @author 王杨帅 * @create 2018-04-22 14:43 * @desc 响应实体类 **/ @Data public class ResultVO<T> { private Integer code; private String msg; private T data; public ResultVO() { } public ResultVO(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
8.2.2 响应工具类
利用工具类来对响应数据进行快速封装
package cn.xiangxu.demo.sell_demo.util; import cn.xiangxu.demo.sell_demo.entity.entityVO.ResultVO; /** * @author 王杨帅 * @create 2018-04-22 14:57 * @desc 响应结果封装 **/ public class ResultUtil { public static ResultVO success() { return success(null); } public static ResultVO success(Object data) { Integer code = 0; String message = "success"; ResultVO resultVO = new ResultVO(); resultVO.setCode(code); resultVO.setMsg(message); resultVO.setData(data); return resultVO; } public static ResultVO error() { Integer code = -1; String message = "error"; Object data = null; return new ResultVO(code, message, data); } public static ResultVO error(Integer code, String message) { Object data = null; return new ResultVO(code, message, data); } }
8.3 商品控制层综合
8.3.1 思路
先根据在售状态查询到 “在售商品信息列表”
从 “在售商品信息列表” 中获取 “商品类目编号列表”
根据商品类目进行数据封装
8.3.2 展现实体类
由于前端并不要求将查询到的数据全部返回,所以需要根据前端要求进行展现实体类设计
技巧01:展现实体类中的字段最好和持久层实体类的字段名称保持一致,数量可以不一致;这样做的目的时为了方便数据的复制
技巧02:前端获取到的属性名称就是展现实体类的属性名称,如果两者不一致时就在咱先实体类对应的属性上添加 @JsonProperty("foods") 注解来解决
package cn.xiangxu.demo.sell_demo.entity.entityVO; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; /** * @author 王杨帅 * @create 2018-04-22 14:45 * @desc 商品类型实响应实体类 **/ @Data public class ProductTypeVO { /** 商品烈性名称 */ private String name; /** 商品类型编号 */ private Integer type; /** 商品信息列表 */ @JsonProperty("foods") private List<ProductInfoVO> productInfoVOList; }
package cn.xiangxu.demo.sell_demo.entity.entityVO; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.Data; import javax.persistence.Column; import java.math.BigDecimal; /** * @author 王杨帅 * @create 2018-04-22 14:47 * @desc 商品信息响应实体 **/ @Data public class ProductInfoVO { /** * 商品ID */ @JsonProperty("id") private String productId; /** * 商品名称 */ @JsonProperty("name") private String productName; /** * 单价 */ @JsonProperty("price") private BigDecimal productPrice; /** * 描述 */ @JsonPropertyOrder("description") private String productDescription; /** * 小图 */ @JsonProperty("icon") private String productIcon; }
8.3.3 控制层代码
stream() -> 将一个列表变成流的形式
map() -> 对数据流中的数据依次进行处理
collect(Collectors.toList()) -> 将数据流转换成一个集合
package cn.xiangxu.demo.sell_demo.controller; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductCategoryPO; import cn.xiangxu.demo.sell_demo.entity.entityPO.ProductInfoPO; import cn.xiangxu.demo.sell_demo.entity.entityVO.ProductInfoVO; import cn.xiangxu.demo.sell_demo.entity.entityVO.ProductTypeVO; import cn.xiangxu.demo.sell_demo.entity.entityVO.ResultVO; import cn.xiangxu.demo.sell_demo.enums.ProductStatusEnum; import cn.xiangxu.demo.sell_demo.service.ProductCategoryService; import cn.xiangxu.demo.sell_demo.service.ProductInfoService; import cn.xiangxu.demo.sell_demo.util.ResultUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * @author 王杨帅 * @create 2018-04-22 16:27 * @desc 买家端控制层 **/ @RestController @RequestMapping("/buyer") @Slf4j public class BuyerController { private final String className = getClass().getName(); @Autowired private ProductInfoService productInfoService; @Autowired private ProductCategoryService productCategoryService; @GetMapping(value = "/product/list") public ResultVO productList() { // 01 获取在售的商品信息列表 List<ProductInfoPO> productInfoPOList = productInfoService.findByProductStatus(ProductStatusEnum.ON_SALE.getCode()); log.info("===/" + className + "/test===获取到的商品信息列表为:{}", productInfoPOList); // 02 从获取到的商品信息列表中获取所有商品类型并组成一个商品类型列表 List<Integer> categoryList = productInfoPOList.stream() .map(e -> e.getCategoryType()) .collect(Collectors.toList()); log.info("===/" + className + "/test===获取到的商品类型列表为:{}", categoryList); // 03 根据商品类型列表查询商品类型信息 List<ProductCategoryPO> productCategoryPOList = productCategoryService.findByCategoryTypeIn(categoryList); log.info("===/" + className + "/test===获取到的商品类型信息列表为:{}", productCategoryPOList ); List<ProductTypeVO> productTypeVOList = new ArrayList<>(); // 用于存放商品类型列表(前端的) // 04 数据整合 for (ProductCategoryPO productCategoryPO : productCategoryPOList) { ProductTypeVO productTypeVO = new ProductTypeVO(); // 创建临时商品类型(前端的) productTypeVO.setType(productCategoryPO.getCategoryType()); productTypeVO.setName(productCategoryPO.getCategoryName()); List<ProductInfoVO> productInfoVOList = new ArrayList<>(); // 用于存放啥商品信息列表(前端的) for (ProductInfoPO productInfoPO : productInfoPOList) { if (productInfoPO.getCategoryType().equals(productCategoryPO.getCategoryType())) { ProductInfoVO productInfoVO = new ProductInfoVO(); BeanUtils.copyProperties(productInfoPO, productInfoVO); productInfoVOList.add(productInfoVO); } } productTypeVO.setProductInfoVOList(productInfoVOList); // 将商品信息存放到商品类目列表中(前端的) productTypeVOList.add(productTypeVO); // 将商品类目存放到商品类目列表中(前端的) } return ResultUtil.success(productTypeVOList); } }