当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…
然后,将以上这些种类的数据的处理排个顺序,即先处理哪种数据,后处理哪种数据!通常,应该先处理基础数据,再处理所相关的数据,例如需要先处理商品数据,才可以处理订单数据,如果多种数据之间没有明显的关联,则应该先处理简单的,再处理较难的!
则以上这些数据的处理顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单
当确定了数据处理顺序后,就应该分析某个用户对应的功能有哪些,以“用户”数据为例,相关功能有:注册、登录、修改密码、修改资料、上传头像…
然后,还是需要确定以上功能的开发顺序,通常,遵循 “增 > 查 > 删 > 改” 的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。
每个功能的开发都应该遵循 创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端页面
一次只解决一个问题
大问题拆成小问题
a.规划SQL语句
b.接口与抽象方法
c.配置映射
业务层的基本定位
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
a.处理异常
b.设计请求
c.处理请求
关于收货地址的管理,涉及的功能有:增加,查看列表,修改,删除,设为默认。
以上功能的开发顺序应该是:增加 > 查看列表 > 设为默认 > 删除 > 修改。
a.规划SQL语句
b.接口与抽象方法
c.配置映射
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
a.处理异常
b.设计请求
c.处理请求
a.规划SQL语句
如果要将某收货地址设置为默认,SQL语句大致是:
UPDATE
t_address
SET
is_default = 1, modified_user = ?, modified_time = ?
WHERE
aid = ?
在把某收货地址设为默认之前,应该把原来的默认地址设为非默认,可以选择把所有的收货地址设置为非默认:
UPDATE
t_address
SET
is_default = 0
WHERE
uid = ?
另外,在设置默认之前,还应该检查该收货地址数据是否存在,可通过简单的查询来实现:
SELECT
*
FROM
t_address
WHERE
aid = ?
并且,除了检查数据是否存在以外,还应该检查数据的归属是否正确,即该 aid
对应的数据是不是当前登录的用户的数据,则查询时,应该查询 uid
字段,与用户登录后 session
中的 uid
进行对比,以判断数据归属,则查询需要调整为:
SELECT
uid
FROM
t_address
WHERE
aid = ?
总结:
// 共三条 sql 语句
// 1、将所有地址设为非默认
UPDATE t_address SET is_default=0 WHERE uid=?
// 2、将指定地址设为默认
UPDATE t_address SET is_default=1, modified_user=?, modified_time=? WHERE aid=?
// 3、查询收货地址是否存在
SELECT uid FROM t_address WHERE aid=?
b.接口与抽象方法
/**
* 将所有地址设为非默认
* @param uid 用户 uid
* @return 受影响的行数
*/
Integer updateNonDefault(Integer uid);
/**
* 将指定地址设为默认
* @param aid 指定的地址 id
* @return 受影响的行数
*/
Integer updateDefault(
@Param("aid") Integer aid,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
/**
* 根据 aid 查询地址信息
* @param aid 要查询的地址的id
* @return 查询到的地址信息
*/
Address findByAid(Integer aid);
c.配置映射
<!-- 将所有地址设置为非默认 -->
<!-- Integer updateNonDefault(Integer uid) -->
<update id="updateNonDefault">
UPDATE
t_address
SET
is_default=0
WHERE
uid=#{uid}
</update>
<!-- 将指定地址设置为默认 -->
<!-- Integer updateDefault(Integer aid) -->
<update id="updateDefault">
UPDATE
t_address
SET
is_default=1, modified_user=#{modifiedUser}, modified_time=#{modifiedTime}
WHERE
aid=#{aid}
</update>
<!-- 根据收货地址 aid 查询收货地址信息 -->
<!-- Address findByAid(Integer aid) -->
<select id="findByAid" resultType="cn.tedu.store.entity.Address">
SELECT
uid
FROM
t_address
WHERE
aid=#{aid}
</select>
编写并执行单元测试:
/**
* 测试将所有地址设为非默认
*/
@Test
public void testUpdateNonDefault() {
Integer uid = 7;
Integer rows = addressMapper.updateNonDefault(uid);
System.err.println("rows = " + rows);
}
/**
* 测试将指定地址设为默认
*/
@Test
public void testUpdateDefault() {
Integer aid = 1;
String modifiedUser = "酒厂管理员";
Date modifiedTime = new Date();
Integer rows = addressMapper.updateDefault(aid, modifiedUser, modifiedTime);
System.err.println("rows = " + rows);
}
/**
* 测试根据 aid 查询 地址信息
*/
@Test
public void testFindByAid() {
Integer aid = 1;
Address address = addressMapper.findByAid(aid);
System.err.println("address.uid = " + address.getUid());
}
a.规划异常
此次操作的流程大致是:先检查收货地址数据是否存在,再检查数据归属是否正确,然后再执行修改操作。
检查数据是否存在时,可能抛出:AddressNotFoundException
;
检查数据归属是否正确时,可能抛出:AccessDeniedException
;
执行修改操作时,可能抛出:UpdateException
。
所以,需要事先创建原本不存在的异常类AddressNotFoundException
和AccessDeniedException
。
b.接口与抽象方法
在IAddressService
接口中添加抽象方法:
/**
* 设置默认收货地址
* @param aid 地址 aid
* @param uid 用户 uid
* @param username 用户名
* @throws AddressNotFoundException 地址不存在异常
* @throws AccessDeniedException 访问被拒绝异常
* @throws UpdateException 更新失败异常
*/
void setDefault(Integer aid, Integer uid, String username) throws AddressNotFoundException, AccessDeniedException, UpdateException;
c.实现类与重写方法
首先,将持久层的 3个新增方法复制到AddressServiceImpl
业务层实现类中:
Integer updateNonDefault(Integer uid);
Integer updateDefault(Integer aid, String modifiedUser, Date modifiedTime);
Address findByAid(Integer aid);
未实现的方法是报错的,先私有化实现:
/**
* 设置所有地址为非默认
* @param uid 用户 uid
*/
private void updateNonDefault(Integer uid) {
Integer rows = addressMapper.updateNonDefault(uid);
if(rows == 0) {
throw new UpdateException("修改默认地址失败!出现未知错误,请联系系统管理员!");
}
}
/**
* 设置指定地址为默认
* @param aid 指定地址 aid
* @param modifiedUser 修改人
* @param modifiedTime 修改时间
*/
private void updateDefault(Integer aid, String modifiedUser, Date modifiedTime) {
Integer rows = addressMapper.updateDefault(aid, modifiedUser, modifiedTime);
if(rows != 1) {
throw new UpdateException("修改默认地址失败!出现未知错误,请联系系统管理员!");
}
}
/**
* 根据地址 aid 查询地址信息
* @param aid 地址 aid
* @return 查询到的地址信息
*/
private Address findByAid(Integer aid) {
return addressMapper.findByAid(aid);
}
然后,重写接口中定义的抽象方法:
在方法前添加@Transactional
注解:
/**
* 设置默认地址
*/
@Override
@Transactional
public void setDefault(Integer aid, Integer uid, String username)
throws AddressNotFoundException, AccessDeniedException, UpdateException {
// 根据 aid 查询地址数据是否存在
// 不存在 -- 抛出地址不存在异常
// 判断查询结果的 uid 与 参数 uid 是否一致
// 不一致 -- 抛出访问被拒绝异常
// 将用户所有地址设为非默认
// 将指定地址设为默认
}
具体实现:
/**
* 设置默认地址
*/
@Override
@Transactional
public void setDefault(Integer aid, Integer uid, String username)
throws AddressNotFoundException, AccessDeniedException, UpdateException {
// 根据 aid 查询地址数据是否存在
// 不存在 -- 抛出地址不存在异常
// 判断查询结果的 uid 与 参数 uid 是否一致
// 不一致 -- 抛出访问被拒绝异常
Address result = findByAid(aid);
if(result == null) {
throw new AddressNotFoundException("修改默认地址失败!该地址数据不存在!");
}
if(result.getUid() != uid) {
throw new AccessDeniedException("修改默认地址失败!访问被拒绝!不允许访问他人数据!");
}
// 将用户所有地址设为非默认
updateNonDefault(uid);
// 将指定地址设为默认
Date now = new Date();
updateDefault(aid, username, now);
}
编写并执行单元测试:
/**
* 测试设置地址为默认
*/
@Test
public void testSetDefault() {
try {
Integer aid = 2;
Integer uid = 7;
String username = "Gin";
service.setDefault(aid, uid, username);
System.err.println("OK");
}catch(ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
a.处理异常
需要处理AddressNotFoundException
和AccessDeniedException
b.设计请求
请求路径:/addresses/{aid}/set_default
请求参数:@PathVariable("aid") Integer aid, HttpSession session
请求类型:POST
响应数据:JsonResult<Void>
RESTful:一种 url 的设计风格
建议风格:/resources/id/command
c.处理请求
/**
* 设置默认地址
* @param aid 地址 aid
* @param session 用于获取 uid、username
* @return
*/
@RequestMapping("/{aid}/set_default")
public JsonResult<Void> setDefault(
@PathVariable("aid") Integer aid,
HttpSession session){
// 从 session 中获取 uid,username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务层方法执行设置默认地址
service.setDefault(aid, uid, username);
// 响应
return new JsonResult<Void>(SUCCESS);
}
在address.html
中,在 设为默认
的选项框中添加 onclick
属性:
然后再添加新的 function setDefault(aid){}
:
先写入 alert(aid)
,登陆后进行测试:
function setDefault(aid){
alert(aid);
}
测试成功后,在function setDefault(aid){}
中添加执行逻辑:
<script type="text/javascript">
$(document).ready(function(){
showAddressList();
});
function showAddressList(){
$.ajax({
......
......
......
});
}
function setDefault(aid){
$.ajax({
"url" : "/addresses/" + aid + "/set_default",
"type" : "post",
"dataType" : "json",
"success" : function(json){
if(json.state == 2000){
showAddressList();
}else{
alert("json.message");
}
},
"error" : function(){
alert("您的登录信息已过期!请重新登录!");
}
});
}
</script>
首先,事务是用于保障数据安全的,可以使得一系列的增删改操作,要么全部成功,要么全部失败!
什么情况下需要使用事务: 当某个业务涉及 2 次或更多次的增、删、改操作时,必须使用事务!例如执行 2 条Update语句,或1条 Insert 语句和 1 条 Update 语句等。
如何使用事务: 基于Spring-JDBC的编程中,只需要在业务方法之前添加@Transactional
注解即可。
框架在处理事务时,大致的处理过程是:
开启事务:begin
try{
执行各任务
提交:commit
} catch(RuntimeException) {
回滚:rollback
}
所以,为了保证@Transactional
注解能够正常保障事务,在每次的增、删、改操作执行完成后,都必须判断受影响的行数是否是预期值,如果不是预期值,必须抛出RuntimeException
或其某个子孙类的异常!
另外,@Transactional
注解还可以添加在业务类之前,表示该类中所有的方法在执行时,都是有事务保障的!通常并不推荐这样处理!
@Override
@Transactional
public void setDefault(Integer aid, Integer uid, String username)
throws AddressNotFoundException, AccessDeniedException, UpdateException {
// 根据 aid 查询地址数据是否存在
// 不存在 -- 抛出地址不存在异常
// 判断查询结果的 uid 与 参数 uid 是否一致
// 不一致 -- 抛出访问被拒绝异常
// Address result = addressMapper.findByAid(aid);
// if(result == null) {
// throw new AddressNotFoundException("修改默认地址失败!该地址数据不存在!");
// }
// if(result.getUid() != uid) {
// throw new AccessDeniedException("修改默认地址失败!访问被拒绝!不允许访问他人的数据!");
// }
// 将用户所有地址设为非默认
updateNonDefault(uid);
// 将指定地址设为默认
Date now = new Date();
updateDefault(aid, username, now);
}