亲宝软件园·资讯

展开

Spring Service功能作用详细讲解

居然天上楼 人气:0

1. Spring项目中的核心组成部分

项目的核心组成部分图解:

2. Spring项目中的Service

2.1 Service的功能作用

Service是项目中用于处理业务逻辑的,因为每种数据在做某种操作时,应该都有某些规则:

这些规则是用于保障数据的有效性、安全性的,使得数据可以随着我们设定的规则而产生或发生变化!

在项目中,关于Service的开发,通常是先定义接口,再定义类实现此接口,接口名通常使用“数据类型Service”这样格式的名称,而实现类通常是在接口名的基础上再添加Impl后缀。

在《阿里巴巴Java开发手册》中的规约:

【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部 的实现类用 Impl 的后缀与接口区别。

2.2 Service的实现

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

public interface IAlbumService {}

然后,在根包下创建service.impl.AlbumServiceImpl类,此类需要实现以上的IAlbumService接口:

public class AlbumServiceImpl implements IAlbumService {}

文件结构如下图所示:

然后,需要在接口中设计“添加相册”的抽象方法:

xx xx(xx);

关于抽象方法的名称:可以完全自定义,当前业务是“添加相册”,可以使用addNewadd等。

关于抽象方法的参数列表:大多参数是由客户端提交到控制器,再由控制器调用时传递过来的参数,另外,也可能是控制器处理出来的某些数据(例如Session中的当前登录用户信息),本次的参数应该包含:相册名称、相册简介、相册的排序序号,可以将这3个数据封装到自定义的DTO类中,并使用DTO类型作为参数。

关于抽象方法的返回值类型:仅以操作成功为前提来设计返回值类型,如果操作失败,将抛出异常。

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

public class AlbumAddNewDTO {
    private String name;
    private String description;
    private Integer sort;
}

并在IAlbumService接口中添加抽象方法:

void addNew(AlbumAddNewDTO albumAddNewDTO);

然后,在AlbumServiceImpl中实现此抽象方法:

@Slf4j
@Service
public class AlbumServiceImpl implements IAlbumService  {
    @Autowired
    private AlbumMapper albumMapper;
    public AlbumServiceImpl() {
        log.debug("创建业务对象:AlbumServiceImpl");
    }
    @Override
    public void addNew(AlbumAddNewDTO albumAddNewDTO) {
        // 【稍后再实现】应该保证此相册的名称是唯一的
        // 创建Album类型的对象
        // 调用BeanUtils.copyProperties(源对象, 目标对象)将参数的属性值复制到新创建的Album对象中
		// 调用albumMapper的int insert(Album album)方法插入相册数据
    }
}

初步实现为:

package cn.tedu.csmall.product.service.impl;
import cn.tedu.csmall.product.mapper.AlbumMapper;
import cn.tedu.csmall.product.pojo.dto.AlbumAddNewDTO;
import cn.tedu.csmall.product.pojo.entity.Album;
import cn.tedu.csmall.product.service.IAlbumService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class AlbumServiceImpl implements IAlbumService  {
    @Autowired
    private AlbumMapper albumMapper;
    public AlbumServiceImpl() {
        log.debug("创建业务对象:AlbumServiceImpl");
    }
    @Override
    public void addNew(AlbumAddNewDTO albumAddNewDTO) {
        // 【稍后再实现】应该保证此相册的名称是唯一的
        // 创建Album类型的对象
        Album album = new Album();
        // 调用BeanUtils.copyProperties(源对象, 目标对象)将参数的属性值复制到新创建的Album对象中
        BeanUtils.copyProperties(albumAddNewDTO, album);
        // 调用albumMapper的int insert(Album album)方法插入相册数据
        albumMapper.insert(album);
    }
}

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

package cn.tedu.csmall.product.service;
import cn.tedu.csmall.product.pojo.dto.AlbumAddNewDTO;
import cn.tedu.csmall.product.pojo.entity.Album;
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 AlbumServiceTests {
    @Autowired
    IAlbumService service;
    @Test
    void addNew() {
        AlbumAddNewDTO album = new AlbumAddNewDTO();
        album.setName("测试数据9998");
        album.setDescription("测试数据的简介");
        album.setSort(99); // 注意:sort值必须是[0, 255]之间的
        service.addNew(album);
        log.debug("添加数据完成!");
    }
}

在具体实现过程中,还应该保证此次尝试添加的相册的名称是唯一的!

可以通过查询数据库来得知尝试添加的相册的名称是否已经被使用,需要执行的SQL语句可以是:

select id from pms_album where name=?
select count(*) from pms_album where name=?

可以选择使用以上第2种查询来检验相册名称是否已经被使用,则应该在

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.java中编写并执行测试:

@Test
void countByName() {
    String name = "测试数据";
    int count = mapper.countByName(name);
    log.debug("根据名称【{}】统计完成,结果:{}", name, count);
}

接下来,可以在Service的实现过程中进行检查,例如:

String albumName = albumAddNewDTO.getName();
int count = albumMapper.countByName(albumName);
if (count > 0) {
    // 相册名称已经被使用,将不允许添加此相册,应该抛出异常
} else {
    // 相册名称没有被使用,可以将此相册数据插入到数据库中
}

提示:以上代码中,由于满足if条件时将抛出异常,所以,可以不必使用else,并且,在后续的编程中,当需要执行某些判断时,应该优先根据“抛出异常”或“终止当前方法的执行”来设计if的条件!即:

if (count > 0) {

// 相册名称已经被使用,将不允许添加此相册,应该抛出异常 }

// 相册名称没有被使用,可以将此相册数据插入到数据库中

具体实现为:

@Override
public void addNew(AlbumAddNewDTO albumAddNewDTO) {
    // 应该保证此相册的名称是唯一的
    String albumName = albumAddNewDTO.getName();
    int count = albumMapper.countByName(albumName);
    if (count > 0) {
        throw new RuntimeException();
    }
    // 创建Album类型的对象
    Album album = new Album();
    // 调用BeanUtils.copyProperties(源对象, 目标对象)将参数的属性值复制到新创建的Album对象中
    BeanUtils.copyProperties(albumAddNewDTO, album);
    // 调用albumMapper的int insert(Album album)方法插入相册数据
    albumMapper.insert(album);
}

为了避免测试时因为相册名称冲突出现异常而导致测试失败,应该在测试时捕获所抛出的异常,例如:

@Test
void addNew() {
    AlbumAddNewDTO album = new AlbumAddNewDTO();
    album.setName("测试数据9998");
    album.setDescription("测试数据的简介");
    album.setSort(99); // 注意:sort值必须是[0, 255]之间的
    try {
        service.addNew(album);
        log.debug("添加数据完成!");
    } catch (RuntimeException e) {
        log.debug("添加数据失败!名称已经被占用!");
    }
}

关于以上实现过程中抛出的异常,使用的是RuntimeException,是不合适的!因为程序出现RuntimeException的原因有很多,例如空指针异常、数组下标越界异常、类型转换异常,都属于RuntimeException,如果“相册名称被占用”时抛出RuntimeException,则此方法的调用者很难区分出现异常的真正原因!

通常,建议自定义异常,并且,当视为失败时,抛出此自定义异常的对象!

则在根包下创建ex.ServiceException类,继承自RuntimeException

public class ServiceException extends RuntimeException {}

提示:本次自定义的异常应该继承自RuntimeException。

然后,在AlbumServiceImpl中添加相册时,如果相册名称被使用,则抛出ServiceException类型的异常:

if (count > 0) {
    throw new ServiceException();
}

并且,在测试中,捕获的异常也改为ServiceException

try {
    service.addNew(album);
    log.debug("添加数据完成!");
} catch (ServiceException e) {
    log.debug("添加数据失败!名称已经被占用!");
}

加载全部内容

相关教程
猜你喜欢
用户评论