(a) 规划SQL语句
如果需要把某条收货地址设置为默认,需要执行的SQL语句大致是:
update t_address set is_default=1,modified_user=?,modified_time=? where aid=?
除此以外,还需要将原有的默认地址设置为非默认,由于原默认收货地址数据的id可能是未知的,可以“将该用户的所有收货地址设置为非默认,然后再把指定的那条设置为默认”即可,所以,“将该用户的所有收货地址设置为非默认”的SQL语句大致是:
update t_address set is_default=0 where uid=?
在操作数据之前,还是应该对数据进行检查,例如:检查收货地址数据是否存在,对应的SQL查询是:
select aid from t_address where aid=?
以上查询时,查询的字段并不重要,最终只需要判断查询结果是否为null即可,即:用于判断将要被设置为默认的收货地址数据是否存在。
除此以外,由于参数aid是客户端提交的,应该视为不可靠数据,该aid对应的数据可能是不存在的,另外,也可能是他人的数据,所以,在查询时,还应该将uid也查询出来,用于和Session中的uid对比,以判断即将需要操作的数据的归属是否正常:
select uid from t_address where aid=?
(b) 接口与抽象方法
在AddressMapper.java
接口中,声明3个抽象方法:
Integer updateDefault(
@Param("aid") Integer aid,
@Param("username") String username,
@Param("modifiedTime") Date modifiedTime);
Integer updateNonDefault(Integer uid);
Address findByAid(Integer aid);
© 配置映射
映射:
UPDATE
t_address
SET
is_default=1,
modified_user=#{username},
modified_time=#{modifiedTime}
WHERE
aid=#{aid}
UPDATE
t_address
SET
is_default=0
WHERE
uid=#{uid}
单元测试:
@Test
public void updateDefault() {
Integer aid = 30;
String username = "哈哈";
Date modifiedTime = new Date();
Integer rows = mapper.updateDefault(aid, username, modifiedTime);
System.err.println("rows=" + rows);
}
@Test
public void updateNonDefault() {
Integer uid = 7;
Integer rows = mapper.updateNonDefault(uid);
System.err.println("rows=" + rows);
}
@Test
public void findByAid() {
Integer aid = 30;
Address result = mapper.findByAid(aid);
System.err.println(result);
}
(a) 规划异常
此次设为默认的主要操作是Update
操作,则可能抛出UpdateException
;
在操作之前,应该检查被设置为默认的数据是否存在,所以,可能抛出AddressNotFoundException
;
在检查时,还应该检查数据归属是否正确,即用户操作的是不是自己的数据,如果不是,则抛出AccessDeniedException
。
(b) 接口与抽象方法
在IAddressService
接口中添加:
void setDefault(Integer aid, Integer uid, String username) throws AddressNotFoundException, AccessDeniedException, UpdateException;
© 实现抽象方法
先从AddressMapper.java
接口中复制新的抽象方法并粘贴到业务层实现类AddressServiceImpl
中,并私有化实现这些方法:
然后,重写IAddressService
接口中的抽象方法:
public void setDefault(Integer aid, Integer uid, String username) throws AddressNotFoundException, AccessDeniedException, UpdateException {
// 根据参数aid查询数据
// 判断查询结果是否为null
// 是:AddressNotFoundException
// 判断查询结果中的uid与参数uid是否不同
// 是:AccessDeniedException
// 将该用户的所有收货地址设置为非默认
// 将指定的收货地址设置为默认
}
具体实现为:
@Override
@Transactional
public void setDefault(Integer aid, Integer uid, String username)
throws AddressNotFoundException, AccessDeniedException, UpdateException {
// 根据参数aid查询数据
Address result = findByAid(aid);
// 判断查询结果是否为null
if (result == null) {
// 是:AddressNotFoundException
throw new AddressNotFoundException(
"设置默认收货地址失败!尝试访问的数据不存在!");
}
// 判断查询结果中的uid与参数uid是否不同
if (!result.getUid().equals(uid)) {
// 是:AccessDeniedException
throw new AccessDeniedException(
"设置默认收货地址失败!不允许访问他人的数据!");
}
// 将该用户的所有收货地址设置为非默认
updateNonDefault(uid);
// 将指定的收货地址设置为默认
updateDefault(aid, username, new Date());
}
完成后,单元测试:
@Test
public void setDefault() {
try {
Integer aid = 250;
Integer uid = 7;
String username = "呵呵";
service.setDefault(aid, uid, username);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
(a) 统一处理异常
需要处理2个新的异常!
(b) 设计请求
设计“收货地址-设为默认”的请求方式:
请求路径:/addresses/{aid}/set_default
请求参数:@PathVariable("aid") Integer aid, HttpSession session
请求方式:POST
响应数据:JsonResult
http://localhost:8080/addresses/18/set_default
© 处理请求
@RequestMapping("{aid}/set_default")
public JsonResult setDefault(
@PathVariable("aid") Integer aid,
HttpSession session) {
// 从Session中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务层对象执行设置默认
addressService.setDefault(aid, uid, username);
// 返回
return new JsonResult<>(SUCCESS);
}
(a) 规划SQL语句
当执行删除时,需要执行的SQL语句大致是:
delete from t_address where aid=?
在执行删除之前,还应该检查数据是否存在和数据归属是否正确,这项检查功能已经存在,无需再开发或调整。
如果删除的是“默认”的收货地址,如果这已经是最后一条收货地址,则无需进行后续的任何处理,可以在删除之后,再次通过此前的countByUid()
进行查询,从而得知刚才删除的是不是最后一条收货地址。
如果删除的是“默认”的收货地址,且还有更多的收货地址,则应该把剩余的收货地址中的某一条设置为默认,可以制定规则“将最近修改的那条设置为默认”,设置为默认可以使用此前的updateDefault()
直接完成,而“找出最近修改的收货地址”需要执行的SQL语句大致是:
select * from t_address where uid=? order by modified_time desc limit 0,1
(b) 接口与抽象方法
则需要在持久层接口中添加2个抽象方法:
Integer deleteByAid(Integer aid);
Address findLastModified(Integer uid);
© 配置映射
映射:
DELETE FROM
t_address
WHERE
aid=#{aid}
测试:
@Test
public void deleteByAid() {
Integer aid = 20;
Integer rows = mapper.deleteByAid(aid);
System.err.println("rows=" + rows);
}
@Test
public void findLastModified() {
Integer uid = 7;
Address result = mapper.findLastModified(uid);
System.err.println(result);
}
(a) 规划异常
删除时:DeleteException;
删除前:AddressNotFoundException,AccessDeniedException;
删除后:UpdateException。
(b) 接口与抽象方法
void delete(Integer aid, Integer uid, String username) throws AddressNotFoundException, AccessDeniedException, DeleteException, UpdateException;
© 实现抽象方法
私有化实现持久层中新增的方法:
/**
* 根据收货地址数据的id删除收货地址
* @param aid 收货地址数据的id
* @throws DeleteException 删除数据异常
*/
private void deleteByAid(Integer aid) {
Integer rows = addressMapper.deleteByAid(aid);
if (rows != 1) {
throw new DeleteException(
"删除收货地址失败!出现未知错误!");
}
}
/**
* 查询某用户最后修改的收货地址数据
* @param uid 用户的id
* @return 该用户最后修改的收货地址数据
*/
private Address findLastModified(Integer uid) {
return addressMapper.findLastModified(uid);
}
重写业务接口中的抽象方法:
@Transactional
public void delete(Integer aid, Integer uid, String username) throws AddressNotFoundException, AccessDeniedException, DeleteException, UpdateException {
// 根据参数aid查询数据
// 判断查询结果是否为null:AddressNotFoundException
// 判断查询结果中的uid与参数uid是否不同:AccessDeniedException
// 执行删除
// 判断此前的查询结果中的isDefault是否为0:return
// 根据参数uid统计收货地址数量
// 判断数量为0:return
// 根据参数uid查询最后修改的收货地址
// 根据查询到的最后修改的收货地址中的aid执行设置默认
}
实现:
@Override
@Transactional
public void delete(Integer aid, Integer uid, String username)
throws AddressNotFoundException, AccessDeniedException, DeleteException, UpdateException {
// 根据参数aid查询数据
Address result = findByAid(aid);
// 判断查询结果是否为null
if (result == null) {
// 是:AddressNotFoundException
throw new AddressNotFoundException(
"删除收货地址失败!尝试访问的数据不存在!");
}
// 判断查询结果中的uid与参数uid是否不同
if (!result.getUid().equals(uid)) {
// 是:AccessDeniedException
throw new AccessDeniedException(
"删除收货地址失败!不允许访问他人的数据!");
}
// 执行删除
deleteByAid(aid);
// 判断此前的查询结果中的isDefault是否为0:return
if (result.getIsDefault() == 0) {
return;
}
// 根据参数uid统计收货地址数量
Integer count = countByUid(uid);
// 判断数量为0:return
if (count == 0) {
return;
}
// 根据参数uid查询最后修改的收货地址
Address lastModifiedAddress = findLastModified(uid);
// 根据查询到的最后修改的收货地址中的aid执行设置默认
updateDefault(lastModifiedAddress.getAid(), username, new Date());
}
测试:
@Test
public void delete() {
try {
Integer aid = 26;
Integer uid = 7;
String username = "呵呵";
service.delete(aid, uid, username);
System.err.println("OK.");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
(a) 统一处理异常
处理:DeleteException
(b) 设计请求
设计“收货地址-删除”的请求方式:
/resources/id/command
请求路径:/addresses/{aid}/delete
请求参数:@PathVariable("aid") Integer aid, HttpSession session
请求方式:POST
响应数据:JsonResult
http://localhost:8080/addresses/18/delete
© 处理请求
@RequestMapping("{aid}/delete")
public JsonResult delete(
@PathVariable("aid") Integer aid,
HttpSession session) {
// 从Session中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务层对象执行删除
addressService.delete(aid, uid, username);
// 返回
return new JsonResult<>(SUCCESS);
}
创建cn.tedu.store.entity.Product
实体类,继承自BaseEntity
:
public class Product extends BaseEntity {
// ...
}
(a) SQL
应该按照数据的priority
降序排列,取出排名最靠前的4样商品,则:
select * from t_product order by priority desc limit 0, 4
(b) 接口与抽象方法
创建cn.tedu.store.mapper.ProductMapper
接口,声明抽象方法:
List findHotList();
© 配置映射
复制得到ProductMapper.xml
配置文件,修改根节点的namespace
属性值对应以上接口,然后配置以上抽象方法的映射:
创建测试类,编写并执行单元测试:
...
(a) 规划异常
无
(b) 接口与抽象方法
创建cn.tedu.store.service.IProductService
接口,然后添加抽象方法:
List getHotList();
© 实现
创建cn.tedu.store.service.impl.ProductServiceImpl
,实现IProductService
接口,添加@Service
注解,在类中声明@Autowired private ProductMapper productMapper;
持久层对象。
私有化实现持久层接口中的方法:
重写IProductService
接口中的抽象方法:
public List getHotList() {
List list = findHotList();
for (Product item : list) {
// 把item中所有不必要的字段设置为null
}
return list;
}
创建测试类,编写并执行单元测试:
...
事务是数据库中可以保证多项数据操作要么全部成功,要么全部失败的机制,以此可以保障数据安全!
基于spring-jdbc的事务处理方式就是在业务方法之前添加@Transactional
注解即可!
在spring-jdbc中,处理事务大致是:
开启事务
try {
调用带@Transactional注解的业务方法
提交:commit
} catch (RuntimeException e) {
回滚:rollback
}
所以,要实现spring-jdbc对事务的管理,不仅仅只是添加@Transactional
注解而已,还需要保证:在视为“失败”时,必须抛出RuntimeException
或其子孙类异常的对象!
另外,@Transactional
注解还可以添加在类之前,表示该类中所有的业务方法都是有事务保障的,但是并不推荐这样处理!
小结:
什么时候需要使用事务:当某项业务涉及2次或更多的增、删、改操作时,需要使用事务,例如:需要执行2次Update操作,或1次Insert操作加上1次Update操作等;
如何保证事务能正确执行:首先,每次增、删、改操作都必须获取受影响的行数,并判断行数是否是预期值,如果不是预期值,必须抛出RuntimeException
或其子孙类异常的对象,然后,在业务方法之前添加@Transactional
注解即可。