java开发MVC三层架构上再加一层Manager层原理详解
作者:飘渺Jam 发布时间:2023-06-14 06:10:51
MVC三层架构
我们在刚刚成为程序员的时候,就会被前辈们 “教育” 说系统的设计要遵循 MVC(Model-View-Controller)架构。它将整体的系统分成了 Model(模型),View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是一种标准的软件分层架构。
MVC分层架构是架构上最简单的一种分层方式。为了遵循这种分层架构我们在构建项目时往往会建立这样三个目录:controller、service 和 dao,它们分别对应了表现层、逻辑层还有数据访问层。
每层的作用如下:
Controller层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理。
Service层:主要是处理业务逻辑和事务
Dao层:负责与底层数据库MySQL,Oracle等进行数据交互
可是随着我们的业务逻辑越来复杂,代码写的越来越多,这种简单的三层架构的问题也越来越明显。
MVC架构弊端
传统的MVC分层有以下几个很明显的问题:
Service层代码臃肿
Service层很容易出现大事务,事务嵌套,导致问题很多,而且极难排查
dao层参杂业务逻辑
dao层sql语句复杂,关联查询比较多
为了解决这个问题,我们参考《alibaba java开发手册》,在Service层之下再独立出一个通用业务处理层(Manager层)
在这个分层架构中主要增加了 Manager 层,它与 Service 层的关系是:Manager 层提供原子的服务接口,Service 层负责依据业务逻辑来编排原子接口。
Manager层的特征
在《alibaba java开发手册》中是这样描述Manager层的:
Manager 层:通用业务处理层,它有如下特征:
对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口;对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;与 DAO 层交互,对多个 DAO 的组合复用。
在实际开发中我们可以这样使用Manager层
复杂业务,service提供数据给Manager层,负责业务编排,然后把事务下沉到Manager层,Manager层不允许相互调用,不会出现事务嵌套。
专注于不带业务sql语言,也可以在manager层进行通用业务的dao层封装。
避免复杂的join查询,数据库压力比java大很多,所以要严格控制好sql,所以可以在manager层进行拆分,比如复杂查询。
当然对于简单的业务,可以不使用Manager层。
Manager层使用案例
这里我们举个例子说明一下Manager层的使用场景:
假设你有一个用户系统,他有一个获取用户信息的接口,它调用逻辑Service层的 getUser
方法,getUser
方法又和 User DB
交互获取数据。如下图左边展示部分。
这时,产品提出一个需求,在 APP 中展示用户信息的时候,如果用户不存在,那么要自动给用户创建一个用户。同时,要做一个 HTML5 的页面,HTML5 页面要保留之前的逻辑,也就是不需要创建用户。
此时按照传统的三层架构,逻辑层的边界就变得不清晰,表现层也承担了一部分的业务逻辑,因为我们往往会在表现层Controller中增加业务逻辑处理,将获取用户和创建用户接口编排起来。
而添加Manager层以后,Manager 层提供创建用户和获取用户信息的接口,而 Service 层负责将这两个接口组装起来。这样就把原先散布在表现层的业务逻辑都统一到了 Service 层,每一层的边界就非常清晰了。
接下来我们看一段实际代码说明一下Service层与Manager层如何进行区分?
@Transactional(rollbackFor = Throwable.class)
public Result<String> upOrDown(Long departmentId, Long swapId) {
// 验证 1
DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
if (departmentEntity == null) {
return Result.error("部门xxx不存在");
}
// 验证 2
DepartmentEntity swapEntity = departmentDao.selectById(swapId);
if (swapEntity == null) {
return Result.error("部门xxx不存在");
}
// 验证 3
Long count = employeeDao.countByDepartmentId(departmentId);
if (count != null && count > 0) {
return Result.error("员工不存在");
}
// 操作数据库 4
Long departmentSort = departmentEntity.getSort();
departmentEntity.setSort(swapEntity.getSort());
departmentDao.updateById(departmentEntity);
swapEntity.setSort(departmentSort);
departmentDao.updateById(swapEntity);
return Result.OK("success");
}
上面代码在我们在我们采用三层架构时经常会遇到,那么它有什么问题呢?
上面的代码是典型的长事务问题(类似的还有调用第三方接口),前三步都是使用 connection 进行验证操作,但是由于方法上有@Transactional 注解,所以这三个验证都是使用的同一个 connection。
若对于复杂业务、复杂的验证逻辑,会导致整个验证过程始终占用该 connection 连接,占用时间可能会很长,直至方法结束,connection 才会交还给数据库连接池。
对于复杂业务的不可预计的情况,长时间占用同一个 connection 连接不是好的事情,应该尽量缩短占用时间。
说明:对于@Transactional 注解,当 spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,如果业务并没有进入到最终的 操作数据库环节,那么就没有必要获取连接并开启事务,应该直接将 connection 返回给数据库连接池,供其他使用。
所以我们在加入Manager层以后可以这样写:
DepartmentService.java
public Result<String> upOrDown(Long departmentId, Long swapId) {
// 验证 1
DepartmentEntity departmentEntity = departmentDao.selectById(departmentId);
if (departmentEntity == null) {
return Result.error("部门xxx不存在");
}
// 验证 2
DepartmentEntity swapEntity = departmentDao.selectById(swapId);
if (swapEntity == null) {
return Result.error("部门xxx不存在");
}
// 验证 3
Long count = employeeDao.countByDepartmentId(departmentId);
if (count != null && count > 0) {
return Result.error("员工不存在");
}
// 操作数据库 4
departmentManager.upOrDown(departmentSort,swapEntity);
return Result.OK("success");
}
DepartmentManager.java
@Transactional(rollbackFor = Throwable.class)
public void upOrDown(DepartmentEntity departmentEntity ,DepartmentEntity swapEntity){
Long departmentSort = departmentEntity.getSort();
departmentEntity.setSort(swapEntity.getSort());
departmentDao.updateById(departmentEntity);
swapEntity.setSort(departmentSort);
departmentDao.updateById(swapEntity);
}
将数据在 service 层准备好,然后传递给 manager 层,由 manager 层添加 @Transactional
事务注解进行数据库操作。
来源:https://blog.csdn.net/jianzhang11/article/details/120884260


猜你喜欢
- 异常分类可查的异常(checked exceptions):Exception下除了RuntimeException外的异常不可查的异常(u
- 需求背景最近的一个项目,在项目基本完工的阶段,客户提出要将所有业务操作的日志记录到数据库中,并且要提取一些业务的关键信息(比如交易单号)体现
- SpringMVC服务器验证一种是有两种方式,一种是基于Validator接口,一种是使用Annotaion JSR-303标准的验证,下面
- 目录规则(来自百度百科,康威生命游戏词条)控制台实现的关键接口代码实现规则(来自百度百科,康威生命游戏词条)游戏开始时,每个细胞随机地设定为
- 随着使用Spring进行开发的个人和企业越来越多,Spring从一个单一简介的框架变成了一个大而全的开源软件,最直观的变化就是Spring需
- java的String对象底层是有字符数组存储的,理论上char[] 最大长度是int的最大值,实际思路:首先,String字面
- 不适用click而用touch自定义监听:class myOnGestureListener extends GestureDetector
- 本文介绍了浅谈Java的两种多线程实现方式,分享给大家。具有如下:一、创建多线程的两种方式Java中,有两种方式可以创建多线程:1 通过继承
- java中引用数据类型有哪些Java中有俩种数据类型,其中主要有8中基本数据类型和引用数据类型,除了8中基本数据类型以外都是引用数据类型,8
- 前言本篇文章会说下如何使用并且要用麻烦的自定义 view 去实现时间轴效果,以及如何分析、实现自定义 view。 需要具备的知识:Paint
- 本文实例为大家分享了Android实现简单点赞动画的具体代码,供大家参考,具体内容如下思路1、找到Activity中DecorView的Ro
- 1. Resource Basics(1) Manifest Resources(资源清单)资源在编译期间添加到程序集。如果要将资源嵌入到程
- OpenFeign介绍一开始,我们使用原生的 DiscoveryClient 发现服务和使用RestTemplate进行服务间调用,然后我们
- 某少年宫引进了一批机器人小车。可以接受预先输入的指令,按指令行动。小车的基本动作很简单,只有3种:左转(记为L),右转(记为R),向前走若干
- 在Android中,Activity主要负责前台页面的展示,Service主要负责需要长期运行的任务,所以在我们实际开发中,就会常常遇到Ac
- 一、使用QueryByExampleExecutor1. 继承MongoRepositorypublic interface Student
- 目录Java反射超详解1.反射基础1.1Class类1.2类加载2.反射的使用2.1Class对象的获取 2.2Construct
- 新建一个表:create table abc(id int IDENTITY(1,1) NOT NULL,name nvarchar(100
- 本文实例讲述了C#中Out与Ref的区别,可以加深C#程序设计人员对Out和Ref用法的理解,具体分析如下:一、区别分析:Out和Ref作为
- 1. ObsoleteAttributeObsoleteAttribute 适用于除组件、模块、参数和返回值以外的所有程序元素。将元素标记为