增加商品类别
Dao层
ProductCategoryDao接口
/**
*
*
* @Title: batchInsertProductCategory
*
* @Description: 批量增加roductCategory
*
* @param productCategoryList
*
* @return: int
*/
int batchInsertProductCategory(List<ProductCategory> productCategoryList);
ProductCategoryDao SQL映射文件
<insert id="batchInsertProductCategory" parameterType="java.util.List">
INSERT INTO
tb_product_category(
product_category_name,
product_category_desc,
priority,
create_time,
last_edit_time,
shop_id)
VALUES
<foreach collection="list" item="productCategory" index="index" separator=",">
(
#{productCategory.productCategoryName},
#{productCategory.productCategoryDesc},
#{productCategory.priority},
#{productCategory.createTime},
#{productCategory.lastEditTime},
#{productCategory.shopId}
)
</foreach>
</insert>
单元测试
/**
* 测试删除
*
*/
@Test
public void testCDeleteProductCategory() throws Exception {
long shopId = 1;
List<ProductCategory> productCategoryList = productCategoryDao.selectProductList(shopId);
for (ProductCategory pc : productCategoryList) {
if ("商品类别1".equals(pc.getProductCategoryName()) || "商品类别2".equals(pc.getProductCategoryName())) {
int effectedNum = productCategoryDao.deleteProductCategory(pc.getProductCategoryId(),
shopId);
assertEquals(1, effectedNum);
}
}
}
单元测试OK。
## Service层
ProductCategoryExecution DTO类的开发
我们需要增加操作的状态及数量等信息,因此单独的Domain类已经无法满足需求了,因此我们使用DTO来扩展实体类的功能
package com.artisan.o2o.dto;
import java.util.List;
import com.artisan.o2o.entity.ProductCategory;
import com.artisan.o2o.enums.ProductCategoryStateEnum;
/**
*
*
* @ClassName: ProductCategoryExecution
*
* @Description: 封装操作ProductCategory的返回结果,包括操作状态和ProductCategory信息
*
* @author: Mr.Yang
*
* @date: 2018年6月21日 上午12:17:07
*/
public class ProductCategoryExecution {
private int state;
private String stateInfo;
// 因为是批量操作,所以使用List
private List<ProductCategory> productCategoryList;
private int count;
/**
*
*
* @Title:ProductCategoryExecution
*
* @Description:空的构造函数
*/
public ProductCategoryExecution() {
super();
}
/**
*
*
* @Title:ProductCategoryExecution
*
* @Description:操作成功的时候使用的构造函数,返回操作状态和ProductCategory集合
*
* @param productCategoryStateEnum
* @param productCategoryList
* @param count
*/
public ProductCategoryExecution(ProductCategoryStateEnum productCategoryStateEnum, List<ProductCategory> productCategoryList, int count) {
this.state = productCategoryStateEnum.getState();
this.stateInfo = productCategoryStateEnum.getStateInfo();
this.productCategoryList = productCategoryList;
this.count = count;
}
/**
*
*
* @Title:ProductCategoryExecution
*
* @Description:操作失败的时候返回的信息,仅包含状态和状态描述即可
*
* @param productCategoryStateEnum
*/
public ProductCategoryExecution(ProductCategoryStateEnum productCategoryStateEnum) {
this.state = productCategoryStateEnum.getState();
this.stateInfo = productCategoryStateEnum.getStateInfo();
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public List<ProductCategory> getProductCategoryList() {
return productCategoryList;
}
public void setProductCategoryList(List<ProductCategory> productCategoryList) {
this.productCategoryList = productCategoryList;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
ProductCategoryStateEnum 增加几个标识
SUCCESS(1, "操作成功"), INNER_ERROR(-1001, "操作失败"), NULL_SHOP(-1002, "Shop信息为空"), EMPETY_LIST(-1003, "请输入商品目录信息");
封装特定异常类
批量添加,这里我们使用事务控制
package com.artisan.o2o.exception;
/**
*
*
* @ClassName: ProductCategoryOperationException
*
* @Description: 继承RuntimeException,便于异常时候的回滚。 保持所有的操作在一个事务中。
*
* 这样在标注了@Transactional事务的方法中,出现了异常,才会回滚数据。
*
* 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring
* 将回滚事务;除此之外,Spring 不会回滚事务。
*
*
* @author: Mr.Yang
*
* @date: 2018年6月21日 上午12:22:44
*/
public class ProductCategoryOperationException extends RuntimeException {
private static final long serialVersionUID = 6500682256313143297L;
public ProductCategoryOperationException(String message) {
super(message);
}
}
ProductCategoryService接口
/**
*
*
* @Title: addProductCategory
*
* @Description: 批量插入ProductCategory
*
* @param productCategoryList
* @throws ProductCategoryOperationException
*
* @return: ProductCategoryExecution
*/
ProductCategoryExecution addProductCategory(List<ProductCategory> productCategoryList) throws ProductCategoryOperationException;
ProductCategoryServiceImpl实现类
/**
* 使用@Transactional控制事务
*/
@Override
@Transactional
public ProductCategoryExecution addProductCategory(List<ProductCategory> productCategoryList) throws ProductCategoryOperationException {
// 非空判断
if (productCategoryList != null && productCategoryList.size() > 0) {
try {
// 批量增加ProductCategory
int effectNum = productCategoryDao.batchInsertProductCategory(productCategoryList);
if (effectNum > 0) {
return new ProductCategoryExecution(ProductCategoryStateEnum.SUCCESS, productCategoryList, effectNum);
} else {
return new ProductCategoryExecution(ProductCategoryStateEnum.INNER_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
throw new ProductCategoryOperationException("batchAddProductCategory Error:" + e.getMessage());
}
} else {
return new ProductCategoryExecution(ProductCategoryStateEnum.EMPETY_LIST);
}
}
单元测试
@Test
public void testAddProductCategory() {
ProductCategory productCategory1 = new ProductCategory();
productCategory1.setProductCategoryName("ProductCategoryTest3");
productCategory1.setProductCategoryDesc("ProductCategoryTest3-desc");
productCategory1.setPriority(300);
productCategory1.setCreateTime(new Date());
productCategory1.setLastEditTime(new Date());
productCategory1.setShopId(5L);
ProductCategory productCategory2 = new ProductCategory();
productCategory2.setProductCategoryName("ProductCategoryTest4");
productCategory2.setProductCategoryDesc("ProductCategoryTest4-desc");
productCategory2.setPriority(600);
productCategory2.setCreateTime(new Date());
productCategory2.setLastEditTime(new Date());
productCategory2.setShopId(5L);
List<ProductCategory> productCategoryList = new ArrayList<ProductCategory>();
productCategoryList.add(productCategory1);
productCategoryList.add(productCategory2);
ProductCategoryExecution productCategoryExecution = productCategoryService.addProductCategory(productCategoryList);
Assert.assertEquals(1, productCategoryExecution.getState());
Assert.assertEquals(2, productCategoryExecution.getProductCategoryList().size());
}
Controller层
ProductCategoryController增加addProductCategory方法
/**
*
*
* @Title: addProductCategory
*
* @Description: 添加商铺目录 ,使用@RequestBody接收前端传递过来的productCategoryList
*
* @param productCategoryList
* @param request
*
* @return: Map<String,Object>
*/
@RequestMapping(value = "/addproductcategory", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> addProductCategory(@RequestBody List<ProductCategory> productCategoryList, HttpServletRequest request) {
Map<String, Object> modelMap = new HashMap<String, Object>();
if (productCategoryList != null && productCategoryList.size() > 0) {
// 从session中获取shop的信息
Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
if (currentShop != null && currentShop.getShopId() != null) {
// 为ProductCategory设置shopId
for (ProductCategory productCategory : productCategoryList) {
productCategory.setShopId(currentShop.getShopId());
}
try {
// 批量插入
ProductCategoryExecution pce = productCategoryService.addProductCategory(productCategoryList);
if (pce.getState() == ProductCategoryStateEnum.SUCCESS.getState()) {
modelMap.put("success", true);
// 同时也将新增成功的数量返回给前台
modelMap.put("effectNum", pce.getCount());
} else {
modelMap.put("success", false);
modelMap.put("errMsg", pce.getStateInfo());
}
} catch (ProductCategoryOperationException e) {
e.printStackTrace();
modelMap.put("success", false);
modelMap.put("errMsg", e.getMessage());
return modelMap;
}
} else {
modelMap.put("success", false);
modelMap.put("errMsg", ProductCategoryStateEnum.NULL_SHOP.getStateInfo());
}
} else {
modelMap.put("success", false);
modelMap.put("errMsg", "至少输入一个店铺目录信息");
}
return modelMap;
}
单元测试
待前端页面完成,一并测试
View层
productcategorymanage.js
$(function () {
// 后台从session中获取shop的信息,这里就不传shopId了
//var shopId = getQueryString("shopId");
//var productCategoryURL = '/o2o/shopadmin/getproductcategorybyshopId?shopId=' + shopId;
var getProductCategoryURL = '/o2o/shopadmin/getproductcategorybyshopId';
var addProductCategoryURL = '/o2o/shopadmin/addproductcategory';
// 调用getProductCategoryList,加载数据
getProductCategoryList();
function getProductCategoryList() {
$.getJSON(getProductCategoryURL,
function(data) {
if (data.success) {
var dataList = data.data;
$('.product-categroy-wrap').html('');
var tempHtml = '';
dataList
.map(function(item, index) {
tempHtml += ''
+ '<div class="row row-product-category now">'
+ '<div class="col-33 product-category-name">'
+ item.productCategoryName
+ '</div>'
+ '<div class="col-33">'
+ item.priority
+ '</div>'
+ '<div class="col-33"><a href="#" class="button delete" data-id="'
+ item.productCategoryId
+ '">删除</a></div>'
+ '</div>';
});
$('.product-categroy-wrap').append(tempHtml);
}
});
}
// 新增按钮的点击事件
$('#new').click(
function(){
// 新增数据 以 temp 为标识,便于和库表中的数据区分开来
var tempHtml = '<div class="row row-product-category temp">'
+ '<div class="col-33"><input class="category-input category" type="text" placeholder="分类名"></div>'
+ '<div class="col-33"><input class="category-input priority" type="number" placeholder="优先级"></div>'
+ '<div class="col-33"><a href="#" class="button delete">删除</a></div>'
+ '</div>';
$('.product-categroy-wrap').append(tempHtml);
});
$('#submit').click(function() {
// 通过temp 获取新增的行
var tempArr = $('.temp');
// 定义数组接收新增的数据
var productCategoryList = [];
tempArr.map(function(index, item) {
var tempObj = {};
tempObj.productCategoryName = $(item).find('.category').val();
tempObj.priority = $(item).find('.priority').val();
if (tempObj.productCategoryName && tempObj.priority) {
productCategoryList.push(tempObj);
}
});
$.ajax({
url : addProductCategoryURL,
type : 'POST',
// 后端通过 @HttpRequestBody直接接收
data : JSON.stringify(productCategoryList),
contentType : 'application/json',
success : function(data) {
if (data.success) {
$.toast('新增【' + data.effectNum + '】条成功!');
// 重新加载数据
getProductCategoryList();
} else {
$.toast(data.errMsg);
}
}
});
});
});
前后端联调
前端页面debug, 后端也可以加入断点,以debug的方式开启tomcat,逐步调测
效果如下:
删除商品类别
Dao层
ProductCategoryDao接口增加接口方法
/**
*
*
* @Title: deleteProductCategory
*
* @Description: 删除特定shop下的productCategory
*
* @param productCategoryId
* @param shopId
*
* @return: int
*/
int deleteProductCategory(@Param("productCategoryId") Long productCategoryId, @Param("shopId") Long shopId);
ProductCategoryDao SQL映射文件
<delete id="deleteProductCategory">
DELETE FROM
tb_product_category
WHERE
product_category_id = #{productCategoryId}
and
shop_id = #{shopId}
</delete>
闭环的单元测试
这里我们使用Junit 4.11里及其以后的版本中增加的@FixMethodOrder注解来实现. 具体见代码注释。
package com.artisan.o2o.dao;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import com.artisan.o2o.BaseTest;
import com.artisan.o2o.entity.ProductCategory;
/**
*
*
* @ClassName: ProductCategoryTest
*
* @Description: Junit 4.11里增加了指定测试方法执行顺序的特性 .
*
* 测试类的执行顺序可通过对测试类添加注解@FixMethodOrder(value) 来指定,其中value 为执行顺序
*
* 三种执行顺序可供选择:
*
* 默认(MethodSorters.DEFAULT),
* 默认顺序由方法名hashcode值来决定,如果hash值大小一致,则按名字的字典顺序确定
* 由于hashcode的生成和操作系统相关
* (以native修饰),所以对于不同操作系统,可能会出现不一样的执行顺序,在某一操作系统上,多次执行的顺序不变
*
* 按方法名( MethodSorters.NAME_ASCENDING)【推荐】,
* 按方法名称的进行排序,由于是按字符的字典顺序,所以以这种方式指定执行顺序会始终保持一致;
* 不过这种方式需要对测试方法有一定的命名规则,如 测试方法均以testNNN开头(NNN表示测试方法序列号 001-999)
*
* JVM(MethodSorters.JVM)
* 按JVM返回的方法名的顺序执行,此种方式下测试方法的执行顺序是不可预测的,即每次运行的顺序可能都不一样
*
*
* @author: Mr.Yang
*
* @date: 2018年6月21日 下午11:55:45
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProductCategoryTest extends BaseTest {
@Autowired
ProductCategoryDao productCategoryDao;
@Test
public void testB_SelectProductCategoryList() {
long shopId = 5L;
List<ProductCategory> productCategories = productCategoryDao.selectProductCategoryList(shopId);
// shopId = 5 有2条测试数据,期望list中有2条
assertEquals(2, productCategories.size());
// SQL中按照权重排序, product1 priority 99 ,期望第一条数据是 product1
assertEquals("product1", productCategories.get(0).getProductCategoryName());
for (ProductCategory productCategory : productCategories) {
System.out.println(productCategory.toString());
}
productCategories = productCategoryDao.selectProductCategoryList(6L);
assertEquals(0, productCategories.size());
}
@Test
public void testA_BatchInsertProductCategory() {
ProductCategory productCategory1 = new ProductCategory();
productCategory1.setProductCategoryName("product1");
productCategory1.setProductCategoryDesc("product1_desc");
productCategory1.setPriority(99);
productCategory1.setCreateTime(new Date());
productCategory1.setLastEditTime(new Date());
productCategory1.setShopId(5L);
ProductCategory productCategory2 = new ProductCategory();
productCategory2.setProductCategoryName("product2");
productCategory2.setProductCategoryDesc("product2_desc");
productCategory2.setPriority(98);
productCategory2.setCreateTime(new Date());
productCategory2.setLastEditTime(new Date());
productCategory2.setShopId(5L);
List<ProductCategory> productCategoryList = new ArrayList<ProductCategory>();
productCategoryList.add(productCategory1);
productCategoryList.add(productCategory2);
int effectNum = productCategoryDao.batchInsertProductCategory(productCategoryList);
Assert.assertEquals(2, effectNum);
}
@Test
public void testC_DeleteProductCategory() {
// 查询出来shopId=5的商铺下面全部的商品目录
List<ProductCategory> productCategoryList = productCategoryDao.selectProductCategoryList(5L);
// 遍历循环删除
for (ProductCategory productCategory : productCategoryList) {
if ("product1".equals(productCategory.getProductCategoryName()) || "product2".equals(productCategory.getProductCategoryName())) {
int effectNum = productCategoryDao.deleteProductCategory(productCategory.getProductCategoryId(), 5L);
assertEquals(1, effectNum);
}
}
}
}
Servie层
接口
/**
*
*
* @Title: deleteProductCategory
*
* @Description: TODO 需要先将该商品目录下的商品的类别Id置为空,然后再删除该商品目录, 因此需要事务控制
*
* @param productCategoryId
* @param shopId
* @throws ProductCategoryOperationException
*
* @return: ProductCategoryExecution
*/
ProductCategoryExecution deleteProductCategory(long productCategoryId, long shopId) throws ProductCategoryOperationException;
接口实现
/**
* TODO 需要先将该商品目录下的商品的类别Id置为空,然后再删除该商品目录, 因此需要事务控制@Transactional
*/
@Override
@Transactional
public ProductCategoryExecution deleteProductCategory(long productCategoryId, long shopId) throws ProductCategoryOperationException {
// TODO 第一步 需要先将该商品目录下的商品的类别Id置为空
// 第二步 删除该商品目录
try {
int effectNum = productCategoryDao.deleteProductCategory(productCategoryId, shopId);
if (effectNum > 0) {
return new ProductCategoryExecution(ProductCategoryStateEnum.SUCCESS);
} else {
return new ProductCategoryExecution(ProductCategoryStateEnum.INNER_ERROR);
}
} catch (Exception e) {
throw new ProductCategoryOperationException(e.getMessage());
}
}
单元测试
@Test
public void testDeleteProductCategory() {
ProductCategoryExecution productCategoryExecution = productCategoryService.deleteProductCategory(26, 5);
Assert.assertEquals(1, productCategoryExecution.getState());
ProductCategoryExecution productCategoryExecution2 = productCategoryService.deleteProductCategory(27, 5);
Assert.assertEquals(1, productCategoryExecution2.getState());
}
Controller层
路由方法
/**
*
*
* @Title: remooveProductCategory
*
* @Description: 删除商品目录
*
* @param productCategoryId
* @param request
*
* @return: Map<String,Object>
*/
@RequestMapping(value = "/removeproductcategory", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> remooveProductCategory(Long productCategoryId, HttpServletRequest request) {
Map<String, Object> modelMap = new HashMap<String, Object>();
if (productCategoryId != null && productCategoryId > 0) {
// 从session中获取shop的信息
Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
if (currentShop != null && currentShop.getShopId() != null) {
try {
// 删除
Long shopId = currentShop.getShopId();
ProductCategoryExecution pce = productCategoryService.deleteProductCategory(productCategoryId, shopId);
if (pce.getState() == ProductCategoryStateEnum.SUCCESS.getState()) {
modelMap.put("success", true);
} else {
modelMap.put("success", false);
modelMap.put("errMsg", pce.getStateInfo());
}
} catch (ProductCategoryOperationException e) {
e.printStackTrace();
modelMap.put("success", false);
modelMap.put("errMsg", e.getMessage());
return modelMap;
}
} else {
modelMap.put("success", false);
modelMap.put("errMsg", ProductCategoryStateEnum.NULL_SHOP.getStateInfo());
}
} else {
modelMap.put("success", false);
modelMap.put("errMsg", "请选择商品类别");
}
return modelMap;
}
单元测试
前端完成后,一起测试
View层
productcategorymanage.js
增加如下代码
var deleteProductCategoryUrl = '/o2o/shopadmin/removeproductcategory';
// 一种是需要提交到后台的删除 now ,另外一种是 新增但未提交到数据库中的删除 temp
$('.product-categroy-wrap').on('click', '.row-product-category.now .delete',
function(e) {
var target = e.currentTarget;
$.confirm('确定么?', function() {
$.ajax({
url : deleteProductCategoryUrl,
type : 'POST',
data : {
productCategoryId : target.dataset.id,
},
dataType : 'json',
success : function(data) {
if (data.success) {
$.toast('删除成功!');
// 重新加载数据
getProductCategoryList();
} else {
$.toast('删除失败!');
}
}
});
});
});
$('.product-categroy-wrap').on('click', '.row-product-category.temp .delete',
function(e) {
$(this).parent().parent().remove();
});
联调
前端页面debug, 后端也可以加入断点,以debug的方式开启tomcat,逐步调测
效果如下:
这里写图片描述