注意事项
addShop的多个操作步骤(设置基本信息,插入tb_shop || 添加成功,则继续处理文件,获取shopid,用于创建图片存放的目录 || 更新tb_shop中 shop_img字段 ),需要找同一个事务中,所以addShop方法添加了
@Transactional注解
Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚,所以ShopOperationException必须继承RuntimeException才可以实现回滚,如果继承Exception则不会回滚数据库操作。
Service层接口类ShopService
src/main/java建立 com.imooc.o2o.service包,新增接口类ShopService
package com.imooc.o2o.service;
import java.io.File;
import com.artisan.o2o.dto.ShopExecution;
import com.artisan.o2o.entity.Shop;
public interface ShopService {
ShopExecution addShop(Shop shop, File shopFile);
}
这样每当出错了就抛出这样的异常,能够事务回滚
package com.artisan.o2o.service.impl;
import java.io.File;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.artisan.o2o.dao.ShopDao;
import com.artisan.o2o.dto.ShopExecution;
import com.artisan.o2o.entity.Shop;
import com.artisan.o2o.enums.ShopStateEnum;
import com.artisan.o2o.exception.ShopOperationException;
import com.artisan.o2o.service.ShopService;
import com.artisan.o2o.util.FileUtil;
import com.artisan.o2o.util.ImageUtil;
@Service
public class ShopServiceImpl implements ShopService {
private static final Logger logger = LoggerFactory.getLogger(ShopServiceImpl.class);
@Autowired
private ShopDao shopDao;
/**
* 3个步骤需要在一个事务中,添加@Transactional注解
*
* 1. 将shop基本信息添加到数据库,返回shopId
*
* 2. 根据shopId创建目录,得到图片存储的相对路径
*
* 3. 将相对路径更新到数据库
*
*
* Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚
*
* (1)注解@Transactional 只能应用到 public 方法才有效
*
* (2)在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring成的代理对象来管理,这会造成自调用问题。
* 若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,
* 有@Transactional注解的方法的事务被忽略,不会发生回滚。
*
*
* 上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP
* 代理造成的。为解决这两个问题,可以使用 AspectJ 取代 Spring AOP 代理
*
* 在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP
* 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional
* 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor
* 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑,
* 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源
* DataSource 提交或回滚事务
*
*/
@Override
@Transactional
public ShopExecution addShop(Shop shop, File shopImg) {
// 非空判断 (这里先判断shop是否为空,严格意义上讲shop中的are的属性也需要判断)
if (shop == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP_INFO);
}
// 关键步骤1. 设置基本信息,插入shop
// 初始状态: 审核中
shop.setEnableStatus(0);
shop.setCreateTime(new Date());
shop.setLastEditTime(new Date());
int effectedNum = shopDao.insertShop(shop);
if (effectedNum <= 0) {
throw new ShopOperationException("店铺创建失败");
} else {
// 关键步骤2. 添加成功,则继续处理文件,获取shopid,用于创建图片存放的目录
if (shopImg != null) {
try {
// 需要根据shopId来创建目录,所以也需要shop这个入参
addShopImg(shop, shopImg);
} catch (Exception e) {
logger.error("addShopImg error {} ", e.toString());
throw new ShopOperationException("addShopImg error:" + e.getMessage());
}
// 关键步骤3. 更新tb_shop中 shop_img字段
effectedNum = shopDao.updateShop(shop);
if (effectedNum <= 0) {
logger.error("updateShop error {} ", "更新店铺失败");
throw new ShopOperationException("updateShop error");
}
}
}
// 返回店铺的状态:审核中,以及店铺信息
return new ShopExecution(ShopStateEnum.CHECK, shop);
}
/**
*
*
* @Title: addShopImg
*
* @Description: 根据shopId创建目录,并生成水印图片
*
* @param shop
* @param shopImg
*
* @return: void
*/
private void addShopImg(Shop shop, File shopImg) {
String imgPath = FileUtil.getShopImagePath(shop.getShopId());
// 生成图片的水印图
String relativeAddr = ImageUtil.generateThumbnails(shopImg, imgPath);
// 将相对路径设置个shop,用于更新数据库
shop.setShopImg(relativeAddr);
}
}
ShopOperationException
package com.artisan.o2o.exception;
/**
*
*
* @ClassName: ShopOperationException
*
* @Description: 继承自RuntimeException ,这样在标注了@Transactional事务的方法中,出现了异常,才回回滚数据。
*
* 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring
* 将回滚事务;除此之外,Spring 不会回滚事务。
*
* @author: Mr.Yang
*
* @date: 2018年5月21日 下午5:37:53
*/
public class ShopOperationException extends RuntimeException {
private static final long serialVersionUID = 6860566652051914211L;
public ShopOperationException(String message) {
super(message);
}
}
我们的private void addShopImg(Shop shop,File shopImg) 这个方法是通过shop实体和图片文件来获得图片存储的路径并且将文件存入应该存储的路径
代码实现:
package storepro.service;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import storepro.BaseTest;
import storepro.dto.ShopExecution;
import storepro.entity.Area;
import storepro.entity.PersonInfo;
import storepro.entity.Shop;
import storepro.entity.ShopCategory;
import storepro.enums.ShopStateEnum;
import java.io.File;
import java.util.Date;
public class ShopServiceTest extends BaseTest {
@Autowired
private ShopService shopService;
@Test
public void testAddShop(){
Shop shop=new Shop();
PersonInfo personInfo=new PersonInfo();
ShopCategory shopCategory=new ShopCategory();
Area area =new Area();
personInfo.setUserId(1L);//long型后面加L
shopCategory.setShopCategoryId(1L);
area.setAreaId(2);
shop.setOwner(personInfo);
shop.setArea(area);
shop.setShopName("测试的店铺1");
shop.setShopCategory(shopCategory);
shop.setShopAddr("test1");
shop.setPhone("test1");
shop.setShopDesc("test1");
shop.setShopImg("test1");
shop.setCreateTime(new Date());
shop.setAdvice("审核中");
shop.setEnableStatus(ShopStateEnum.CHECK.getState());
File file=new File("C:/Users/YF/Desktop/yufei.jpg");
ShopExecution se=shopService.addShop(shop,file);
Assert.assertEquals(ShopStateEnum.CHECK.getState(),se.getState());
}
}
讲解:我们通过手动配置一个shop对象并手动提供一个图片,
调用shopService的实现类来完成addShop方法。
判断:因为成功创建店铺后,dto中ShopExecution的类中的state是一个审核中的状态码,也就是CHECK,我们判断我check的state码和我们创建的店铺的state比较可得正确与否