跳到主要内容

保存聚合根

概述

尽管Jimmer的数据保存能力为任意复杂的数据结构而设计,但为了循序渐进地讲解如何安全地暴露数据保存能力,本文仅讨论保存孤单对象,复杂数据结构的保存在后续文章讲述。

为更具实战指导意义,本文分两种情况讨论

  • 增改同构场景:

    这是一种相对简单的业务场景,INSERT操作和UPDATE操作的表单结构一致。

  • 增改异构场景:

    这是一种相对复杂的业务场景,INSERT操作和UPDATE操作的表单结构不一致。

另外,因为save指令只需一个函数调用,采用Repository对其进行封装意义不大。为了简化非必要复杂性,本文不再定义Repository,而是让Controller直接使用sqlClient

增改同构场景

定义Input DTO

由于在查询任意形状/暴露功能/返回输出DTO一文中,为讲解Output DTO,我们已经对DTO语言有了一定了解,本文不做重复性阐述。

  1. 安装DTO语言Intellij插件:https://github.com/ClearPlume/jimmer-dto (此过程不是必须的,但非常推荐)

  2. 新建目录src/main/dto

  3. src/main/dto下建立一个文件Book.dto,编写代码如下

    Book.dto
    export com.yourcompany.yourproject.model.Book 
    -> package com.yourcompany.yourproject.model.dto

    input BookInput {
    #allScalars(this)
    }
    ...省略其他DTO类型定义...
    信息

    查询任意形状/暴露功能/返回输出DTO一文中的Output DTO不同,这里输入DTO采用input修饰符

生成的代码

编译,Jimmer将自动生成如下类型

BookInput.java
@GeneratedBy(
file = "<yourproject>/src/main/dto/Book.dto"
)
public class BookInput implements Input<Book> {

@Nullable
private Long id;

@NotNull
private String name;

private int edition;

@NotNull
private BigDecimal price;

public BookInput(@NotNull Book base) {
......
}

@Override
public Book toEntity() {
......
}

...省略其他成员...
}
  • ❶ 提醒开发人员,这个类是Jimmer自动生成的

  • ❷ 和查询任意形状/暴露功能/返回输出DTO一文中的Output DTO不同, 在DTO语言中使用input修饰符后,生成的类会实现Input<Book>接口,而非View<Book>接口。

  • ❸ 如果为原实体的id属性配置了自动增长策略 (比如自动变化,序列,UUID,雪花ID),那么在DTO语言中使用input修饰符会导致DTO的id属性为null。

    最终DTO对象要通过❺转化为实体对象再由Jimmer保存,如果DTO的id属性为null,那么转化为实体后,实体的id属性就会处于未指定状状态。

    对于没有明确指定INSERT模式或UPDATE模式的保存操作而言。

    • 如果实体对象id属性被指定了,按照id判断应该INSERT还是UPDATE

    • 如果实体对象id属性没有被指定,按照key *(本例中为nameedition) *判断应该INSERT还是UPDATE

  • ❹ 将实体转化为DTO

  • ❺ 将DTO转化为实体

编写HTTP服务

既然DTO可以转化为实体,可以如此编程

BookInput input = ......;
sqlClient.save(input.toEntity());

事实上,Jimmer提供更简便的方法,连调用toEntity方法将DTO转化为实体也不需要,所以,可以简化代码

BookInput input = ......;
sqlClient.save(input);

接下来,就可以实现Controller了。

BookController.java
@RestController
public class BookController {

private final JSqlClient sqlClient;

public BookController(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}

@PutMapping("/book")
pubic int saveBook(
@RequestBody BookInput input
) {
return sqlClient
.save(input)
.getTotalAffectedRowCount();
}
}

增改异构场景

需求

这里我们假设存在两种图书管理者角色:

  • 提供两种可以保存书籍的角色

    • Operator: 仅能修改已有书籍的价格

    • Administrator: 能新建和编辑书籍的所有信息

  • 对于Administrator角色而言,新建书籍的Web Api和编辑书籍的Web Api需要分开

编写DTO

Book.dto
export com.yourcompany.yourproject.model.Book 
-> package com.yourcompany.yourproject.model.dto

/**
* 供`Operator`角色修改书籍的input, 只能修改`price`属性
*/
input BookOperatorUpdateInput {
id! //覆盖默认行为,id不能为null
price
}

/**
* 供`Administrator`角色新建书籍的input, 没有id属性
*/
input BookAdministratorCreateInput {
#allScalars(this)
-id //新建书籍不需要id
}

/**
* 供`Administrator`角色修改书籍的input, id属性不能为null
*/
input BookAdministratorUpdateInput {
#allScalars(this)
id! //覆盖默认行为,id不能为null
}
...省略其他DTO定义...

生成的代码

编译,自动生成如下三个类型

  • BookOperatorUpdateInput

    BookOperatorUpdateInput
    /**
    * 供`Operator`角色修改书籍的input, 只能修改`price`属性
    */
    @GeneratedBy(
    file = "<yourproject>/src/main/dto/Book.dto"
    )
    public class BookOperatorUpdateInput implements Input<Book> {

    private long id;

    @NotNull
    private BigDecimal price;

    ...省略其他方法...
    }
  • BookAdministratorCreateInput

    BookAdministratorCreateInput
    /** 
    * 供`Administrator`角色新建书籍的input, 没有id属性
    */
    @GeneratedBy(
    file = "<yourproject>/src/main/dto/Book.dto"
    )
    public class BookAdministratorCreateInput implements Input<Book> {

    @NotNull
    private String name;

    private int edition;

    @NotNull
    private BigDecimal price;

    ...省略其他方法...
    }
  • BookAdministratorUpdateInput

    BookAdministratorUpdateInput
    /**
    * 供`Administrator`角色修改书籍的input, id属性不能为null
    */
    @GeneratedBy(
    file = "<yourproject>/src/main/dto/Book.dto"
    )
    public class BookAdministratorUpdateInput implements Input<Book> {

    @NotNull
    private String name;

    private int edition;

    @NotNull
    private BigDecimal price;

    private long id;

    ...省略其他方法...
    }

编写HTTP服务

BookController.java
@RestController
public class BookController {

private final JSqlClient sqlClient;

public BookController(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}

@Secured("ADMINISTRATOR")
@PostMapping("/book")
pubic int createBookByAdministrator(
@RequestBody BookAdministratorCreateInput input
) {
return sqlClient
.insert(input)
.getTotalAffectedRowCount();
}

@Secured("OPERATOR")
@PutMapping("/book/byOperator")
pubic int updateBookByOperator(
@RequestBody BookOperatorUpdateInput input
) {
return sqlClient
.update(input)
.getTotalAffectedRowCount();
}

@Secured("ADMINISTRATOR")
@PutMapping("/book")
pubic int updateBookByAdministrator(
@RequestBody BookAdministratorUpdateInput input
) {
return sqlClient
.update(input)
.getTotalAffectedRowCount();
}
}

不难发现,无论Input DTO参数如何多样化,Jimmer一律一个方法调用完成数据保存。