83. 减去订单中的商品的库存
首先,应该在“管理商品数据”的持久层,添加“修改商品库存”的功能,需要执行的SQL语句大致是:
UPDATE
t_product
SET
num=#{num},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
id=#{id}
除此以外,还应该先获取原来的库存值,需要“根据商品id查询商品详情”的功能,该功能已经开发,则无需重复开发!
所以,在ProductMapper.java
接口中,需要添加:
Integer updateNumById(
@Param("id") Integer id,
@Param("num") Integer num,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
然后配置映射:
<!-- 修改商品的库存 -->
<!-- Integer updateNumById(
@Param("id") Integer id,
@Param("num") Integer num,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime) -->
<update id="updateNumById">
UPDATE
t_product
SET
num=#{num},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
id=#{id}
</update>
测试:
@Test
public void updateNumById() {
Integer id = 10000001;
Integer num = 50;
String modifiedUser = "ADMIN";
Date modifiedTime = new Date();
Integer rows = mapper.updateNumById(id, num, modifiedUser, modifiedTime);
System.err.println("rows=" + rows);
}
接下来,应该在“处理商品数据的业务层”实现“减少某商品的库存”的功能,则在IProductService
接口中添加抽象方法:
void reduceNum(Integer id, Integer amount, String username);
然后,在ProductServiceImpl
实现类,先添加私有的与持久层对应的updateNumById()
方法:
/**
* 修改商品的库存
* @param id 商品id
* @param num 新的库存值
* @param modifiedUser 修改执行人
* @param modifiedTime 修改时间
* @return 受影响的行数
*/
private void updateNumById(Integer id, Integer num,
String modifiedUser, Date modifiedTime) {
Integer rows = productMapper.updateNumById(id, num, modifiedUser, modifiedTime);
if (rows != 1) {
throw new UpdateException(
"更新商品库存时出现未知错误,请联系系统管理员");
}
}
然后,重写接口中的抽象方法:
public void reduceNum(Integer id, Integer amount, String username) {
// 根据id查询商品数据
// 判断查询结果是否为null
// 是:抛出ProductNotFoundException
// 取出查询结果中的原库存值
// 结合参数amount计算出新的库存值
// 判断新的库存值是否小于0
// 是:抛出ProductOutOfStockException
// 更新库存
}
代码:
@Override
public void reduceNum(Integer id, Integer amount, String username) {
// 根据id查询商品数据
Product result = findById(id);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出ProductNotFoundException
throw new ProductNotFoundException(
"尝试访问的商品数据不存在");
}
// 取出查询结果中的原库存值
// 结合参数amount计算出新的库存值
Integer newNum = result.getNum() - amount;
// 判断新的库存值是否小于0
if (newNum < 0) {
// 是:抛出ProductOutOfStockException
throw new ProductOutOfStockException(
"商品库存不足");
}
// 更新库存
updateNumById(id, newNum, username, new Date());
}
测试:
@Test
public void reduceNum() {
try {
Integer id = 10000001;
Integer amount = 8;
String username = "Hello";
service.reduceNum(id, amount, username);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getSimpleName());
System.err.println(e.getMessage());
}
}
84. 订单-创建订单-控制器
(a) 处理异常
需要处理ProductOutOfStockException
。
(b) 设计请求
请求路径:/orders/create
请求参数:Integer aid, Integer[] cids, HttpSession session
请求方式:POST
响应结果:JsonResult<Order>
(c) 处理请求
创建cn.tedu.store.controller.OrderController
控制器类,继承自BaseController
,添加@RestController
和@RequestMapping("orders")
注解,声明@Autowired private IOrderService orderService;
对象。
并在类中处理请求:
@RestController
@RequestMapping("orders")
public class OrderController extends BaseController {
@Autowired
private IOrderService orderService;
@RequestMapping("create")
public JsonResult<Order> create(Integer aid, Integer[] cids, HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
Order data = orderService.create(uid, username, aid, cids);
return new JsonResult<>(OK, data);
}
}
最后,在浏览器中测试:http://localhost:8080/orders/create?aid=42&cids=12&cids=13&cids=14&cids=15&cids=16
85. 订单-创建订单-前端页面
前端页面处理: 1. 第112行,<form>
必须添加id; 2. 第117行,收货地址的<select>
必须配置name="aid"
; 3. 第169行,“在线支付”按钮必须添加id; 4. JS代码的showAddressList()
中,生成列表项的<option>
必须配置value="??"
,且值必须是aid; 5. JS代码的showCartList()
中,生成页面代码中,每项数据必须添加<input type="hidden" name="cids" value="#{cid}" />
,且后续占位符必须被替换为真实的cid。
86. Spring AOP
AOP:面向切面(Aspect)编程;
AOP并不是Spring的特有的功能,只不过是Spring很好的支持了AOP。
在一个项目中,关于功能的处理流程是:
注册: 前端页面 --> 控制器层 --> 业务层 --> 持久层
登录: 前端页面 --> 控制器层 --> 业务层 --> 持久层
改密: 前端页面 --> 控制器层 --> 业务层 --> 持久层
头像: 前端页面 --> 控制器层 --> 业务层 --> 持久层
如果,需要在每个功能的处理过程中,增加一些新的操作,例如计算业务层的执行耗时,或添加某些日志等!如果按照传统模式来实现,则应该将相关的代码封装在某个方法中,然后,在业务层的各方法中均调用这个封装的方法即可!
AOP在思想,是假想在整个处理流程中可以存在切面,切面中可以有方法,只要将切面加在处理流程中,就可以使得整个处理流程过程中执行切面中的方法!
在开发AOP之前,需要添加aspectj-tools
和aspectj-weaver
这2个依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
使用AOP时,并不会对原有的处理业务的代码产生影响!首先,需要创建cn.tedu.store.aop.TimerAspect
切面类,并在类之前添加@Aspect
和@Component
注解:
@Aspect
@Component
public class TimerAspect {
}
然后,需要确定切面的作用方式,如果是添加在某个组件之前,后续需要使用@Before
注解,如果是添加在某个组件之后,后续需要使用@After
注解,如果是在某个组件之前和之后都需要执行,后续需要使用@Around
注解!所以,如果需求是“统计业务层的执行耗时”,则应该在业务层执行之前记录时间,并在执行之后也记录时间,对比2个时间,就能得到执行耗时,所以,此功能需要使用@Around
注解!
然后,还需要确定切面的连接点(作用于哪里),后续将通过表达式来设置!如果需要作用于业务层,可以将表达式设置为:
execution(* cn.tedu.store.service.impl.*.*(..))
// 无视返回值 impl包.所在类.所有方法(无视参数数量)
将以上注解中配置以上表达式,将注解添加在切面方法之前即可,关于切面方法的声明:
-
应该使用
public
权限; -
如果使用
@Around
注解,需要使用Object
作为返回值类型,表示业务层方法的返回值; -
方法名可以自定义;
-
如果使用
@Around
注解,需要添加ProceedingJoinPoint
作为参数,它是用于调用业务方法的!
所以,在切面类中添加切面方法:
@Aspect
@Component
public class TimerAspect {
@Around("execution(* cn.tedu.store.service.impl.*.*(..))")
public Object aaaaa(ProceedingJoinPoint pjp) {
return null;
}
}
最后,完成切面方法:
@Aspect
@Component
public class TimerAspect {
@Around("execution(* cn.tedu.store.service.impl.*.*(..))")
public Object aaaaa(ProceedingJoinPoint pjp) throws Throwable {
// 记录起始时间
long start = System.currentTimeMillis();
// 执行业务层的方法,例如UserServiceImpl中的reg()方法或login()方法
// 调用以下方法时,会出现异常,必须抛出,不可以try...catch
Object obj = pjp.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
System.err.println("耗时:" + (end - start) + "ms.");
// 必须返回以上proeceed()方法的返回值
// 否则,相当于login()、reg()等业务方法都不会返回值
return obj;
}
}