Spring Service功能作用详细讲解
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);
关于抽象方法的名称:可以完全自定义,当前业务是“添加相册”,可以使用addNew
、add
等。
关于抽象方法的参数列表:大多参数是由客户端提交到控制器,再由控制器调用时传递过来的参数,另外,也可能是控制器处理出来的某些数据(例如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("添加数据失败!名称已经被占用!"); }
到此这篇关于Spring Service功能作用详细讲解的文章就介绍到这了,更多相关Spring Service内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringMVC+Mybatis实现的Mysql分页数据查询的示例
本篇文章主要介绍了SpringMVC+Mybatis实现的Mysql分页数据查询的示例,具有一定的参考价值,有兴趣的可以了解一下2017-08-08
最新评论