开发常识技巧

DO/BO/DTO/VO

DO = PO

都是表示持久化对象的实体类,Persistent Object 和 Data Object

如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。通过 DAO 层向上传输数据源对象

DTO

又叫做 Data Transfer Object

业务上传输的数据对象,

例如一张数据库表有50个字段,那么PO就有50个属性,但是我们在远程服务或者页面显示只需要10个字段。这时就没有必要传输所有的字段,而是用10个属性的DTO来进行传递

BO

业务对象,具体是指由Service层内封装的临时业务逻辑的对象

通过调用 DAO 方法 , 结合 PO、VO 进行业务操作。 一个BO对象可以包括多个PO对象

除了基本的 get set 方法,BO本身也会包含很多针对自身数据进行计算的方法

VO

View Object

页面展示用的数据对象

代码举例

以一个最常见的用户注册场景,引出分层设计的模式

现在我们有一张用户表,包含 id name email password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// PO = DO 和数据源交互的对象
public class UserPO {
private Long id;
private String name;
private String email;
private String password;
// getters and setters
}

// BO 包含一些业务操作
public class UserBO {
private Long id;
private String name;
private String email;
private String password;

public boolean register() {
// 业务逻辑处理
// 转换为 PO 保存到数据库
UserPO userPO = new UserPO();
userPO.setName(this.name);
userPO.setEmail(this.email);
// 保存 userPO 到数据库
return true;
}
// getters and setters
}

// DTO 有一个用户注册请求,需要传输用户的基本信息和密码,这个时候 id 就不需要了
public class UserDTO {
private String name;
private String email;
private String password;
// getters and setters
}


// VO 前端展示,这个时候 password 就不需要传输了
public class UserVO {
private Long id;
private String name;
private String email;
// getters and setters
}

try-with-resources

JDK7 引入的新特性,其作用是简化try catch

例如下面这一段

1
2
3
4
try (InputStream inputStream = new FileInputStream(configFile)) {
String json = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
config = GsonUtil.parseObject(json, ServerConfig.class);
}

try块执行完毕后,无论是否出现异常,都会自动关闭inputStream资源。

  1. 自动关闭资源
    • 任何在try块中声明的资源都会在try块结束后自动关闭。这相当于在finally块中调用了资源的close()方法。
    • 这简化了代码,使得资源管理更加安全和便捷,防止因忘记关闭资源而导致资源泄露。
  2. 可选的异常处理
    • 即使try块中的代码抛出异常,资源仍然会被自动关闭。
    • 如果需要处理异常,可以在try-with-resources块后添加catch块来捕获和处理异常。

业务上如何进行调试

针对业务接口调试,我们主要就是从接口进入,然后看接口输出参数,出问题的字段,找到有没有被正确赋值(要么没赋值,要么错误赋值导致的)然后看下业务方法在哪里赋值合适

页面上一些接口不清楚在哪里的,有两种方式来定位:

  • 问前端接口调用的是哪一个,取数怎么取的参数
  • 全局搜索页面上的关键字,有文档注释的就会自动定位到核心接口

QueryDSL

动态参数解析

可以使用 Predicate 参数来绑定实体类信息进行动态参数类型传递,不需要手动进行解析

当然在实体类中还是需要进行额外的配置和映射

1
@QuerydslPredicate(root = Tast.class) Predicate predicate

这里就绑定了 Tast 这个类的实体信息,之后前端传递的,和实体类中直接包含或者是通过复杂数据类型组合的方式实现的间接包含的数据类型,都会被自动解析和传递

代码自动生成

QueryDSL 会根据我们的实体类生成一组与之对应的查询类型。这些类型通常以 Q 开头,并且包含了一些静态字段和方法,用于帮助构建查询

这些自动生成的 java 代码都会放在 build/generated/sources/annotationProcessor/java/main 目录下而不是自己编写的源代码目录下

1
2
3
4
5
6
7
@Override
public List<ItemHistory> queryByItemId(Long itemId) {
//用代码API映射sql的判断
BooleanExpression booleanExpression = QItemHistory.itemHistory.itemId.eq(itemId);
//查询指定id的记录并根据 version 字段降序
return (List<ItemHistory>) itemHistoryRepository.findAll(booleanExpression, Sort.by(Sort.Direction.DESC, "version"));
}

@Embedded 和 @Transient

这两个都是用于 SpringData JPA 中实体类上的注解

其中 @Embedded 注解用于将一个可嵌入的复杂数据类型 作为实体的一部分进行持久化。它表示该属性是一个嵌入类型,将其所有字段嵌入到拥有它的实体表中

@Transient 注解用于标记某个字段不需要持久化到数据库中。被标记的字段在ORM框架中会被忽略,不会在数据库表中生成对应的列,也不会在持久化操作中进行存取

异常抛出和处理

异常的处理时机

在工具方法中 不要 catch 处理异常并返回某种默认值,不然会导致处理逻辑调用工具方法的时候不好直接定位 BUG,要的话就直接抛出

例如下面这种就是不被允许的

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String twoDecimalRetain(String str) {
if (str != null && !str.isEmpty()) {
try {
BigDecimal decimalResult = new BigDecimal(str);
decimalResult = decimalResult.setScale(2, RoundingMode.HALF_UP);
return decimalResult.toString();
} catch (NumberFormatException e) {
return "0.00";
}
} else {
return "0.00";
}
}

出了 BUG 是不好直接定位的,因为这里任何异常或者是非法值都会返回 0 值,前端数据反而还有值,而且大概率和数据库的不一致,这样肯定就 BUG 了,不好排查


开发常识技巧
http://example.com/2024/05/29/开发常识技巧/
作者
Noctis64
发布于
2024年5月29日
许可协议