目录
1. Spring项目中的核心组成部分
2. Spring项目中的Service
2.1 Service的功能作用
2.2 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("添加数据失败!名称已经被占用!");
}
个人主页:居然天上楼
感谢你这么可爱帅气还这么热爱学习~~
人生海海,山山而川
你的点赞👍 收藏⭐ 留言📝 加关注✅
是对我最大的支持与鞭策