关于在Java三层架构下传统crud业务的一般逻辑和几点思考
关于在Java三层架构下传统crud业务的一般逻辑和几点思考
持久层逻辑
在传统的项目业务中,持久层的逻辑其实较为简单,就是负责与数据库进行交互。那么在进行持久层开发时,需要注意以下问题。
- sql语句逻辑。
- sql语句调优。
- 参数传递、返回值类型的选择。
sql语句逻辑
所谓持久层,其实就是希望持久层逻辑能够得到长期有效的利用,那么从sql语句的逻辑设计上,就要考虑其复用性和高效性。首先通过例子来体现一下sql语句的复用性:
假设有这样一个业务场景,业务层获取数据需要以下两个相似持久层的sql语句
<select id="queryUserInfoByName" parameterType="string" resultType="user">
select * from User where username = #{username}
</select>
<select id="queryUserInfoByPhone" parameterType="string" resultType="user">
select * from User where phone = #{phone}
</select>
我们可以通过<sql>
标签,把公共的部分定义成一个块:
<sql id="queryUserInfo">
select * from User where
</sql>
然后在需要该段sql的地方通过include标签引入即可,此时mysql语句变成这样:
<select id="queryUserInfoByName" parameterType="string" resultType="user">
<include refid="queryUserInfo"></include> username = #{username}
</select>
<select id="queryUserInfoByPhone" parameterType="string" resultType="user">
<include refid="queryUserInfo"></include> phone = #{phone}
</select>
上述这种对sql语句的片段复用其优势便是能够提高开发效率,但是代码的可读性较差。实际项目中是否需要使用需要结合实际情况。
另一种情况,当随着项目的进行和维护升级,业务也会逐渐变的越来越复杂,可能涉及到的sql语句也会变的越来越复杂。这个时候,我们应该尽量避免sql语句的复杂度越来越高,因为随着sql语句复杂度的提高,其语句的复用性也会越来越差。尝试将一个复杂的sql语句拆分成多个小的、简单的sql语句,这里并不是指上述那样简单的提取共性sql语句复用,而是以dao层接口为单位拆分,即将一个复杂业务需要调用的sql语句分成几个小的,可单独运行,具备完整逻辑的sql语句。在业务层处理复杂业务时,可以通过调用多个dao层接口组合完成。这样才是真正的做到对sql的复用性。
sql语句调优
关于sql语句调优问题,可以参考网上关于mysql调优的相关说明(以下附上两个关于sql调优链接)
参数传递、返回值类型的选择
关于持久层的参数、返回值传递(这里以mybatis为例),目前普遍有两种选择,一种是使用实体类,另一种是使用map集合。
使用实体类的优点总结如下:
- 面向对象的良好诠释。
- 数据结构清晰,便于团队开发 & 后期维护。
- 代码足够健壮,可以排除掉编译期错误。
当然,其缺点就是会导致代码量增多,我们需要根据数据库表字段一个一个去建立对应的实体类(entity或者叫pojo),影响开发效率,当然,其中我们可以提取实体类中的共有属性作为父类,减少代码量。
使用map集合的优点如下:
- 灵活性强于实体类,易扩展,耦合度低。
- 写起来简单,无需构建实体类,代码量少。
- mybatis 查询的返回结果本身就是MAP,可能会比返回实体类快。
当然,其缺点也不可忽视:
- 实体类在数据输入编译期就会对一些数据类型进行校验,如果出错会直接提示。而map的数据类型则需要到sql层,才会进行处理判断。
- map的参数名称如果写错、或者多传、乱传,也是需要到sql层,才能判断出是不是字段写错,不利于调试等。相对而言实体类会在编译期间发现错误。
- 仅仅看方法签名,你不清楚map中所拥有的参数个数、类型、每个参数代表的意义。后期人员去维护,例如需要增加一个参数,如果项目较复杂,层次较多,则需要把每一层的代码都要了解清楚才能够知道传递了哪些参数,影响维护效率。
权衡利弊,如果团队开发还是使用实体类比较好,个人项目就无所谓了。追求高效开发,可以使用Map。
业务层逻辑
业务层可以说是整个项目开发过程中最重要的模块了,一个项目的一个功能实现都是由业务层编写业务逻辑完成的。业务层的实现一般遵循以下逻辑:
- 对持久层接口的调用。
- 业务逻辑处理(数据处理及补充、数据验证、异常处理)。
- 返回处理结果。
对持久层接口的调用
对持久层接口的调用其实是业务逻辑中的一部分,也是主要部分,上部分内容有介绍过,复杂的业务可能需要调用多个接口方法来实现,同时需要注意使用这些接口方法时需要传递的参数传递和返回值类型。
业务逻辑处理(数据处理及补充、数据验证、异常处理)
数据处理即是在对持久层接口方法的调用之前或之后,对数据进行业务相关处理,一般涉及到基本运算、统计等等,还可以根据上层或者下层的需要对现有数据进行补充,满足相应的业务需求。
数据验证是业务处理中很重要的一环,也是保证数据库安全以及保证客户端良好交互体验的重要环节,数据验证涉及到整个业务逻辑的每个角落,包括对业务层传递参数的合法性认证、对数据处理结果的验证,在一些特殊情况下还要涉及到事务、并发处理等等。通常数据验证伴随的是异常的处理,当用户错误的操作或者数据处理结果错误时,需要我们通过自定义异常的方式进行处理,分析业务逻辑中可能会出现的各种因人为或者系统出错导致的不良后果,在适当位置加上逻辑判断,并抛出自定义异常,以防止各种错误处理导致的数据库甚至系统出错。
异常处理在java中主要分成以下两大类:
unchecked exception(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。
在处理业务逻辑时,主要处理非检查异常,因为该类异常并不是由系统决定,而是由业务决定,举一个很简单的例子,人的年龄必须是整数且大于0,对于计算机而言,int age = 0本身并没有错,但在业务逻辑上,该数据是不合法的,需要做异常处理。
业务层的异常处理几乎都统一抛给上一层,即控制层,做一个全局的异常处理,如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。
返回处理结果
就返回结果来说,对于dao层的update、delete、insert操作,因为其业务只是对数据库进行输入,并未有输出,dao层返回结果也只有其受影响的行数。那么对于此类操作,业务层的返回结果一般都是返回受影响行数,也可以在此基础上做业务判断是否操作成功或失败,甚至不返回结果。
对于select操作,一般都是要向客户端响应数据,那么这些数据都会封装到实体类或者map集合中作为结果返回。项目中每一层封装结果的类对象都有统一的名字。
阿里巴巴java开发手册中的DO、DTO、BO、AO、VO、POJO定义
分层领域模型规约:
- DO( Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
- DTO( Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
- BO( Business Object):业务对象。 由Service层输出的封装业务逻辑的对象。
- AO( Application Object):应用对象。 在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
- VO( View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
- POJO( Plain Ordinary Java Object):在本手册中, POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
- Query:数据查询对象,各层接收上层的查询请求。 注意超过2个参数的查询封装,禁止使用Map类来传输。
领域模型命名规约:
- 数据对象:xxxDO,xxx即为数据表名。
- 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
- 展示对象:xxxVO,xxx一般为网页名称。
- POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO
详情可查看java中PO、BO、VO、DTO、POJO、DAO概念及其作用和项目示例图
在DAO层中,我们可能会根据数据库表设计字段来决定我们实体类的属性,但是这些属性可能不能满足上层业务或者客户端响应结果的需求,为此我们可能要在原有的实体类基础上增改相应的属性,生成新的实体类来封装数据,交到客户端进行呈现,最典型的便是控制层新定义的JsonResult类,他就是专门用来提交响应客户端数据的实体类。
控制层逻辑
控制层简单来说就是负责接收请求并响应数据,内部逻辑其实非常简单:接收请求中的参数,调用业务层方法,将返回结果封装到JsonResult中响应给客户端。
- 全局异常处理类
- 返回结果处理
全局异常处理类
全局异常处理类是项目设计中很重要的一环,首先保证整个项目的正常运行,不会因为异常抛出无法处理而导致系统错误,其二,全局异常处理会使得整个程序的流程清晰易懂,对于后期维护和异常排查有很大的帮助。
下面是全局处理异常的样例:
package com.cy.web;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.cy.vo.JsonResult;
//全局异常处理
@ControllerAdvice//使用该注解springmvc会认为他是一个控制层全局异常处理对象
public class GlobalExceptionHandler {
//JDK中的自带的日志API
@ExceptionHandler(RuntimeException.class)//注解中定义的异常类型(或其子类异常),是该方法可以处理的异常类型
@ResponseBody
//此方法参数接受了所有继承RuntimeException的异常类
public JsonResult doHandleRuntimeException(RuntimeException e){
e.printStackTrace();//也可以写日志、异常信息
return new JsonResult(e);//封装
}
}
该类在捕获各层的异常之后便会将异常信息封装到JsonResult中,响应给客户端,即我们在客户端操作的一切异常信息提示都由此而来
返回结果处理
对于控制层返回结果处理,最常见的就是使用JsonResult类,该类中除了封装了业务层的返回数据,同时也封装了状态码和状态信息:
package com.cy.vo;
public class JsonResult {
/**状态码*/
private int state=1;//1表示SUCCESS,0表示ERROR
/**状态信息*/
private String message="ok";
/**正确数据*/
private Object data;
//无参构造方法
public JsonResult() {}
public JsonResult(String message) {
this.message = message;
}
//一般查询时调用,封装查询结果
public JsonResult(Object data) {
this.data = data;
}
//出现异常时调用,封装抛出异常信息
public JsonResult(Throwable t) {
this.state = 0;
this.message = t.getMessage();
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
最关键的便是代码中的几个构造方法,重点集中在异常处理,Throwable类是所有异常类的父类,用该类可以接受所有的异常类(向上造型的思想),通过t.getMessage()方法将异常信息封装到state中,上述异常处理类中return new JsonResult(e);正是调用了这个构造方法。属性中private Object data;也是使用Object类,向上造型的思想,可以接收所有的实体类。
除了返回JsonResult外,也可以借助相关模板引擎(如Thymeleaf)返回页面,并同时在页面实现数据的渲染。
几点思考
目前Web应用主要遵循持久层-业务层-控制层的开发模式,持久层负责与数据库的交互,业务层负责主要的业务逻辑,控制层主要负责接受请求和响应数据。这种开发模式将传统的web开发模式jsp+servlet+javabean进行进一步区分,使得每一层的逻辑结构更加明晰,让程序的进一步开发和维护升级更加高效简便。但是,随着业务的复杂度增加,将持久层和控制层变得更加简单的同时,业务层也变得越来越臃肿,一旦面临业务的更改或者是技术的更新,可能带来的是业务逻辑的重推复写,这样反而会增加开发的难度,而且,这样的设计模式似乎背离了面向对象的设计初衷,现在的对象,只有属性,没有行为,因为行为都交给了业务层了。前段时间,无意间了解到了DDD(领域驱动设计),它似乎推崇着为对象赋予行为的理念,认为应该将业务层本属于对象的行为交给对象来处理,以此减轻业务层的压力。“充血模型”和“贫血模型”也由此而来,“充血模型”正是指DDD,试图将对象塑造的更加圆满,不仅仅只是承载数据,他也能够有所行动,执行业务。而“贫血模型”即是指如今主流的Java三层架构,对象只负责承载数据。我不知道在未来DDD能否有一席之地,但至少现在来看,还是难以推广,我难以想象应该如何将一些灵活多变的业务交给对象自身来处理,我所能知道的一些业务,比如数据校验(比如一个对象中有age属性,该属性值默认应该大于0,或许可以将业务层的校验逻辑交给对象自身处理),这样或许可以做到,但是想大规模,或许有点难度。但我反倒觉得,如果技术发展能将现有三层架构和DDD思想做整合,那应当是一件妙不可言的事。
本文地址:https://blog.csdn.net/weixin_42415023/article/details/107391811
上一篇: 【自问自答】关于 Swift 的几个疑问
下一篇: IOS shareSDK分享异常