02-类别管理--添加类别--(持久层\业务逻辑层)

本文档详细介绍了使用SpringJDBC进行事务管理的原理和实践,结合一个具体的类别管理案例,展示了如何在业务逻辑层添加新类别,包括持久层的配置、SQL语句、接口与抽象方法、测试用例。同时,讨论了如何处理异常以确保事务的原子性。此外,还涵盖了前端页面和控制器层的设计考虑。
摘要由CSDN通过智能技术生成

8. 类别管理–添加类别–持久层

8.1. 配置

续前日,无新增

8.2. 规划需要执行的SQL语句

续前日,无新增

8.3. 接口与抽象方法

此前需要执行的SQL语句大致是:

select id from pms_category where name=?;

csmall-pojo的根包下创建vo.CategorySimpleVO类,用于封装以上查询结果:

@Data
public class CategorySimpleVO implements Serializable {
    private Long id;
}

csmall-product-webapiCategoryMapper接口中添加抽象方法:

CategorySimpleVO getByName(String name);

8.4. 配置SQL语句

csmall-product-webapiCategoryMapper.xml中添加配置:

<!-- CategorySimpleVO getByName(String name); -->
<select id="getByName" resultMap="SimpleResultMap">
    select id from pms_category where name=#{name}
</select>

<resultMap id="SimpleResultMap" type="cn.tedu.csmall.pojo.vo.CategorySimpleVO">
    <id column="id" property="id" />
</resultMap>

8.5. 测试

csmall-product-webapisrc\test\resources下创建insert_data.sql文件,用于插入测试数据:

insert into pms_category (name) value ('类别001'), ('类别002');

然后,在CategoryMapperTests中添加测试方法:

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetByNameSuccessfully() {
    // 测试数据
    String name = "类别001";
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getByName(name);
        // 断言查询结果不为null
        assertNotNull(category);
    });
}

@Test
@Sql({"classpath:truncate.sql"})
public void testGetByNameFailBecauseNotFound() {
    // 测试数据
    String name = "类别999";
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getByName(name);
        // 断言查询结果为null
        assertNull(category);
    });
}

完成后,执行整个测试类(将执行此类中所有测试方法),应该全部通过测试。

9. 类别管理–添加类别–业务逻辑层

9.1. 接口与抽象方法

在使用Dubbo的微服务架构中,需要将业务逻辑层的接口声明在专门的Module中,便于被其它微服务Module依赖,所以,先在csmall-product下创建新的Module,名为csmall-product-service,创建参数:

  • Group:cn.tedu
  • Artifact:csmall-product-service
  • Package Name:cn.tedu.csmall.product.service

首先,应该调用新Module的pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 父级项目 -->
    <parent>
        <groupId>cn.tedu</groupId>
        <artifactId>csmall-product</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <!-- 当前项目的信息 -->
    <groupId>cn.tedu</groupId>
    <artifactId>csmall-product-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 当前项目需要使用的依赖项 -->
    <dependencies>
        <!-- Csmall POJO -->
        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>csmall-pojo</artifactId>
        </dependency>
    </dependencies>

</project>

然后,在csmall-productpom.xml中补充此子级Module:

<!-- 当前Project的各子级Module -->
<modules>
    <module>csmall-product-webapi</module>
    <module>csmall-product-service</module> <!-- 新增 -->
</modules>

接下来,需要删除不必要的文件:

  • 启动类
  • src\main\resources及其下的配置文件
  • src\test

接下来,需要创建接口并添加抽象方法,方法的参数应该是封装的对象(因为一个StringLong等简单数据不足以完成添加类别的操作),则先在csmall-pojo的根包下创建dto.CategoryAddNewDTO类,并在类中添加必要的属性:

package cn.tedu.csmall.pojo.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class CategoryAddNewDTO implements Serializable {

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

}

然后,在csmall-product-service中,在cn.tedu.csmall.product.service下创建ICategoryService接口:

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

9.2. 实现

csmall-product-service中只存放业务逻辑层的接口,而业务逻辑层的实现类仍在csmall-product-webapi中,所以,需要在csmall-product-webapi中依赖csmall-product-service

先在Project的pom.xml中添加对csmall-product-service的依赖管理:

<!-- ===== 原有其它代码 ===== -->

<!-- 依赖管理,主要管理各依赖项的版本,使得子级Module添加依赖时不必指定版本 -->
<dependencyManagement>
    <dependencies>
        <!-- Csmall Product Service -->
        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>csmall-product-service</artifactId>
            <version>${csmall.version}</version>
        </dependency>
        
        <!-- ===== 原有其它代码 ===== -->

然后,在csmall-product-webapi中添加依赖:

<!-- ===== 原有其它代码 ===== -->

<!-- 当前项目需要使用的依赖项 -->
<dependencies>
    <!-- Csmall Product Service -->
    <dependency>
        <groupId>cn.tedu</groupId>
        <artifactId>csmall-product-service</artifactId>
    </dependency>
    
    <!-- ===== 原有其它代码 ===== -->

cn.tedu.csmall.product.webapi下创建service.CategoryServiceImpl类,此类应该实现ICategoryService接口,此类还应该添加@Service注解:

package cn.tedu.csmall.product.webapi.service;

import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.product.service.ICategoryService;
import org.springframework.stereotype.Service;

@Service
public class CategoryServiceImpl implements ICategoryService {
    
    @Override
    public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
    }
    
}

关于以上业务的实现分析:

@Autowired
private CategoryMapper categoryMapper;

// 注意:需要创建异常
// 注意:需要在CategoryMapper中补充getById()方法,至少返回:depth
// 注意:需要在CategoryMapper中补充updateIsParentById()方法
public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
    // 从参数中取出尝试添加的类别的名称
    // 调用categoryMapper.getByName()方法查询
    // 判断查询结果是否不为null
    // 是:抛出ServiceException
    
    // 从参数中取出父级类别的id:parentId
    // 判断parentId是否为0
    // 是:此次尝试添加的是一级类别,没有父级类别,则当前depth >>> 1
    // 否:此次尝试添加的不是一级类别,则应该存在父级类别,调用categoryMapper.getById()方法查询父级类别的信息
    // -- 判断查询结果是否为null
    // -- 是:抛出ServiceException
    // -- 否:当前depth >>> 父级depth + 1

    // 创建Category对象
    // 调用BeanUtils.copyProperties()将参数对象中的属性值复制到Category对象中
    // 补全Category对象中的属性值:depth >>> 前序运算结果
    // 补全Category对象中的属性值:enable >>> 1(默认即启用)
    // 补全Category对象中的属性值:isParent >>> 0
    // 补全Category对象中的属性值:gmtCreate, gmtModified >>> LocalDateTime.now()
    // 调用categoryMapper.insert(Category)插入类别数据,获取返回的受影响的行数
    // 判断返回的受影响的行数是否不为1
    // 是:抛出ServiceException
    
    // 判断父级类别的isParent是否为0
    // 是:调用categoryMapper.updateIsParentById()方法,将父级类别的isParent修改为1,获取返回的受影响的行数
    // 判断返回的受影响的行数是否不为1
    // 是:抛出ServiceException
}

要实现以上业务,需要先在持久层完成“根据id查询类别信息”的功能,则在CategorySimpleVO中添加private Integer depth;属性(原getByName()方法对应的查询也作对应的修改,虽然不是必须的)。

然后,还需要在CategorySimpleVO中补充private Integer isParent;属性,并且,必须在接下的查询中,查出此值。

然后CategeoryMapper接口中添加:

CategorySimpleVO getById(Long id);

然后在CategoryMapper.xml中配置以上方法映射的SQL:

<!-- CategorySimpleVO getById(Long id); -->
<select id="getById" resultMap="SimpleResultMap">
    select id, depth from pms_category where id=#{id}
</select>

完成后,还需要在CategoryMapperTests中添加2个测试,以检验以上功能是否正常运行:

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testGetByIdSuccessfully() {
    // 测试数据
    Long id = 1L;
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getById(id);
        // 断言查询结果不为null
        assertNotNull(category);
    });
}

@Test
@Sql({"classpath:truncate.sql"})
public void testGetByIdFailBecauseNotFound() {
    // 测试数据
    Long id = -1L;
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行查询
        CategorySimpleVO category = mapper.getById(id);
        // 断言查询结果为null
        assertNull(category);
    });
}

CategoryMapper接口中添加:

int updateIsParentById(@Param("id") Long id, @Param("isParent") Integer isParent);

然后在CategoryMapper.xml中配置以上方法映射的SQL:

<!-- int updateIsParentById(@Param("id") Long id, @Param("isParent") Integer isParent); -->
<update id="updateIsParentById">
    update pms_category set is_parent=#{isParent} where id=#{id}
</update>

完成后,还需要在CategoryMapperTests中添加2个测试,以检验以上功能是否正常运行:

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testUpdateIsParentByIdSuccessfully() {
    // 测试数据
    Long id = 1L;
    Integer isParent = 1;
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行测试
        int rows = mapper.updateIsParentById(id, isParent);
        // 断言受影响的行数为1
        assertEquals(1, rows);
    });
}

@Test
@Sql({"classpath:truncate.sql"})
public void testUpdateIsParentByIdFailBecauseNotFound() {
    // 测试数据
    Long id = -1L;
    Integer isParent = 1;
    // 断言不会抛出异常
    assertDoesNotThrow(() -> {
        // 执行测试
        int rows = mapper.updateIsParentById(id, isParent);
        // 断言受影响的行数为0
        assertEquals(0, rows);
    });
}

在实现业务逻辑之前,还需要创建自定义的异常类型,由于后续还有不少需要被多个Module共同使用的类、接口等,所以,此异常类型和后续可能被共用的类、接口都应该放在一个公共的Module中,则在Project下创建csmall-common这个新的Module,创建成功后,需要:

  • csmall-common中,修改pom.xml中的父项目
  • csmall-common中,在pom.xml删除依赖项
  • csmall-common中,在pom.xml删除<build>配置
  • csmall-common中,删除src/test
  • csmall-common中,删除src/main/resources
  • csmall-common中,删除启动类
  • 在Project的pom.xml中,添加<module>
  • 在Project的pom.xml中,添加对新Module的依赖管理
  • csmall-product-webapi中的pom.xml中,添加对csmall-common的依赖

csmall-common的根包下创建ex.ServiceException类:

public class ServiceException extends RuntimeException {
	// 暂时不加构造方法
}

然后,在csmall-product-webapi中的CategoryServiceImpl中实现业务:

package cn.tedu.csmall.product.webapi.service;

import cn.tedu.csmall.common.ex.ServiceException;
import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.pojo.entity.Category;
import cn.tedu.csmall.pojo.vo.CategorySimpleVO;
import cn.tedu.csmall.product.service.ICategoryService;
import cn.tedu.csmall.product.webapi.mapper.CategoryMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
public class CategoryServiceImpl implements ICategoryService {

    @Autowired
    CategoryMapper categoryMapper;

    @Override
    public void addNew(CategoryAddNewDTO categoryAddNewDTO) {
        // 从参数中取出尝试添加的类别的名称
        String name = categoryAddNewDTO.getName();
        // 调用categoryMapper.getByName()方法查询
        CategorySimpleVO queryResult = categoryMapper.getByName(name);
        // 判断查询结果是否不为null
        if (queryResult != null) {
            // 是:抛出ServiceException
            throw new ServiceException();
        }

        // 从参数中取出父级类别的id:parentId
        Long parentId = categoryAddNewDTO.getParentId();
        // 判断parentId是否为0,当前尝试新增的类别的depth默认为1
        Integer depth = 1;
        CategorySimpleVO parentCategory = null;
        if (parentId != 0) {
            // 否:此次尝试添加的不是一级类别,则应该存在父级类别,调用categoryMapper.getById()方法查询父级类别的信息
            parentCategory = categoryMapper.getById(parentId);
            // -- 判断查询结果是否为null
            if (parentCategory == null) {
                // -- 是:抛出ServiceException
                throw new ServiceException();
            }
            // -- 否:当前depth >>> 父级depth + 1
            depth = parentCategory.getDepth() + 1;
        }

        // 创建Category对象
        Category category = new Category();
        // 调用BeanUtils.copyProperties()将参数对象中的属性值复制到Category对象中
        BeanUtils.copyProperties(categoryAddNewDTO, category);
        // 补全Category对象中的属性值:depth >>> 前序运算结果
        category.setDepth(depth);
        // 补全Category对象中的属性值:enable >>> 1(默认即启用)
        category.setEnable(1);
        // 补全Category对象中的属性值:isParent >>> 0
        category.setIsParent(0);
        // 补全Category对象中的属性值:gmtCreate, gmtModified >>> LocalDateTime.now()
        LocalDateTime now = LocalDateTime.now();
        category.setGmtCreate(now);
        category.setGmtModified(now);
        // 调用categoryMapper.insert(Category)插入类别数据,获取返回的受影响的行数
        int rows = categoryMapper.insert(category);
        // 判断返回的受影响的行数是否不为1
        if (rows != 1) {
            // 是:抛出ServiceException
            throw new ServiceException();
        }

        // 判断父级类别的isParent是否为0
        // 以下判断条件有部分多余,但不会报错
        if (parentId != 0 && parentCategory != null && parentCategory.getIsParent() == 0) {
            // 是:调用categoryMapper.updateIsParentById()方法,将父级类别的isParent修改为1,获取返回的受影响的行数
            rows = categoryMapper.updateIsParentById(parentId, 1);
            // 判断返回的受影响的行数是否不为1
            if (rows != 1) {
                // 是:抛出ServiceException
                throw new ServiceException();
            }
        }
    }

}

9.3. 测试

src/test/java下的根包下创建service.CategoryServiceTests测试类,编写并执行测试:

package cn.tedu.csmall.product.webapi.service;

import cn.tedu.csmall.common.ex.ServiceException;
import cn.tedu.csmall.pojo.dto.CategoryAddNewDTO;
import cn.tedu.csmall.product.service.ICategoryService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
public class CategoryServiceTests {

    @Autowired
    ICategoryService service;

    @Test
    @Sql("classpath:truncate.sql")
    public void testAddNewSuccessfully() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("大屏智能手机");
        category.setParentId(0L);
        category.setIcon("未上传类别图标");
        category.setKeywords("未设置关键字");
        category.setSort(88);
        category.setIsDisplay(1);
        // 断言不会抛出异常
        assertDoesNotThrow(() -> {
            // 执行测试
            service.addNew(category);
        });
    }

    @Test
    @Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
    public void testAddNewFailBecauseNameDuplicate() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("类别001");
        // 断言不会抛出异常
        assertThrows(ServiceException.class, () -> {
            // 执行测试
            service.addNew(category);
        });
    }

    @Test
    @Sql({"classpath:truncate.sql"})
    public void testAddNewFailBecauseParentNotFound() {
        // 测试数据
        CategoryAddNewDTO category = new CategoryAddNewDTO();
        category.setName("类别001");
        category.setParentId(-1L);
        // 断言不会抛出异常
        assertThrows(ServiceException.class, () -> {
            // 执行测试
            service.addNew(category);
        });
    }

}

10. 基于Spring JDBC的事务管理

事务:是一种能够保证同一个业务中多个写(增删改)操作要么全部成功,要么失败的机制!

在业务方法上添加@Transactional即可保证此方法是业务性(要么全部成功,要么全部失败)的。

在Spring JDBC中,处理事务的机制大致是:

开启事务:Begin
try {
    你的业务方法
    提交:Commit
} catch (RuntimeException e) {
    回滚:Rollback
}

所以,为了保证事务性,所有的写操作在执行之后,必须有某个判定为失败的标准,且判断定为失败后,必须抛出RuntimeException或其子孙类异常!

  • Spring JDBC默认对RuntimeException进行回滚处理,有必要的话,也可以配置为其它异常类型

11. 类别管理–添加类别–控制器层

12. 类别管理–添加类别–前端页面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青木编码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值