網(wǎng)站開發(fā)實施計劃與安排網(wǎng)絡(luò)推廣培訓(xùn)
Springboot寫電商系統(tǒng)(2)
- 1.新增收貨地址
- 1.創(chuàng)建t_addresss數(shù)據(jù)庫表
- 2.創(chuàng)建Address實體類
- 3.數(shù)據(jù)庫操作的持久層
- 1.接口寫抽象方法
- 2.xml寫方法映射sql
- 3.測試
- 4.前后數(shù)據(jù)交互的業(yè)務(wù)層
- 1.sql操作的異常拋出
- 2.交互方法的接口定義
- 3.接口的方法實現(xiàn)
- 4.測試
- 5.與前端交互的控制層
- 1.添加請求成功的異常響應(yīng)
- 2.處理請求
- 6.前端ajax請求
- 7.后端獲取省市區(qū)三級菜單
- 1.創(chuàng)建地址表
- 2.創(chuàng)建實體類
- 3.sql持久層
- 4.前后端數(shù)據(jù)操作的業(yè)務(wù)層
- 5.響應(yīng)前端的控制層
- 6.前端請求
- 2.刪除收貨地址
- 1.持久層
- 2.業(yè)務(wù)層
- 3.控制層
- 4.前端請求和渲染
- 3.顯示購物車列表
- 1.VO實體類
- 2.持久層
- 3.業(yè)務(wù)層
- 4.控制層
- 5.前端請求與渲染
- 4.補充知識點
1.新增收貨地址
1.創(chuàng)建t_addresss數(shù)據(jù)庫表
name是關(guān)鍵字,所以需要用``
CREATE TABLE t_address (aid INT AUTO_INCREMENT COMMENT '收貨地址id',uid INT COMMENT '歸屬的用戶id',`name` VARCHAR(20) COMMENT '收貨人姓名',province_name VARCHAR(15) COMMENT '省-名稱',province_code CHAR(6) COMMENT '省-行政代號',city_name VARCHAR(15) COMMENT '市-名稱',city_code CHAR(6) COMMENT '市-行政代號',area_name VARCHAR(15) COMMENT '區(qū)-名稱',area_code CHAR(6) COMMENT '區(qū)-行政代號',zip CHAR(6) COMMENT '郵政編碼',address VARCHAR(50) COMMENT '詳細地址',phone VARCHAR(20) COMMENT '手機',tel VARCHAR(20) COMMENT '固話',tag VARCHAR(6) COMMENT '標(biāo)簽',is_default INT COMMENT '是否默認(rèn):0-不默認(rèn),1-默認(rèn)',created_user VARCHAR(20) COMMENT '創(chuàng)建人',created_time DATETIME COMMENT '創(chuàng)建時間',modified_user VARCHAR(20) COMMENT '修改人',modified_time DATETIME COMMENT '修改時間',PRIMARY KEY (aid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
2.創(chuàng)建Address實體類
記得要繼承BaseEntity類(里面有四個字段)。
把除了BaseEntity里面的四個字段外的所有字段在這個實體類里添加,然后創(chuàng)建他們的get,set;equals和hashCode;toString。
實體類的字段名要符合駝峰命名,所以會有和數(shù)據(jù)庫字段名不一樣的,記得要在持久層做字段名的映射。
/**收貨地址額實體類*/
public class Address extends BaseEntity {private Integer aid;private Integer uid;private String name;private String provinceName;private String provinceCode;private String cityName;private String cityCode;private String areaName;private String areaCode;private String zip;private String address;private String phone;private String tel;private String tag;private Integer isDefault;/*** get,set* equals和hashCode* toString*/
}
3.數(shù)據(jù)庫操作的持久層
新增收獲地址之前要判斷一下這個uid用戶的收貨地址是否已經(jīng)有20個了,如果是就不能再新增;如果是0,那么用戶添加的第一個就要默認(rèn)地址賦值1,如果是1-20,那默認(rèn)地址就要賦值0。所以這里面設(shè)計到2個SQL語句:
insert into t_address (aid以外的所有字段) values (字段值)
select count(*) from t_address where uid=?
1.接口寫抽象方法
在接口AddressMapper里面寫上面兩個SQL語句的抽象方法,第一個傳遞參數(shù)address,第二個傳遞uid。
public interface AddressMapper {Integer insert (Address address);Integer countByUid(Integer uid);
}
2.xml寫方法映射sql
在AddressMapper.xml里面首先寫xml對mybatis和上面接口的映射。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.store.mapper.AddressMapper">
</mapper>
然后在resultMap標(biāo)簽里面寫表字段和實體字段的映射(這里寫名字不一樣的字段和自增的主鍵字段aid),type寫實體類的全路徑。
<resultMap id="AddressEntityMap" type="com.example.store.entity.Address"><id column="aid" property="aid"/><result column="province_name" property="provinceName"/><result column="province_code" property="provinceCode"/><result column="city_name" property="cityName"/><result column="city_code" property="cityCode"/><result column="area_name" property="areaName"/><result column="area_code" property="areaCode"/><result column="is_default" property="isDefault"/><result column="created_user" property="createdUser"/><result column="created_time" property="createdTime"/><result column="modified_user" property="modifiedUser"/><result column="modified_time" property="modifiedTime"/></resultMap>
最后寫上面2個方法的sql映射,id對應(yīng)接口里定義的方法名,有自增主鍵的要加上useGeneratedKeys和keyProperty,定義返回類型的加上resultType。
<insert id="insert" useGeneratedKeys="true" keyProperty="aid">INSERT INTO t_address (uid, `name`, province_name, province_code, city_name, city_code, area_name, area_code, zip,address, phone, tel,tag, is_default, created_user, created_time, modified_user, modified_time) VALUES (#{uid}, #{name}, #{provinceName}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},#{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault}, #{createdUser},#{createdTime}, #{modifiedUser}, #{modifiedTime})</insert><!--resultType="java.lang.Integer"不寫會報錯,因為Integer不是基本數(shù)據(jù)類型--><select id="countByUid" resultType="java.lang.Integer">select count(*) from t_address where uid=#{uid}</select>
3.測試
首先給AddressMapperTests添加兩個注解,一個是聲明是測試類最終不會打包的@SpringBootTest
,一個是可以運行的測試類@RunWith(SpringRunner.class)
;然后通過@Autowired
自動裝配Mapper,就可以使用mapper里面的方法了。
@SpringBootTest
@RunWith(SpringRunner.class)
public class AddressMapperTests {@Autowiredprivate AddressMapper addressMapper;@Testpublic void insert(){Address address=new Address();address.setUid(1);address.setName("zoe");address.setAddress("Los Angeles");addressMapper.insert(address);}@Testpublic void countByUid(){Integer count=addressMapper.countByUid(1);System.err.println(count);}
}
4.前后數(shù)據(jù)交互的業(yè)務(wù)層
拿到前端數(shù)據(jù)進行數(shù)據(jù)插入時會出現(xiàn)錯誤uid的用戶不存在,這個異常UsernameNotFoundException已存在。
當(dāng)用戶插入數(shù)據(jù)超過20條時不可以再添加,所以需要添加異常AddressCountLimitException。
插入操作中出現(xiàn)的異常InsertException已存在。
1.sql操作的異常拋出
需要添加一個AddressCountLimitException異常并繼承ServiceException ,然后重構(gòu)5個構(gòu)造方法。
public class AddressCountLimitException extends ServiceException {/**重寫ServiceException的所有構(gòu)造方法*/
}
2.交互方法的接口定義
在IAddressService 接口里面定義新增地址的方法addNewAddress,這里面從前端拿到的表單信息是address(這里面有name是收貨人),然后從session中拿到uid和username(這個將是修改人和創(chuàng)建人)。
public interface IAddressService {void addNewAddress(Integer uid, String username, Address address);
}
3.接口的方法實現(xiàn)
首先要給這個接口實現(xiàn)類添加注解@Service
,之后spring才能找到這個bean。
在AddressServiceImpl里面實現(xiàn)接口方法。業(yè)務(wù)層里面將會用到持久層里面定義的sql方法,所以將用到方法的mapper(接口)導(dǎo)入并用@Autowired
自動裝載。
這里用到了一個超參:用戶收獲地址的最大條數(shù),在application.properties
里面設(shè)置user.address.max-count=20
;然后在類里面通過注解將值賦值到變量maxCount
中。
@Service//交給spring管理,所以需要在類上加@Service
public class AddressServiceImpl implements IAddressService {@Autowiredprivate AddressMapper addressMapper;@Autowiredprivate UserMapper userMapper;@Value("${user.address.max-count}")private Integer maxCount;@Overridepublic void addNewAddress(Integer uid, String username, Address address) {User result=userMapper.findByUid(uid);if(result==null||result.getIsDelete()==1){throw new UsernameNotFoundException("用戶數(shù)據(jù)不存在");}Integer count=addressMapper.countByUid(uid);if(count>=maxCount){throw new AddressCountLimitException("用戶收貨地址超出上限");}Integer isDefault=count==0?1:0;address.setIsDefault(isDefault);address.setUid(uid);address.setCreatedUser(username);address.setModifiedUser(username);address.setCreatedTime(new Date());address.setModifiedTime(new Date());Integer rows=addressMapper.insert(address);if (rows!=1){throw new InsertException("插入用戶收獲地址時發(fā)生未知異常");}}
}
4.測試
還是先添加測試的兩個注解@SpringBootTest
,@RunWith(SpringRunner.class)
,然后自動裝載@Autowired
業(yè)務(wù)層的接口,在測試方法里使用接口方法測試。
@SpringBootTest
@RunWith(SpringRunner.class)
public class AddressServiceTests {@Autowiredprivate IAddressService addressService;@Testpublic void addNewAddress(){Address address=new Address();address.setName("老王");addressService.addNewAddress(2,"zoe",address);}
}
5.與前端交互的控制層
1.添加請求成功的異常響應(yīng)
首先在業(yè)務(wù)層的時候有一個異常拋出,所以在控制層里面要把這個異常添加到響應(yīng)的異常里面,在BaseController里添加:
else if (e instanceof AddressCountLimitException) {result.setState(4003);result.setMessage("用戶的收貨地址超出上限的異常");
}
2.處理請求
首先AddressController要繼承BaseController。
然后添加注解@RequestMapping("addresses")
,@RestController
,用到了業(yè)務(wù)層的接口,所以用@Autowired
自動裝配,添加功能的響應(yīng)地址@RequestMapping("add_new_address")
,然后拿到前端數(shù)據(jù)進行處理。
@RequestMapping("addresses")
@RestController
public class AddressController extends BaseController{@Autowiredprivate IAddressService addressService;@RequestMapping("add_new_address")public JsonResult<Void>addNewAddress(Address address, HttpSession session){Integer uid=getUidFromSession(session);String username=getUsernameFromSession(session);addressService.addNewAddress(uid,username,address);return new JsonResult<>(OK);}
}
瀏覽器中輸入http://localhost:8080/addresses/add_new_address?name=tom&phone=987進行測試。
6.前端ajax請求
<script>$("#btn-add-new-address").click(function () {$.ajax({url: "/addresses/add_new_address",type: "POST",data: $("#form-add-new-address").serialize(),dataType: "JSON",success: function (json) {if (json.state == 200) {alert("新增收貨地址成功")} else {alert("新增收貨地址失敗")}},error: function (xhr) {alert("新增收貨地址時產(chǎn)生未知的異常!"+xhr.message);}});});</script>
然后就可以提交表單進行測試。
7.后端獲取省市區(qū)三級菜單
現(xiàn)在的三級菜單功能是寫在了前端的js里面,但實際業(yè)務(wù)中是要把這些數(shù)據(jù)寫在數(shù)據(jù)庫里面的,所以現(xiàn)在需要實現(xiàn)這個功能,首先把前端這個js代碼刪除掉:
<script type="text/javascript" src="../js/distpicker.data.js"></script>
<script type="text/javascript" src="../js/distpicker.js"></script>
然后開始實現(xiàn)這個功能,這里前端用戶選擇省市區(qū)之后有一個聯(lián)動效果,這個就是通過父code查到子name的操作。
還有一個就是用戶選擇省市區(qū)之后傳遞給后端的其實是一個code,要補全數(shù)據(jù)庫內(nèi)容的話還需要有一個自己code查自己name的操作。所以這里的sql將是兩個操作。
1.創(chuàng)建地址表
parent代表父區(qū)域的代碼號;code代表自身的代碼號;省的父代碼號是+86,代表中國
CREATE TABLE t_dict_district (id INT(11) NOT NULL AUTO_INCREMENT,parent VARCHAR(6) DEFAULT NULL,`code` VARCHAR(6) DEFAULT NULL,`name` VARCHAR(16) DEFAULT NULL,PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
然后將這些規(guī)定好的數(shù)據(jù)插入到表中:
LOCK TABLES t_dict_district WRITE;
INSERT INTO t_dict_district VALUES (1,'110100','110101','東城區(qū)'),(2,'110100','110102','西城區(qū)')等等等等;
UNLOCK TABLES;
2.創(chuàng)建實體類
這里就不用再繼承了,因為這個表里面沒有那四個是字段,這個表只是用來查詢父子地址的。
因為沒有繼承BaseEntity所以需要實現(xiàn)接口Serializable序列化。
添加表字段之后,添加get,set; equals和hashCode; toString方法
。
public class District implements Serializable {private Integer id;private String parent;private String code;private String name;
}
3.sql持久層
為了之后這個功能的復(fù)用,將這個新建持久層DistrictMapper接口,然后寫mapper接口和sql映射
public interface DistrictMapper {List<District> findByParent(String parent);String findNameByCode(String code);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.store.mapper.DistrictMapper"><select id="findByParent" resultType="com.example.store.entity.District">select * from t_dict_district where parent=#{parent} order by code ASC</select><select id="findNameByCode" resultType="java.lang.String">select name from t_dict_district where code=#{code}</select>
</mapper>
測試持久層:
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictMapperTests {@Autowiredprivate DistrictMapper districtMapper;@Testpublic void findByParent(){List<District> list=districtMapper.findByParent("86");for (District d:list){System.out.println(d);}}@Testpublic void findNameByCode(){String name= districtMapper.findNameByCode("610000");System.out.println(name);}
}
4.前后端數(shù)據(jù)操作的業(yè)務(wù)層
首先查詢的時候不會有什么異常,所以直接定義抽象接口方法,然后實現(xiàn)方法。從前端拿到parent(父的code)然后查詢并返回數(shù)據(jù),這里返回數(shù)據(jù)的時候?qū)⒚恳粭l子數(shù)據(jù)的id和parent都置為null,利于數(shù)據(jù)的傳輸,所以每個子數(shù)據(jù)傳到前端的就是這個子的code和name。
public interface IDistrictService {List <District> findByParent(String parent);String findNameByCode(String code);
}
@Service
public class DistrictServiceImpl implements IDistrictService {@Autowiredprivate DistrictMapper districtMapper;@Overridepublic List<District> findByParent(String parent) {List<District> list=districtMapper.findByParent(parent);for (District d :list){d.setId(null);d.setParent(null);}return list;}@Overridepublic String findNameByCode(String code) {return districtMapper.findNameByCode(code);}
}
測試業(yè)務(wù)層:
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictServiceTests {@Autowiredprivate IDistrictService districtService;@Testpublic void findByParent(){List<District> list= districtService.findByParent("86");for (District d:list){System.out.println(d);}}@Testpublic void findNameByCode(){String name=districtService.findNameByCode("610000");System.out.println(name);}
}
這里要分開討論,首先是聯(lián)動選擇,當(dāng)前端選擇一個父code到后端之后,后端需要拿到子code和那么,這個和前端交互,所以需要寫一個controller,當(dāng)前端有動作請求時后端就要判斷是不是要用這個控制器來返回給前端子name數(shù)據(jù)。
但是通過code找到自己的name,這個數(shù)據(jù)不需要傳遞給前端,只需要在后端把這個name數(shù)據(jù)添加到district對象上,然后保存到數(shù)據(jù)庫。所以第二個code找name的方法不用到控制層,在Address添加地址的時候,把前端傳來的code找到name,然后添加到數(shù)據(jù)庫即可。所以在AddressServiceImpl新增收貨地址的時候用這個服務(wù)實現(xiàn)功能:
@Autowired
private IDistrictService districtService;String provinceName=districtService.findNameByCode(address.getProvinceCode());String cityName = districtService.findNameByCode(address.getCityCode());String areaName = districtService.findNameByCode(address.getAreaCode());address.setProvinceName(provinceName);address.setCityName(cityName);address.setAreaName(areaName);
5.響應(yīng)前端的控制層
首先是沒有新的error,所以不用處理異常,直接繼承BaseConrtroller即可,然后開始處理響應(yīng),
@RequestMapping("districts")
@RestController
public class DistrictController extends BaseController {@Autowiredprivate IDistrictService districtService;@RequestMapping({"/",""})public JsonResult<List<District>> findByParent(String parent){List<District> list=districtService.findByParent(parent);return new JsonResult<>(OK,list);}
}
輸入http://localhost:8080/districts?parent=86
網(wǎng)址進行測試。
6.前端請求
進入頁面顯示省的列表加載,然后省的組件有改變的話就向后端發(fā)送請求市,同理獲取第三級數(shù)據(jù)。
var defaultOption="<option value='0'>-----請選擇-----</option>";// option標(biāo)簽并不會把內(nèi)容發(fā)送到后端,而是將value值發(fā)送給后端,所以用value表示當(dāng)前這個區(qū)域的code值$(document).ready(function () {showProvinceList();//調(diào)用這個方法,給省列表數(shù)據(jù)$("#province-list").append(defaultOption);//將省,市,縣的下拉列表內(nèi)容設(shè)為"-----請選擇-----"$("#city-list").append(defaultOption);$("#area-list").append(defaultOption);});function showProvinceList() {$.ajax({url: "/districts",type: "POST",data: "parent=86",//發(fā)送請求用于獲取所有省對象,一級菜單dataType: "JSON",success: function (json) {if (json.state == 200) {var list = json.data;//獲取所有省對象的List集合for (var i = 0; i < list.length; i++) {var opt ="<option value='"+list[i].code+"'>"+list[i].name+"</option>";$("#province-list").append(opt);}} else {alert("省/直轄區(qū)的信息加載失敗")}}});}$("#province-list").change(function () {//省組件有改變,先獲取到省區(qū)域父代碼號var parent = $("#province-list").val();$("#city-list").empty();$("#area-list").empty();$("#city-list").append(defaultOption);$("#area-list").append(defaultOption);if (parent == 0) {//如果繼續(xù)程序,后面的ajax接收的json數(shù)據(jù)中的data是空集合[],進不了for循環(huán),沒有任何意義,所以直接在這里終止程序return;}$.ajax({url: "/districts",type: "POST",data: "parent="+parent,dataType: "JSON",success: function (json) {if (json.state == 200) {var list = json.data;for (var i = 0; i < list.length; i++) {var opt ="<option value='"+list[i].code+"'>"+list[i].name+"</option>";$("#city-list").append(opt);}} else {alert("市的信息加載失敗")}}});});$("#city-list").change(function () {var parent = $("#city-list").val();$("#area-list").empty();$("#area-list").append(defaultOption);if (parent == 0) {return;}$.ajax({url: "/districts",type: "POST",data: "parent="+parent,dataType: "JSON",success: function (json) {if (json.state == 200) {var list = json.data;for (var i = 0; i < list.length; i++) {var opt ="<option value='"+list[i].code+"'>"+list[i].name+"</option>";$("#area-list").append(opt);}} else {alert("縣的信息加載失敗")}}});});
2.刪除收貨地址
設(shè)為默認(rèn)地址和刪除收貨地址有很多相似之處,我就只寫一下刪除吧。首先是sql語句的設(shè)計。
1.持久層
刪除的話就是通過aid刪除數(shù)據(jù),但如果刪除的數(shù)據(jù)是默認(rèn)地址(通過getIsDefault拿到真否),那么就要得到modeified_time最近的那一條數(shù)據(jù)作為默認(rèn)(拿到之后設(shè)為默認(rèn),設(shè)默認(rèn)這個功能我已經(jīng)實現(xiàn)了,可以自己寫一下),
那么些接口和映射文件,limit 0,1表示查詢到的第一條數(shù)據(jù)(limit (n-1),pageSize),
這樣查詢后就只會獲得第一條數(shù)據(jù)。
select 整個數(shù)據(jù)時,一定要有resultMap="AddressEntityMap"
,對實體和表進行映射。
Integer deleteByAid(Integer aid);
Address findLastModified(Integer uid);
<delete id="deleteByAid">delete from t_address where aid=#{aid}
</delete>
<select id="findLastModified" resultMap="AddressEntityMap">select * from t_address where uid=#{uid} order by modified_time DESC limit 0,1
</select>
2.業(yè)務(wù)層
這里sql執(zhí)行會出現(xiàn)的異常包括:沒有該條地址數(shù)據(jù)(已開發(fā)),地址數(shù)據(jù)歸屬錯誤(已開發(fā))和刪除的時候可能會產(chǎn)生未知的異常(DeleteException )。
public class DeleteException extends ServiceException{/**重寫ServiceException的所有構(gòu)造方法*/
}
然后實現(xiàn)delete這個功能:
void delete(Integer aid,Integer uid,String username);
@Overridepublic void delete(Integer aid, Integer uid, String username) {Address result=addressMapper.findByAid(aid);if (result == null) {throw new AddressNotFoundException("收貨地址數(shù)據(jù)不存在");}if (!result.getUid().equals(uid)) {throw new AccessDeniedException("非法數(shù)據(jù)訪問");}Integer rows = addressMapper.deleteByAid(aid);if (rows != 1) {throw new DeleteException("刪除數(shù)據(jù)時產(chǎn)生未知的異常");}if (result.getIsDefault() == 0) {return;}Integer count = addressMapper.countByUid(uid);if (count == 0) {return;}Address address = addressMapper.findLastModified(uid);rows = addressMapper.updateDefaultByAid(address.getAid(), username, new Date());if (rows != 1) {throw new UpdateException("更新數(shù)據(jù)時產(chǎn)生未知的異常");}}
3.控制層
添加delete的異常到BaseController:
else if (e instanceof DeleteException) {result.setState(5002);result.setMessage("刪除數(shù)據(jù)時產(chǎn)生未知的異常");
}
然后是后端響應(yīng):/addresses/{aid}/set_default(以前的數(shù)據(jù)是通過表單直接提交的,還有一種提交方式就是RestFul風(fēng)格,這種提交方式可以提交更多的數(shù)據(jù),這里用這個提交方式)。
RestFul編寫時不管參數(shù)名和占位符是否一致都必須加@PathVariable(“aid”)
@RequestMapping("{aid}/delete")
public JsonResult<Void> delete(@PathVariable("aid") Integer aid,HttpSession session) {addressService.delete(aid,getUidFromSession(session),getUsernameFromSession(session));return new JsonResult<>(OK);
}
4.前端請求和渲染
給顯示列表的"刪除"按鈕添加onclick屬性并指向deleteByAid(aid)方法。
<td><a onclick="delete_(#{aid})" class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 刪除</a></td>
在顯示列表的for循環(huán)中為占位符aid賦值。
tr = tr.replace("#{aid}",list[i].aid);
然后寫點擊了刪除按鈕和觸發(fā)的delete_函數(shù),這里不能寫delete,因為這是一個關(guān)鍵字。
function delete_(aid) {$.ajax({url: "/addresses/"+aid+"/delete",type: "POST",dataType: "JSON",success: function (json) {if (json.state == 200) {//重新加載收貨地址列表頁面showAddressList();} else {alert("刪除收貨地址失敗")}},error: function (xhr) {alert("刪除收貨地址時產(chǎn)生未知的異常!"+xhr.message);}});}
3.顯示購物車列表
這里涉及到一個多表查詢,用到了cart和product兩個表的信息,所以表不用建,但是這兩個表組成的數(shù)據(jù)是什么類型的實體呢(即不是product也不是cart),所以在store包下創(chuàng)建一個vo包,在該包下面創(chuàng)建CartVO類,不需要繼承BaseController類,那相應(yīng)的就需要單獨實現(xiàn)Serializable接口。
VO全稱Value Object,值對象。當(dāng)進行select查詢時,查詢的結(jié)果屬于多張表中的內(nèi)容,此時發(fā)現(xiàn)結(jié)果集不能直接使用某個POJO實體類來接收,因為POJO實體類不能包含多表查詢出來的信息,解決方式是:重新去構(gòu)建一個新的對象,這個對象用于存儲所查詢出來的結(jié)果集對應(yīng)的映射,所以把這個對象稱之為值對象。
1.VO實體類
首先分析前端需要的數(shù)據(jù)都有哪些,哪些是cart表里的數(shù)據(jù),哪些是product表里的數(shù)據(jù),最后在生成他們的getset,equal hash和tostring
public class CartVO implements Serializable {private Integer cid;//購物車頁面的勾選功能,要有cidprivate Integer uid;//要拿到某個用戶的購物車信息,要有uidprivate Integer pid;//購物車操作后要更新product表里面的庫存等信息,所以要有pidprivate Long price;//這個價格是用戶加入購物車時的商品價格private Integer num;//這個num是購物車?yán)镞@個用戶這個商品的數(shù)量,和product里的num不一樣(庫存量)private String title;//這個是product表里的信息,要展示在購物車列表的前端private Long realPrice;//這個是product表里的價格private String image;//這個是product表里的信息,要展示在購物車列表的前端
}
2.持久層
首先在CartMapper接口中定義方法,這個方法是通過uid查詢到上面vo實體數(shù)據(jù),然后兩表關(guān)聯(lián)的時候不希望有null數(shù)據(jù),所以以cart表作為主表(左連接)查詢此用戶的vo信息。
List<CartVO> findVoByUid(Integer uid);
<select id="findVoByUid" resultType="com.example.store.vo.CartVO">select cid,uid,pid,t_cart.price,t_cart.num,title,t_product.price as realPrice,imagefrom t_cart left join t_product on t_cart.pid=t_product.idwhere uid=#{uid}order by t_cart.created_time desc</select>
3.業(yè)務(wù)層
List<CartVO> getVOByUid(Integer uid);
@Overridepublic List<CartVO> getVOByUid(Integer uid) {return cartMapper.findVoByUid(uid);}
4.控制層
@RequestMapping({"", "/"})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session) {List<CartVO> data = cartService.getVOByUid(getUidFromSession(session));return new JsonResult<List<CartVO>>(OK, data);
}
5.前端請求與渲染
<script type="text/javascript">$(document).ready(function() {showCartList();});//展示購物車列表數(shù)據(jù)function showCartList() {$("#cart-list").empty();$.ajax({url: "/carts",type: "GET",dataType: "JSON",success: function(json) {var list = json.data;for (var i = 0; i < list.length; i++) {var tr = '<tr>\n' +'<td>\n' +'<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("加載購物車列表數(shù)據(jù)時產(chǎn)生未知的異常"+xhr.status);}});}
</script>
4.補充知識點
1.當(dāng)從url中拿數(shù)據(jù)傳遞給后端時,在ajax請求里的data可以用下面的方式:
data:location.search.substring(1),// URL 中的 ? 及之后的部分,1就是從下標(biāo)為1的開始,相當(dāng)于把?去掉了