Day03

127 阅读5分钟

关于Service

Service:业务,它是Mapper的调用者,也是被Controller调用的组件。

Service的主要作用是设计业务流程、业务逻辑,以保障数据的有效性、安全性、完整性。

Service在代码中的表现通常包含接口与实现类。

Service的方法声明原则:

  • 返回值类型:仅以操作成功为前提来设计

    • 失败将通过抛出异常来表示
  • 方法名称:自定义

  • 参数列表:通常根据客户端提交的数据来设计,对于相关的数据,可以封装

添加相册--Service

业务规则:相册名称必须唯一。

需要实现“检查相册名称是否被占用”的检查功能,可以通过数据库查询来实现,相关的SQL语句大致是:

select * from pms_album where name=?

或者:

select count(*) from pms_album where name=?

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

int countByName(String name);

AlbumMapper.xml中配置以上抽象方法映射的SQL语句:

<!-- int countByName(String name); -->
<select id="countByName" resultType="int">
    SELECT count(*) FROM pms_album WHERE name=#{name}
</select>

AlbumMapperTests中编写并执行测试:

在项目的根包下创建pojo.dto.AlbumAddNewDTO类,用于封装客户端提交的数据(客户端会把数据提交到Controller,而Controller会使用这些数据来调用Service的方法):

@Data
public class AlbumAddNewDTO implements Serializable {
    private String name;
    private String description;
    private Integer sort;
}

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

public interface IAlbumService {
    void addNew(AlbumAddNewDTO albumAddNewDTO);
}

在项目的根包下创建service.impl.AlbumServiceImpl类,实现以上接口:

@Service
public class AlbumServiceImpl implements IAlbumService {
    
    @Autowired
    private AlbumMapper albumMapper;
    
    public void addNew(AlbumAddNewDTO albumAddNewDTO) {
        // 调用参数对象的getName()得到尝试添加的相册的名称
        // 调用Mapper对象的countByName()执行统计查询
        // 判断统计结果是否大于0
        // -- 是:名称被占用,抛出异常,例如:throw new RuntimeException()
        
        // 创建Album对象
        // 调用BeanUtils.copyProperties(源,目标)将参数对象中的属性复制到Album对象中
        // 调用Mapper对象的insert()执行插入相册数据
    }
}

完成后,在src/test/java的根包下创建service.AlbumServiceTests测试类,编写并执行测试:

@Slf4j
@SpringBootTest
public class AlbumServiceTests {
    
    @Autowired
    IAlbumService service;
    
    @Test
    void xxx() {
        
    }
}

关于异常

为了避免捕获并处理异常时产生歧义,在Service的实现过程中,如果需要通过抛出异常来表示某种“失败”,应该抛出自定义异常!

在项目的根包下创建ex.ServiceException类,继承自RuntimeException

package cn.tedu.csmall.product.ex;

public class ServiceException extends RuntimeException {
}

关于继承自RuntimeException,主要原因有:

  • 第1点:因为Spring MVC框架有统一处理异常的机制,所以,Service方法始终抛出异常,Controller方法也始终抛出异常,则没有必要通过throws关键字声明抛出,如果继承的父级异常不是RuntimeException,必须在各Service方法和Controller方法上都声明抛出
  • 第2点:待定

添加相册-Controller

首先,需要在pom.xml中添加spring-boot-starter-web依赖项,目前,项目中已经添加spring-boot-starter依赖项,而spring-boot-starter-web包含此依赖项,所以,只需要将现有的spring-boot-starter改为spring-boot-starter-web即可:

<!-- Spring Boot支持Spring MVC的依赖项 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

在项目的根包下创建controller.AlbumController类,添加@RestController,表示此类是一个响应数据的控制器类,并在类中自动装配IAlbumService接口类型的对象,在类中添加方法处理“添加相册”的请求:

@RestController
public class AlbumController {

    @Autowired
    private IAlbumService albumService;

    // http://localhost:8080/album/add-new?name=test001&description=hahahaha&sort=100
    // http://localhost:8080/album/add-new
    @RequestMapping("/album/add-new")
    public int addNew(AlbumAddNewDTO albumAddNewDTO) {
        try {
            albumService.addNew(albumAddNewDTO);
            return 1;
        } catch (ServiceException e) {
            return 0;
        }
    }

}

关于以上处理请求的方法:

  • 返回值类型:根据期望响应到客户端的信息类型来设计,如果返回值类型是自定义的数据类型,则响应时会由Spring MVC框架自动转换成JSON格式的字符串
  • 方法名称:自定义
  • 参数列表:

关于响应结果类型

在项目的根包下创建web.JsonResult类,在类中声明需要响应到客户端的数据对应的属性:

@Data
public class JsonResult implements Serializable {

    private Integer state;
    private String message;

}

然后,在控制器类中,在处理请求的方法上,将返回值类型改为以上类型,并在方法中返回此类型的对象:

@RequestMapping("/album/add-new")
public JsonResult addNew(AlbumAddNewDTO albumAddNewDTO) {
    try {
        albumService.addNew(albumAddNewDTO);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setState(1);
        jsonResult.setMessage("添加相册成功!");
        return jsonResult;
    } catch (ServiceException e) {
        JsonResult jsonResult = new JsonResult();
        jsonResult.setState(0);
        jsonResult.setMessage("添加相册失败,相册名称已经被占用!");
        return jsonResult;
    }
}

完成后,重启项目,再次访问,可以看到服务器端响应了JSON格式的结果。

以上代码中,使用了较多的代码来完成“创建对象、为属性赋值”,非常臃肿,可以考虑简化:

  • JsonResult中添加全参数构造方法

  • JsonResult中设计链式的Setter

    • 或直接在类上添加@Accessors(chain = true)
  • JsonResult中添加静态方法

例如,在JsonResult中添加静态方法:

public static JsonResult ok() {
    JsonResult jsonResult = new JsonResult();
    jsonResult.state = 1;
    return jsonResult;
}

public static JsonResult fail(Integer state, String message) {
    JsonResult jsonResult = new JsonResult();
    jsonResult.state = state;
    jsonResult.message = message;
    return jsonResult;
}

则控制器类中处理请求的方法可以调整为:

@RequestMapping("/album/add-new")
public JsonResult addNew(AlbumAddNewDTO albumAddNewDTO) {
    try {
        albumService.addNew(albumAddNewDTO);
        return JsonResult.ok();
    } catch (ServiceException e) {
        return JsonResult.fail(0, "添加相册失败,相册名称已经被占用!");
    }
}

根据ID删除相册--Mapper

实现此功能需要执行的SQL语句大致是:

delete from pms_album where id=?

此功能此前已经实现。

根据ID删除相册--Service

IAlbumService中添加抽象方法:

void delete(Long id);

AlbumServiceImpl中实现以上抽象方法:

public void delete(Long id) {
    // 调用Mapper对象的getStandardById()执行查询
    // 判断查询结果是否为null
    // 是:数据不存在,抛出异常
    
    // 调用Mapper对象的deleteById()方法执行删除
}

AlbumServiceTests中编写并执行测试:

根据ID删相册--Controller

AlbumController中添加处理此请求的方法:

// http://localhost:8080/album/delete?id=1
@RequestMapping("/album/delete")
public JsonResult delete(Long id) {
    try {
        albumService.delete(id);
        return JsonResult.ok();
    } catch (ServiceException e) {
        return JsonResult.fail(0, "删除相册失败,尝试删除的相册数据不存在!");
    }
}