谈SSM的Service层两种实现结构
概述
最近由于入职大厂,所以文章也少了,并不是自己懈怠了,而是大厂的保密措施不允许我上班写文章了,更何况还有无尽的加班。。。。。。。。。唉,所幸现在习惯了好多,现在觉着该记录一下知识了。目前市场上,要实现Java项目主要有Maven和Gradle两种框架,其中Gradle是新兴势力,Maven是老牌势力,其实我更加喜欢用Gradle,因为更加便捷,无奈,现在市面上Maven仍然是主流,因为稳定,更因为许多老项目都是Maven搭建的。。。。。。今天这篇文章主要是介绍两种实现springboot的方式,以防以后忘记
第一种:Domain(entity)-Mapper-Service-Controller
这种方式依赖于mybatis-plus的BaseMapper和ServiceImpl<Mapper,entity>实现,效果就是没有了Service接口类,这种方式是我的老师教给我的方式,我也认为是最简单的方式,也是我的最爱,不过却是不常见的
例如:
实体类层:Domain(entity)
package com.example.pmxt.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@TableName("sys_region")
public class Region {
@NotNull(message = "id不能为空")
// @NotBlank(message = "id不能为空")
// @Length(min = 6,max = 6,message = "长度必须为6")
@ExcelProperty("编号")
private Integer id;
@NotNull(message = "名称不能为空")
@NotBlank(message = "名称不能为空")
@ExcelProperty("名称")
private String name;
@NotNull(message = "父类id不能为空")
// @NotBlank(message = "父类id不能为空")
// @Length(min = 6,max = 6,message = "长度必须为6")
@ExcelProperty("父类id")
private Integer parentId;
@ExcelProperty("备注")
private String remark;
}
这一层在两种方式中并无区别,就是建立实体类,方便管理。
Mapper层
这一层主要有两种实现方式:注解和写<这种标签,注解比较多,因为简便,标签的话,应该是老程序员专属吧,注解如下:
package com.example.pmxt.modules.region;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.pmxt.domain.Region;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface RegionMapper extends BaseMapper<Region> {
// @Select("SELECT sys_region.id,sys_region.name,sys_region.parent_id from sys_region,busi_bd.szd where sys_region.id = busi_bd.szd union SELECT sys_region.id,sys_region.name,sys_region.parent_id from sys_region,busi_bd where sys_region.id = concat(left(busi_bd.szd,4),'00') union SELECT sys_region.id,sys_region.name,sys_region.parent_id from sys_region,busi_bd where sys_region.id = concat(left(busi_bd.szd,2),'0000');")
// List<Region> selectbybd();
@Select("select * from sys_region where name = #{name}")
Region getByname(String name);
@Select("select parent_id from sys_region where name = #{name};")
Integer getparentIdByName(String name);
@Select("select id from sys_region where name = #{name};")
Integer getIdByName(String name);
@Select("select * from sys_region where concat(id) like concat(left(concat(#{id}),2),'%');")
List<Region> getByProvince(Integer id);
@Select("select * from sys_region where concat(id) like concat(left(concat(#{id}),4),'%');")
List<Region> getByCity(Integer id);
@Select("select * from sys_region where id = #{id}")
List<Region> getByCounty(Integer id);
}
注意BaseMapper来自于Mybatis-plus导包
主要就是注解+SQL语句后,下面跟个方法名
Service层
这块是两种方式差别最大的一个,这种方式只需要一个Service类就好了,但是需要一个工具类BaseService。
BaseService:
package com.example.pmxt.common;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
public class BaseService<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
}
所有的Service都可以继承这个BaseService,这样就不用写ServiceImpl了,只需要卸载Service中就行了
Service:
package com.example.pmxt.modules.region;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.example.pmxt.common.BaseService;
import com.example.pmxt.domain.Region;
import lombok.SneakyThrows;
import org.apache.commons.compress.utils.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Service
@Transactional
public class RegionService extends BaseService<RegionMapper,Region> {
private final RegionMapper mapper;
@Autowired
public RegionService(RegionMapper mapper) {
this.mapper = mapper;
}
@SneakyThrows
public boolean importFileToDB(boolean deleteHistory, MultipartFile file) {
//是否删除数据库表中历史数据
if (deleteHistory) {
this.remove(null);
}
//读取excel表格中数据
EasyExcel.read(file.getInputStream(), Region.class, new ReadListener<Region>() {
//定义一个常量BATCH_SIZE与一个列表ls
private static final int BATCH_SIZE = 1000;
private List<Region> ls = Lists.newArrayList();
//重写invoke方法
@Override
public void invoke(Region data, AnalysisContext context) {
//System.err.println(data);
//列表里添加Flowfee类的数据
ls.add(data);
//每1000条放到数据库里面
if (ls.size() >= BATCH_SIZE) {
RegionService.this.saveBatch(ls);
ls.clear();
}
}
//重写doAfterAllAnalysed方法,处理剩下的数据,例如1050条,其中1000条在上面已经放到数据库,剩下的50条下面处理
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
RegionService.this.saveBatch(ls);
}
}).sheet().doRead();
return true;
}
public Integer getparentIdByName(String name){
return mapper.getparentIdByName(name);
}
public Integer getIdByName(String name){
return mapper.getIdByName(name);
}
public List<Region> gettable(String name){
if (mapper.getByname(name)==null){
return null;
}
Integer id = mapper.getIdByName(name);
if ("0000".equals(String.valueOf(id).substring(2))){
return mapper.getByProvince(id);
}else if ("00".equals(String.valueOf(id).substring(4))){
return mapper.getByCity(id);
}else {
return mapper.getByCounty(id);
}
}
}
Controller层
这一层是用于给前端写接口的类,两种方法基本一致
package com.example.pmxt.modules.region;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.example.pmxt.common.ReturnResult;
import com.example.pmxt.domain.Region;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@Validated
@RequestMapping("/regions")
public class RegionController {
private final RegionService service;
@Autowired
public RegionController(RegionService service) {
this.service = service;
}
@GetMapping()
public ReturnResult getAllRegion(){
//这里用的MyBatis Plus
List<Region> dtos = service.list();
//配置
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 自定义属性名 都要默认值的
treeNodeConfig.setIdKey("id");
// 最大递归深度
treeNodeConfig.setDeep(4);
//转换器
List<Tree<Integer>> treeNodes = TreeUtil.build(dtos, 0, treeNodeConfig,
(treeNode, tree) -> {
tree.setId(treeNode.getId());
tree.setParentId(treeNode.getParentId());
tree.setName(treeNode.getName());
});
return ReturnResult.buildSuccessResult(treeNodes);
}
@GetMapping("/name")
public ReturnResult getByName(@NotNull(message = "parentid不能为空") String name){
Integer parentid = service.getparentIdByName(name);
//这里用的MyBatis Plus
List<Region> dtos = service.gettable(name);
if (dtos == null){
return ReturnResult.buildFailureResult("该地区不存在");
}
//配置
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 自定义属性名 都要默认值的
treeNodeConfig.setIdKey("id");
// 最大递归深度
treeNodeConfig.setDeep(3);
//转换器
List<Tree<Integer>> treeNodes = TreeUtil.build(dtos, parentid, treeNodeConfig,
(treeNode, tree) -> {
tree.setId(treeNode.getId());
tree.setParentId(treeNode.getParentId());
tree.setName(treeNode.getName());
});
return ReturnResult.buildSuccessResult(treeNodes);
}
// @GetMapping("/selectbybd")
// public ReturnResult selectbybd(){
// return ReturnResult.buildSuccessResult(service.selectbybd());
// }
@PostMapping()
public ReturnResult addRegion(@RequestBody @Validated Region region){
if (region == null){
return ReturnResult.buildFailureResult("行政区划不能为空");
}
return ReturnResult.buildSuccessResult(service.save(region));
}
@DeleteMapping("/{id}")
public ReturnResult deleteRegionById(@PathVariable @NotNull(message = "id不能为空") Integer id){
if(id == null){
return ReturnResult.buildFailureResult("id不能为空");
}
Region r = service.getById(id);
if (r != null){
return ReturnResult.buildSuccessResult(service.removeById(id));
}else {
return ReturnResult.buildFailureResult("id不存在");
}
}
@PutMapping()
public ReturnResult updateRegion(@RequestBody Region region){
if (region == null){
return ReturnResult.buildFailureResult("行政区划不能为空");
}
Region r = service.getById(region.getId());
if (r != null) {
return ReturnResult.buildSuccessResult(service.updateById(region));
}else {
return ReturnResult.buildFailureResult("id不存在");
}
}
//@PostMapping定义IP地址映射方法与位置,@ApiOperation是swagger测试注解
@PostMapping("/uploadregion")
public ReturnResult excelToDatabase( @RequestParam boolean deleteHistory,
@RequestPart("file") MultipartFile file) {
if(service.importFileToDB(deleteHistory, file)){
return ReturnResult.buildSuccessResult("添加成功",null);
}else {
return ReturnResult.buildFailureResult("添加失败",null);
}
}
@DeleteMapping("deletes")
public ReturnResult deleteregions(Integer [] ids){
if (ids.length == 0){
return ReturnResult.buildFailureResult("行政区划不能为空");
}else {
return ReturnResult.buildSuccessResult(service.removeBatchByIds(Arrays.asList(ids)));
}
}
}
第二种:Domain(entity)-Mapper-Service-ServiceImpl-Controller
区别:Service层在上面是没有接口的,就是一个纯Service类,而在这中方式里,Service层分为了impl和Service接口,仔细观察可以看出,上面第一种方法中的Service类,就是第二种种的ServiceImpl
那么为什么Service层要写接口,像上面一样不写不是可以少写一个文件吗?
原因:在面向对象的软件设计原则中,有一个依赖倒置的软件设计原则,要求高层模块不能直接依赖于低层模块,应该依赖于抽象;在代码中Controller层为高层模块,Service为低层模块,所以Controller不能直接依赖于Service类,而要依赖于抽象的Service接口
那么,问题又来了,一定要满足面向对象的软件设计原则吗?
答案:由于我们通常写的代码不需要进行额外拓展,所以不写Service接口也是没问题的,如果需要额外拓展,那就需要遵守软件设计原则了(写Service接口,大厂基本上都得写。。。)
这种方法主要就差在Service层,其余层可以参考上面
Service层
Service:
public interface UserSerivce extends IService<User> {
}
这种方法Service要先继承IService工具类,实际上ServiceImpl已将包含了IService中的所有方法
ServiceImpl:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserSerivce {
}
这就需要多写一个Impl类,实际上两者原理一样,第二种多了个ServiceImpl<UserMapper, User> 实现 UserSerivce接口,而UserSerivce接口又要继承IService
最后说明这两种方法应该在Maven和Gradle框架中都可以使用,至少Maven是都可以的,因为我试过了,,Gradle中我用过第一种,第二种还没用过,不过既然原理一致,应该都可以使用;
这篇文章只当自己对喜爱方法的纪念吧。。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通