spring(day07)

1. 添加“类别”的业务

在项目的根包下创建pojo.dto.CategoryAddNewDTO类:

@Data
public class CategoryAddNewDTO implements Serializable {

    private String name;
    private Long parentId;
    private String keywords;
    private Integer sort;
    private String icon;
    private Integer enable;
    private Integer isDisplay;

}

在项目的根包下创建service.ICategoryService接口:

public interface ICategoryService {
    void addNew(CategoryAddNewDTO categoryAddNewDTO);
}

在项目的根包下创建service.impl.CategoryServiceImpl类,实现以上接口,并添加@Service注解:

@Service
public class CategoryServiceImpl implements ICategoryService {
    
    @Autowired
    private CategoryMapper categoryMapper;
    
    @Override
    public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
        // 调用Mapper对象的【根据名称统计数量】方法进行统计
        // 判断统计结果是否大于0
        // 是:名称已经被占用,抛出异常(CONFLICT)
        
        // 声明局部变量depth,默认值为1
        // 取出参数中的parentId
        // 判断parentId是否不为0
        // 是:调用Mapper对象的【根据id查询详情】,使用parentId作为参数,执行查询
        // -- 判断查询结果是否不为null
        // -- 是:局部变量depth=父级depth+1
        // -- 否:父级类别不存在,抛出异常(NOT_FOUND)
 
        // 创建Category实体类的对象
        // 将参数DTO的各属性值复制到Category实体类对象中
        // 补全Category实体类对象的属性:depth
        // 补全Category实体类对象的属性:is_parent:固定为0
        // 调用Mapper对象的方法,将数据插入到数据库,并获取返回值
        // 判断返回值是否不为1
        // 是:抛出异常(ERR_INSERT)
        
        // 判断parentId是否不为0
        // 是:判断父级类别的isParent是否为0
        // -- 是:创建新的Category对象,封装:parentId,isParent(1)
        // -- -- 调用Mapper对象的【更新】方法,执行修改数据,并获取返回值
        // -- -- 判断返回值是否不为1
        // -- -- -- 是:抛出异常(ERR_UPDATE)
    }
    
}

具体实现为:

@Override
public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
    // 调用Mapper对象的【根据名称统计数量】方法进行统计
    String name = categoryAddNewDTO.getName();
    int count = categoryMapper.countByName(name);
    // 判断统计结果是否大于0
    if (count > 0) {
        // 是:名称已经被占用,抛出异常(CONFLICT)
        String message = "添加类别失败,尝试添加的类别名称【" + name + "】已经存在!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
    }

    // 声明父级类别对象
    CategoryStandardVO parentCategory = null;
    // 声明局部变量depth,默认值为1
    Integer depth = 1;
    // 取出参数中的parentId
    Long parentId = categoryAddNewDTO.getParentId();
    // 判断parentId是否不为0
    if (parentId != 0) {
        // 是:调用Mapper对象的【根据id查询详情】,使用parentId作为参数,执行查询
        parentCategory = categoryMapper.getStandardById(parentId);
        // 判断查询结果是否不为null
        if (parentCategory != null) {
        // -- 是:局部变量depth=父级depth+1
            depth += parentCategory.getDepth();
        } else {
            // -- 否:父级类别不存在,抛出异常(NOT_FOUND)
            String message = "添加类别失败,选定的父级类别不存在!";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
    }

    // 创建Category实体类的对象
    Category category = new Category();
    // 将参数DTO的各属性值复制到Category实体类对象中
    BeanUtils.copyProperties(categoryAddNewDTO, category);
    // 补全Category实体类对象的属性:depth
    category.setDepth(depth);
    // 补全Category实体类对象的属性:isParent:固定为0
    category.setIsParent(0);
    // 调用Mapper对象的方法,将数据插入到数据库,并获取返回值
    int rows = categoryMapper.insert(category);
    // 判断返回值是否不为1
    if (rows != 1) {
        // 是:抛出异常(ERR_INSERT)
        String message = "添加类别失败,服务器忙,请稍后再尝试!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_INSERT, message);
    }

    // 判断parentId是否不为0
    if (parentId != 0) {
        // 是:判断父级类别的isParent是否为0
        if (parentCategory.getIsParent() == 0) {
            // 是:创建新的Category对象,封装:parentId,isParent(1)
            Category updateParentCategory = new Category();
            updateParentCategory.setId(parentId);
            updateParentCategory.setIsParent(1);
            // 调用Mapper对象的【更新】方法,执行修改数据,并获取返回值
            int updateRows = categoryMapper.updateById(updateParentCategory);
            // 判断返回值是否不为1
            if (updateRows != 1) {
                // 是:抛出异常(ERR_UPDATE)
                String message = "添加类别失败,服务器忙,请稍后再尝试!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_UPDATE, message);
            }
        }
    }
}

完成后,测试:

package cn.tedu.csmall.product.service;

import cn.tedu.csmall.product.ex.ServiceException;
import cn.tedu.csmall.product.pojo.dto.CategoryAddNewDTO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
public class CategoryServiceTests {

    @Autowired
    ICategoryService service;

    @Test
    void testAddNew() {
        CategoryAddNewDTO categoryAddNewDTO = new CategoryAddNewDTO();
        categoryAddNewDTO.setName("休闲衬衣");
        categoryAddNewDTO.setParentId(23L);

        try {
            service.addNew(categoryAddNewDTO);
            log.debug("添加成功!");
        } catch (ServiceException e) {
            log.debug("serviceCode : " + e.getServiceCode());
            log.debug("message : " + e.getMessage());
        }
    }

}

2. 删除“类别”的业务

CategoryMapper.java接口中添加新的抽象方法:

int countByParentId(Long parentId);

CategoryMapper.xml中配置SQL:

<select id="countByParentId" resultType="int">
	SELECT count(*) FROM pms_category WHERE parent_id=#{parentId}
</select>

CategoryMapperTests中编写并执行测试:

@Test
void testCountByParentId() {
    Long parentId = 1L;
    int count = mapper.countByParentId(parentId);
    log.debug("根据父级id【{}】统计数量完成,数量:", parentId, count);
}

ICategoryService接口中添加抽象方法:

void deleteById(Long id);

CategoryServiceImpl类中实现以上抽象方法:

@Override
public void deleteById(Long id) {
    // 调用Mapper对象的【根据id查询详情】查询数据,是当前尝试删除的数据
    // 判断查询结果是否为null
    // 是:数据不存在,抛出异常(ERR_NOT_FOUND)
    
    // 检查当前尝试删除的类别是否存在子级类别:判断以上查询结果的isParent是否为1
    // 是:当前尝试删除的类别“是父级类别”(包含子级),抛出异常(ERR_CONFLICT)
    
    // 调用Mapper对象的【根据id删除】执行删除,并获取返回值
    // 判断返回值是否不为1
    // 是:抛出异常(ERR_DELETE)
    
    // ====== 如果这是父级类别中的最后一个子级,则将父级的isParent改为0 =====
    // 从当前尝试删除的类别对象中取出parentId
    // 调用Mapper对象的countByParentId(parentId)进行统计
    // 判断统计结果是否为0
    // 创建新的Category对象,用于更新父级,此Category对象中需要封装:id(parentId),isParent(0)
    // 调用Mapper对象的【更新】功能,执行修改数据,并获取返回值
    // 判断返回值是否不为1
    // 是:抛出异常(ERR_UPDATE)
}

具体实现为:

@Override
public void deleteById(Long id) {
    // 调用Mapper对象的【根据id查询详情】查询数据,是当前尝试删除的数据
    CategoryStandardVO currentCategory = categoryMapper.getStandardById(id);
    // 判断查询结果是否为null
    if (currentCategory == null) {
        // 是:数据不存在,抛出异常(ERR_NOT_FOUND)
        String message = "删除类别失败,尝试删除的类别不存在!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
    }

    // 检查当前尝试删除的类别是否存在子级类别:判断以上查询结果的isParent是否为1
    if (currentCategory.getIsParent() == 1) {
        // 是:当前尝试删除的类别“是父级类别”(包含子级),抛出异常(ERR_CONFLICT)
        String message = "删除类别失败,尝试删除的类别仍包含子级类别!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
    }

    // 调用Mapper对象的【根据id删除】执行删除,并获取返回值
    int rows = categoryMapper.deleteById(id);
    // 判断返回值是否不为1
    if (rows != 1) {
        // 是:抛出异常(ERR_DELETE)
        String message = "删除类别失败,服务器忙,请稍后再尝试!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_DELETE, message);
    }

    // ====== 如果这是父级类别中的最后一个子级,则将父级的isParent改为0 =====
    // 从当前尝试删除的类别对象中取出parentId
    Long parentId = currentCategory.getParentId();
    // 判断当前类别是否不为1级类别,即parentId不为0
    if (parentId != 0) {
        // 调用Mapper对象的countByParentId(parentId)进行统计
        int count = categoryMapper.countByParentId(parentId);
        // 判断统计结果是否为0
        if (count == 0) {
            // 创建新的Category对象,用于更新父级,此Category对象中需要封装:id(parentId),isParent(0)
            Category parentCategory = new Category();
            parentCategory.setId(parentId);
            parentCategory.setIsParent(0);
            // 调用Mapper对象的【更新】功能,执行修改数据,并获取返回值
            rows = categoryMapper.updateById(parentCategory);
            // 判断返回值是否不为1
            if (rows != 1) {
                // 是:抛出异常(ERR_UPDATE)
                String message = "删除类别失败,服务器忙,请稍后再尝试!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_UPDATE, message);
            }
        }
    }
}

完成后,测试:

@Test
void testDeleteById() {
    Long id = 18L;

    try {
        service.deleteById(id);
        log.debug("删除成功!");
    } catch (ServiceException e) {
        log.debug("serviceCode : " + e.getServiceCode());
        log.debug("message : " + e.getMessage());
    }
}

3. 事务(Transaction)

事务是数据库中的可以保证多个(至少2个)写操作(增、删、改)要么全部执行成功,要么全部执行失败的机制!

在基于Spring JDBC的项目中,使用@Transactional注解,即可使得注解的方法是事务性的。

关于@Transactional注解,可以添加在:

  • 接口上
    • 等效于在每个抽象方法上添加了此注解
  • 接口的抽象方法上
    • 仅作用于当前方法(重写的方法运行时)
  • 实现类上
    • 等效于在每个重写的接口的方法上添加了此注解
  • 实现类中重写的接口的方法上
    • 仅作用于当前方法

提示:此注解可以配置一些参数,如果同时在接口/类、接口的抽象方法/类重写的方法上添加此注解并配置了不同的参数值,则以方法上的配置为准。

注意:Spring JDBC是基于接口的代理模式来实现事务管理的!所以,如果在实现类中的自定义方法上添加@Transactional注解是错误的做法!仅当对应的方法是在业务接口中已经声明的,使用@Transactional才是正确的!

关于事务处理过程中的几个概念:

  • 开启事务:BEGIN
  • 提交事务:COMMIT
  • 回滚事务:ROLLBACK

在Spring JDBC的事务管理中,大致是:

开启事务
try {
	执行你的业务方法
	提交事务
} catch (RuntimeException e) {
	回滚事务
}

可以看到,Spring JDBC的事务管理中,默认将根据RuntimeException进行回滚,可以通过@Transactional注解的rollbackFor / rollbackForClassName这2个属性中的某1个进行修改,设置为对特定的异常进行回滚,还可以配置noRollbackFor / noRollbackForClassName这2个属性,设置对特定的异常不回滚。

【小结】关于事务:

  • 如果某个业务方法涉及超过1次的增、删、改操作,需要保证此业务方法是事务性的;
  • 推荐在业务的抽象方法上添加@Transactional注解即可保证此业务方法是事务性
    • 对于初学者,更推荐在业务接口上添加@Transactional,则此接口中所有抽象方法都是事务性,可能其中的某些抽象方法并不需要是事务性的,但是,这种做法比较稳妥,能避免遗漏导致的错误
  • 为了保证事务能够按照预期进行回滚,需要:
    • 业务层必须由接口和实现类组成
      • Spring JDBC是基于接口代理模式实现事务管理的,如果没有接口,则无法实现
    • 所有增、删、改操作完成后,应该及时获取返回结果,并对结果进行判断,如果结果不符合预期,应该抛出异常,且异常应该是RuntimeException或其子孙类异常
      • Spring JDBC在管理事务时,默认按照RuntimeException进行回滚

4. 关于@RequestMapping注解

在Spring MVC框架中,@RequestMapping注解的主要作用是:绑定“请求路径”与“处理请求的方法”的映射关系。

关于此注解的声明的源代码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
}

以上源代码中的@Target({ElementType.TYPE, ElementType.METHOD})就表示了此注解可以添加在哪里,其中,ElementType.TYPE表示可以添加在“类型”上,ElementType.METHOD表示可以添加在方法上。

在控制器类上,添加@RequestMapping配置的路径值,将作为当前控制器类中每个处理请求的路径的前缀部分!例如在类上配置为@RequestMapping("/brands"),在方法上配置为@RequestMapping("/delete"),则完整路径为/brands/delete,并且,此控制器类中所有处理请求的方法的路径上都有/brands前缀!

所以,强烈推荐在每个控制器类上都使用@RequestMapping配置路径的前缀部分。

关于注解内部的源代码,以@RequestMapping为例,其内部有:

String name() default "";

以上源代码中,name()表示此注解有名为name的属性,左侧的String表示此属性的数据类型,右侧的default ""表示此属性的默认值。例如:可以在@RequestMapping中配置名为name的属性:

@RequestMapping(name = "test")

@RequestMapping中,还有:

@AliasFor("path")
String[] value() default {};

则以上源代码表示:此注解中有名为value的属性,值是String[]类型的,则默认值是空数组。

在Java语言中,注解的value属性是默认的属性,如果注解只需要配置这1个属性的值,则可以不必显式的声明属性名称,例如:

@RequestMapping(value = {"value1", "value2", "value3"})
@RequestMapping({"value1", "value2", "value3"})

以上2种配置是完全等效的!

在Java语言中,如果某个属性的值是某种数组类型,但是,配置的数组值只有1个元素,则不必使用大括号将其框住,例如:

@RequestMapping(value = {"value1"})
@RequestMapping(value = "value1")

以上2种配置是完全等效的!

关于源代码中value属性的声明,还添加了@AliasFor("path"),表示此value属性等效于当前注解中的path属性!

@RequestMapping注解的源代码中,还有:

RequestMethod[] method() default {};

以上注解的作用是“限制请求方式”,在没有配置此属性的情况,任何请求方式都是允许的!例如:

@RequestMapping(value = "/add-new", method = RequestMethod.POST)

如果使用以上配置,则/add-new路径只允许通过POST方式提交请求,如果使用其它请求方式,将出现405错误!

强制推荐将所有请求都限制请求方式!

在Spring MVC框架中,还定义了相关注解,以简化限制请求方式的配置:

  • @GetMapping
  • @PostMapping
  • PutMapping
  • DeleteMapping
  • PatchMapping

以上这些都相当于是限制了请求方式的@RequestMapping

小结:

  • 推荐在每个控制器类上使用@RequestMapping配置请求路径前缀
  • 推荐在每个处理请求的方法上使用@GetMapping / @PostMapping配置请求路径

作业

完成以下页面设计:

1. 添加类别

菜单位置:临时页面 > 添加类别

文件名:/src/views/sys-admin/temp/CategoryAddNewView.vue

访问路径:/sys-admin/temp/category/add-new

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfXeIDKv-1662131094759)(images/image-20220830175113683.png)]

2. 添加品牌

菜单位置:临时页面 > 添加品牌

文件名:/src/views/sys-admin/temp/BrandAddNewView.vue

访问路径:/sys-admin/temp/brand/add-new

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lgCDkdBw-1662131094762)(images/image-20220830175357675.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值