🧸安清h:个人主页
🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】
🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。
目录
🚀1.加入购物车-数据创建
🚀2.加入购物车-实体类
🚀3.加入购物车-持久层
✨3.1规划需要执行的SQL语句
✨3.2设计接口和抽象方法
✨3.3 SQL映射
🚀4.加入购物车-业务层
✨4.1规划异常
✨4.2接口和抽象方法的设计
✨4.3实现接口
🚀5.加入购物车-控制层
✨5.2设计请求
✨5.3处理请求
🚀6.加入购物车-前端页面
🎯1.显示购物车列表- 持久层
✨1.1规划SQL语句
✨1.2构建VO类
✨1.3设计接口和抽象方法
✨1.4配置SQL映射
🎯2.显示购物车列表- 业务层
🎯3.显示购物车列表- 控制层
✨3.1设计请求
✨3.2处理请求
🎯4.显示购物车列表- 前端页面
🎃1. 增加购物车商品数量-持久层
✨1.1规划需要执行的SQL语句
✨1.2设计接口和抽象方法
✨1.3配置SQL映射
🎃 2.增加购物车商品数量-业务层
✨2.1规划异常
✨2.2设计接口和抽象方法
✨ 2.3实现方法
🎃 3.增加购物车商品数量-控制层
✨3.1处理异常
✨3.2设计请求
✨3.3处理请求
🎃 4.增加购物车商品数量-前端页面
🚀1.加入购物车-数据创建
CREATE TABLE t_cart (
cid INT AUTO_INCREMENT COMMENT '购物车数据id',
uid INT NOT NULL COMMENT '用户id',
pid INT NOT NULL COMMENT '商品id',
price BIGINT COMMENT '加入时商品单价',
num INT COMMENT '商品数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
num:当用户重复添加商品时,只修改num就可以了,无需再重复添加商品。
🚀2.加入购物车-实体类
public class Cart extends BaseEntity{
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
。。。。。。
}
🚀3.加入购物车-持久层
✨3.1规划需要执行的SQL语句
1.向购物车表中插入数据。
insert into t_cart values(值列表)
2.当当前的商品已经在购物车中存在,则直接更新num的数量即可。
update t_cart set num=? where cid=?
3.在插入或更新具体执行那个语句,取决于数据库中是否有当前的这个购物车商品的数据,得去查询才能查询。对当前的用户的pid进行查询,加上uid=?,而不是对当前的整张表进行查询。
select * from t_cart where pid=? and uid=?
✨3.2设计接口和抽象方法
创建一个CartMapper接口持久层的文件。
/**
* 插入购物车数据
* @param cart 购物车数据
* @return 受影响的行数
* 插入时最好放在一个对象中传递,所以用Cart参数列表
* 插入后在业务层可能调用,需要有返回值判断能否插入成功
*/
Integer insert(Cart cart);
/**
* 更新购物车某件商品的数量
* @param cid 购物数据id
* @param num 更新的数量
* @param modifiedUser 修改人
* @param modifiedTime 修改时间
* @return 受影响的行数
* 更新时涉及到修改人和修改时间,除了cid外,还需要知道数量num
*/
Integer updateNumByCid(Integer cid, Integer num, String modifiedUser, Date modifiedTime);
/**
* 根据用户的id和商品的id来查询购物车中的数据
* @param uid 用户id
* @param pid 商品id
*/
Cart findByUidAndPid(Integer uid,Integer pid);
✨3.3 SQL映射
1.创建一个CartMapper.xml映射文件,添加以上三个抽象方法的SQL映射。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.CartMapper">
<resultMap id="CartEntityMap" type="com.cy.store.entity.Cart">
<id property="cid" column="cid"/>
<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>
<insert id="insert" useGeneratedKeys="true" keyProperty="cid">
insert into t_cart (uid, pid, price, num, created_user, created_time, modified_user, modified_time)
values (#{uid}, #{pid}, #{price}, #{num}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
</insert>
<update id="updateNumByCid">
update t_cart set num=#{num},modified_user=#{modifiedUser},modified_time=#{modifiedTime}
where cid=#{cid}
</update>
<select id="findByUidAndPid" resultMap="CartEntityMap">
select * from t_cart where pid=#{pid} and uid=#{uid}
</select>
</mapper>
2.进行测试
@SpringBootTest
public class CartMapperTests {
@Autowired
private CartMapper cartMapper;
@Test
public void insert(){
Cart cart = new Cart();
cart.setNum(3);
cart.setPid(10000002);
cart.setUid(6);
cartMapper.insert(cart);
}
@Test
public void updateNumByCid(){
cartMapper.updateNumByCid(1,6,"小明",new Date());
}
@Test
public void findByUidAndPid(){
Cart cart = cartMapper.findByUidAndPid(6,10000002);
System.err.println(cart);
}
}
🚀4.加入购物车-业务层
✨4.1规划异常
1.插入数据时可能产生异常:InsertException。
2.更新数据时可能产生异常:UpdateException。
✨4.2接口和抽象方法的设计
分析:
1.首先要进行商品的查询,这里面通过findByUidAndPid方法必须要传递过来的是uid和pid的字段。
2.假设拿到后,就要对原有的数据进行更新,需要传递的字段有:cid,num,username。
/**
* 将商品添加到购物车中
* @param uid 用户id
* @param pid 商品id
* @param amount 新增数量
* @param username 用户名(修改者)
*/
void addToCart(Integer uid,Integer pid,Integer amount,String username);
✨4.3实现接口
1.创建一个CartServiceImpl的实现类。
@Service
public class CartServiceImpl implements ICartService {
//购物车的业务层依赖于购物车的持久层和商品的持久层
@Autowired
private CartMapper cartMapper;
@Autowired
//购物车中的一些定义的字段实际上是从商品表中分离出来的
private ProductMapper productMapper;
@Override
public void addToCart(Integer uid, Integer pid, Integer amount, String username) {
//查询当前要添加的购物车是否在表中已存在
Cart result = cartMapper.findByUidAndPid(uid,pid);
Date date = new Date();
if(result == null){ //表示这个商品从来没有被添加到购物车中,则进行新增操作
//创建一个Cart对象
Cart cart = new Cart();
//补全数据:首先补全参数传递过来的数据
cart.setPid(pid);
cart.setUid(uid);
//这里的amount暂且理解为在前端加好,把总数传递过来
cart.setNum(amount);
//补全价格:来自于商品中的数据
Product product = productMapper.findById(pid);
cart.setPrice(product.getPrice());
//补全四日志
cart.setCreatedUser(username);
cart.setCreatedTime(date);
cart.setModifiedUser(username);
cart.setModifiedTime(date);
//执行数据的插入操作
Integer rows = cartMapper.insert(cart);
if(rows != 1){
throw new InsertException("插入数据时产生未知的异常");
}
}else{ //表示当前商品已经存在于购物车中,则更新这条数据的num值
Integer num = result.getNum()+amount;
Integer cid = result.getCid();
Integer rows = cartMapper.updateNumByCid(cid,num,username,date);
if(rows != 1){
throw new UpdateException("更新时产生未知的异常");
}
}
}
}
2. 再创建对应的测试类CartServiceTests。
@SpringBootTest
public class CartServiceTests {
@Autowired
private ICartService cartService;
@Test
public void addToCart(){
cartService.addToCart(6,10000013,2,"北伐不成功不改名");
}
}
🚀5.加入购物车-控制层
1.没有需要处理的异常。
✨5.2设计请求
请求路径:/carts/add_to_cart
请求方式:POST
请求数据:Integer pid,Integer amount,HttpSession session
响应结果:JsonResult<Void>
✨5.3处理请求
1.创建一个CartController类,具体代码如下:
@RequestMapping("carts")
@RestController
public class CartController extends BaseController{
@Autowired
private ICartService cartService;
@RequestMapping("add_to_cart")
public JsonResult<Void> addToCart(Integer pid, Integer amount, HttpSession session){
cartService.addToCart(getuidFromSession(session),pid,amount,getUsernameFromSession(session));
return new JsonResult<>(OK);
}
}
2.登录后访问:http://localhost:8080/carts/add_to_cart?pid=10000003&amount=1
🚀6.加入购物车-前端页面
在product.html页面给【加入购物车】按钮添加点击事件,并发送ajax请求。
$("#btn-add-to-cart").click(function (){
$.ajax({
url:"/carts/add_to_cart",
type:"POST",
data:{
"pid":id,
"amount":$("#num").val()
},
dataType:"JSON",
success:function (json){
if(json.state==200){
alert("加入购物车成功");
}else{
alert("加入购物车失败");
}
},
error:function (xhr){
alert("加入购物车时产生未知的异常"+xhr.message);
}
});
});
在ajax函数中data参数的数据设置的方式:
- data:$("form表单选择").serialize()。适合要么就是可以全部选择的,或者手动输入的串的类型。当参数过多并且在同一个字符串中。
- data:new FormData($("form表单选择")[0])。只适用提交文件,其他形式提交不了。
- data:"username=Tom"。适合参数值固定并且参数值列表有限,可以进行手动拼接。
let user = "tom"; data:"username="+user
- 适用JSON格式提交数据:
data:{ "username":"tom", "age":18, "sex":0 }
🎯1.显示购物车列表- 持久层
✨1.1规划SQL语句
分析上图可知,红线框住的图片和商品标题来自于product表,而蓝线框住的应该属于 cart表,单价和数量应该是从购物车中传过来的,再根据两者计算总金额。由于数据来自于两张表中的部分字段,所以要用到关联查询。
#多表查询如果字段不重复则不需要显式声明字段属于哪张表
select cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.image,
t_product.title,
t_product.price as real
from t_cart left join t_product on t_cart.pid=t_product.id
where uid=#{uid}
order by t_cart.createdTime DESC;
✨1.2构建VO类
VO:Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接收,POJO实体类不能包含多表查询出来的结果。解决方案:重新构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这样的对象称之为值对象。
在com.cy.store下新建一个包VO,在VO包里创建CartVO类。
//购物车数据的VO类(Value Object)值对象
public class CartVO implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String title;
private String image;
private Long realPrice;
......
}
✨1.3设计接口和抽象方法
在CartMapper中编写如下代码:
List<CartVO> findVOByUid(Integer uid);
✨1.4配置SQL映射
<select id="findVOByUid" resultType="com.cy.store.Vo.CartVO">
select cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.image,
t_product.title,
t_product.price as realPrice
from t_cart left join t_product on t_cart.pid=t_product.id
where uid=#{uid}
order by t_cart.createdTime DESC
</select>
单元测试
@Test
public void findVOByUid(){
System.out.println(cartMapper.findVOByUid(6));
}
🎯2.显示购物车列表- 业务层
1.先编写业务层的接口方法。
List<CartVO> getVOByUid(Integer uid);
2.在实现类中实现方法。
@Override
public List<CartVO> getVOByUid(Integer uid) {
List<CartVO> list = cartMapper.findVOByUid(uid);
return list;
}
🎯3.显示购物车列表- 控制层
✨3.1设计请求
请求路径:/carts/(只要发一个carts就可以把列表返回,不需要carts下的什么)
请求方式:GET
请求数据:HttpSession session
响应结果:JsonResult<List<CartVO>>
✨3.2处理请求
1.实现请求处理方法的代码编写。
@RequestMapping({"/",""})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session){
List<CartVO> data = cartService.getVOByUid(getuidFromSession(session));
return new JsonResult<>(OK,data);
}
2.先登录再进行功能测试,访问http://localhost:8080/carts。
🎯4.显示购物车列表- 前端页面
要把cart.html页面通过向Controller层中的getVOByUid方法发送请求就可以返回所有的数据。
1.先注释掉以下代码:
<script src="../js/cart.js" type="text/javascript" charset="utf-8"></script>
2.用户一打开页面就自动发送请求,请求数据。读取form表单,对它的结构做一个了解,因为要把数据显示在form表单中。
- action="orderConfirm.html"
- tbody标签的id="cart-list"属性,自动加载的内容需要体现在tbody中。
- type="button":结算按钮的submit改成button,后续需要传数据。
3.ready()函数来完成自动的ajax请求的提交和处理。
<script type="text/javascript">
$(document).ready(function () {
showCartList();
})
;
//展示购物车列表数据
function showCartList() {
$("#cart-list").empty();
$.ajax({
url: "/carts",
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
for (var i = 0; i < list.length; i++) {
//用户所拿到的是一个list集合,在这个list集合中封装的是cartVO对象,在这里先拿到这个list集合
let tr = '<tr>\n' +
'<td>\n' +
//在这里给复选框一个cid值,在往后点击结算的时候会把这个cid值传递给下个页面
//把这个数据提交给另一个页面是以参数的形式提交,所以这个表单一定要有内部属性
'<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />\n' +
'</td>\n' +
'<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' +
'<td>#{title}#{msg}</td>\n' +
'<td>¥<span id="goodsPrice#{cid}">#{singlePrice}</span></td>\n' +
'<td>\n' +
'<input type="button" value="-" class="num-btn" οnclick="reduceNum(1)" />\n' +
'<input id="goodsCount#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">\n' +
'<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid})" />\n' +
'</td>\n' +
'<td><span id="goodsCast#{cid}">#{totalPrice}</span></td>\n' +
'<td>\n' +
'<input type="button" οnclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />\n' +
'</td>\n' +
'</tr>';
tr = tr.replaceAll(/#{cid}/g, list[i].cid);
tr = tr.replaceAll(/#{image}/g, list[i].image);
tr = tr.replaceAll(/#{title}/g, list[i].title);
tr = tr.replaceAll(/#{singlePrice}/g, list[i].realPrice);
tr = tr.replaceAll(/#{num}/g, list[i].num);
tr = tr.replaceAll(/#{totalPrice}/g, list[i].realPrice * list[i].num);
if (list[i].realPrice < list[i].price) {
tr = tr.replace(/#{msg}/g, "比加入时降价" + (list[i].price - list[i].realPrice) + "元");
} else {
tr = tr.replace(/#{msg}/g, "");
}
$("#cart-list").append(tr);
}
},
error: function (xhr) {
alert("加载购物车列表数据时产生未知的异常"+xhr.status);
}
});
}
</script>
🎃1. 增加购物车商品数量-持久层
✨1.1规划需要执行的SQL语句
1.执行更新t_cart表记录的num的值,无需重复开发。
update t_cart set num=#{num},modified_time={modifiedTime},modified_user=#{modifiedUser} where cid=#{cid}
2.根据cid查询购物车的这条记录是否存在。
select * from t_cart where cid=?
✨1.2设计接口和抽象方法
Cart findByCid(Integer cid);
✨1.3配置SQL映射
<select id="findByCid" resultMap="CartEntityMap">
select * from t_cart where cid=#{cid}
</select>
编写单元测试。
@Test
public void findByCid(){
System.out.println(cartMapper.findByCid(2));
}
🎃 2.增加购物车商品数量-业务层
✨2.1规划异常
1.在更新时会产生更新异常。
2.查询到的数据是否有访问权限。
3.查询的数据不存在,抛出:CartNotFoundException异常。
public class CartNotFoundException extends ServiceException{
public CartNotFoundException() {
super();
}
public CartNotFoundException(String message) {
super(message);
}
public CartNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public CartNotFoundException(Throwable cause) {
super(cause);
}
protected CartNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
✨2.2设计接口和抽象方法
/**
* 更新用户的购物车数据
* @param cid
* @param uid
* @param username
* @return 增加成功后新的数量
*/
Integer addNum(Integer cid,Integer uid,String username);
✨ 2.3实现方法
@Override
public Integer addNum(Integer cid, Integer uid, String username) {
Cart result = cartMapper.findByCid(cid);
if(result == null){
throw new CartNotFoundException("数据不存在");
}
if(!result.getUid().equals(uid)){
throw new AccessDeniedException("数据非法访问");
}
Integer num = result.getNum()+1;
Integer rows = cartMapper.updateNumByCid(cid,num,username,new Date());
if(rows != 1){
throw new UpdateException("更新时产生异常");
}
//返回新的购物车总量
return num;
}
🎃 3.增加购物车商品数量-控制层
✨3.1处理异常
else if(e instanceof CartNotFoundException) {
result.setState(4007);
result.setMessage("购物车数据不存在的异常");
}
✨3.2设计请求
请求路径:/carts/{cid}/num/add
请求方式:POST
请求数据:Integer cid,HttpSession session
响应结果:JsonResult<Integer>
✨3.3处理请求
@RequestMapping("{cid}/num/add")
public JsonResult<Integer> addNum(Integer cid,HttpSession session){
Integer data = cartService.addNum(cid,getuidFromSession(session),getUsernameFromSession(session));
return new JsonResult<>(OK,data);
}
先登录在访问url地址对应的地址。
🎃 4.增加购物车商品数量-前端页面
1.前面已经在onclick中改过里面的内容,所以无需重复修改了,但是需要重新编写addNum()以确保点击后能够增加数量。
<input class="num-btn" type="button" value="+" οnclick="addNum(#{cid})" />
function addNum(cid){
$.ajax({
url: "/carts/"+cid+"/num/add",
type: "POST",
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//先拿到数量展示的id,
$("#goodsCount"+cid).val(json.data)
//由于price的值不是放在val控件上,也不是放在某一个属性上,通过html来拿,
//html就是拿到它标签内部的这个东西,如果内部是个串拿到的就是个串
//获取某个标签内部的内容:文本、标签
//因为刚好这个内容即:singlePrice作为一个子内容放在了开始和结束的中间
let price = $("#goodsPrice"+cid).html();
let totalPrice = price * json.data;
$("#goodsCast"+cid).html(totalPrice);
} else {
alert("增加购物车商品数量失败")
}
},
error: function (xhr) {
alert("增加购物车商品数量时产生未知的异常!"+xhr.message);
}
});
}