BaseDAOImpl最佳实践:前端JSON/Params后台POJO接收

实体 接收前端参数,然后再持久化,有的时候前端参数可能是需要 结构化 的JSON 这对前端不友好。

下面就以一个例子展现如何优雅地前端扁平化传递JSON让实体类接收。假如书只有一位作者,多个标签。那么,N 书 —> 1 作者, N书 <---> N标签

实体

Book.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_book", comment = "书")
public class Book extends BaseEntity {

    private String title;

    /**
     * 书的拥有者
     */
    @ManyToOne(value = "person_id", parentTable = "t_person")
    private Person person;

    /**
     * 标签
     */
    @ManyToMany(thirdPartyTable = "t_book_tag",
            referenceTable = "t_tag", referenceColumnName = "tag_id", columnDefinition="book_id")
    private List<Tag> tagList;

}

Person.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table("t_person")
public class Person extends BaseEntity {

    private String name;

}

Tag.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_tag", comment = "tag")
public class Tag extends BaseEntity {

    private String title;

}

实体创建完成后,通过 tableGenerator 生成表结构

tableGenerator.createTable(Tag.class);
tableGenerator.createTable(Person.class);
tableGenerator.createTable(Book.class);

Junit 测试

  • 添加Tag
 tagDAO.insertOrUpdate(Arrays.asList(
          Tag.builder().title("文学").build(),
          Tag.builder().title("欧美").build(),
          Tag.builder().title("医学").build()
  ));

617083724562964480,文学
617083724571353088,欧美
617083724571353089,医学

  • 添加Person
personDAO.insert(Arrays.asList(
        Person.builder().name("列夫托尔斯泰").build(),
        Person.builder().name("歌德").build(),
        Person.builder().name("莎士比亚").build()
        ));

617123167953149952,列夫托尔斯泰
617123167957344256,歌德
617123167957344257,莎士比亚

  • 添加Book《威尼斯商人》 作者是莎士比亚,标签为文学、科技。
bookDAO.insertOrUpdate(Book.builder()
         .title("威尼斯商人")
         .person(Person.builder().id(617123167957344257L).build())
         .tagList(Arrays.asList(
                 Tag.builder().id(617083724562964480L).build(),
                 Tag.builder().id(617083724571353088L).build()
         ))
         .build());

前端传参并持久化

POST结构化参数JSON

{
    "title": "威尼斯商人",
    "person": {
        "id": "617123167957344257"
    },
    "tagList": [
        {
            "id": "617083724562964480"
        },
        {
            "id": "617083724571353088"
        }
    ]
}

可以看到结构化参数,对前端相当不友好,非常地丑陋。

POST扁平化参数JSON

我们期望JSON格式如下:

{
    "title": "威尼斯商人",
    "personId": "617123167957344257",
    "tagIds": ["617083724562964480", "617083724571353088"]
}

方案1:

我们需要一个 BookDTO 类继承 Book,这是这篇文章的核心。
BookDTO.java

@Setter
public class BookDTO extends Book {

    private Long personId;

    private List<Long> tagIds;

    /**
     * 扩展字段
     */
    private SexEnum sex;

    /**
     * 扩展字段
     */
    private School.TypeEnum type;

    @Override
    public Person getPerson() {
        if (personId != null) {
            return Person.builder().id(personId).build();
        }
        return super.getPerson();
    }

    @Override
    public List<Tag> getTagList() {
        if (CollectionUtils.isNotEmpty(tagIds)) {
            List<Tag> tagList = new ArrayList<>();

            for (Long tagId : tagIds) {
                tagList.add(Tag.builder().id(tagId).build());
            }
            return tagList;
        }
        return super.getTagList();
    }

}

或者

@Setter
public class BookDTO extends Book {

    private Long personId;

    private List<Long> tagIds;

    /**
     * 扩展字段
     */
    private SexEnum sex;

    /**
     * 扩展字段
     */
    private School.TypeEnum type;

    public void setPersonId(Long personId) {
        this.setPerson(Person.builder().id(personId).build());
    }

    public void setTagIds(List<Long> tagIds) {
        if (CollectionUtils.isNotEmpty(tagIds)) {
            List<Tag> tagList = new ArrayList<>();

            for (Long tagId : tagIds) {
                tagList.add(Tag.builder().id(tagId).build());
            }
            setTagList(tagList);
        }
    }

}

BookController.java

@RestController
@RequestMapping("books")
public class BookController {

    @Autowired
    private BookDAO bookDAO;

    @PostMapping
    public BookDTO postSave(@RequestBody BookDTO book) {
        bookDAO.insert(book);
        return book;
    }

    @GetMapping
    public BookDTO getSave(BookDTO book) {
        bookDAO.insert(book);
        return book;
    }

    @GetMapping("{id}")
    public Book queryById(@PathVariable Long id) {
        return bookDAO.selectById(id).get();
    }
}

请求 http://localhost:8080/books 并提交扁平化的json就可以正确持久化。

GET 请求也能奏效:

curl -X GET \
  'http://localhost:8080/books?title=威尼斯商人&personId=617123167957344257&tagIds=617083724562964480,617083724571353088'

curl -X GET \
  'http://localhost:8080/books?title=威尼斯商人&personId=617123167957344257&tagIds=617083724562964480&tagIds=617083724571353088'

或用person tagList属性接收,id会自动映射到对象

curl -X GET \
  'http://localhost:8080/books?title=威尼斯商人&type=PUBLIC&sex=2&person=617327246029365249&tagList=617320872469864448,617320872469864449'

方案2: 实体字段自定义反序列化,添加注解 @JsonAlias @JsonDeserialize,这种方式不需要继承一个类 BookDTO, 会更加简洁优雅。如果需要扩展字段,继承类 BookDTO 还是有必要的,比如 BookDTO 中的 sex type

  • @JsonAlias 反序列化的时候,将JSON属性myPage映射POJO上,序列化的不受影响。

  • @ParamName Message请求对映射关系,支持下划线转驼峰

  • Jackson默认是SNAKE_CASE,属性tagList不会别映射(tag_list才能映射),如需可以在 JsonAlias 指定。

Book.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_book", comment = "书")
public class Book extends BaseEntity {

    private String title;

    /**
     * 书的拥有者
     */
    @ParamName("person_id")
    @JsonAlias("personId")
    @JsonDeserialize(using = EntityWithLongIdPropertyDeserializer.class)
    @ManyToOne(value = "person_id", parentTable = "t_person")
    private Person person;

    /**
     * 标签
     */
    @ParamName("tag_ids")
    @JsonAlias({"tagIds", "tagList"})
    @JsonDeserialize(using = EntityWithLongIdPropertyDeserializer.class)
    @ManyToMany(thirdPartyTable = "t_book_tag",
            referenceTable = "t_tag", referenceColumnName = "tag_id", columnDefinition="book_id")
    private List<Tag> tagList;

}

查看新创建的对象

curl -X GET \

http://127.0.0.1:8080/books/617084709792391168

{
    "id": "617084709792391168",
    "createdBy": 0,
    "createdAt": "2022-10-27T10:07:18Z",
    "updatedBy": 0,
    "updatedAt": "2022-10-27T13:04:14Z",
    "deleted": false,
    "title": "威尼斯商人",
    "person": {
        "id": "552098712424472576",
        "createdBy": 0,
        "createdAt": "2022-05-01T02:16:08Z",
        "updatedBy": 0,
        "updatedAt": "2022-05-01T02:16:08Z",
        "deleted": false,
        "name": "莎士比亚",
    },
    "tagList": [
        {
            "id": "617083724562964480",
            "createdBy": 0,
            "createdAt": "2022-10-27T10:03:23Z",
            "updatedBy": 0,
            "updatedAt": "2022-10-27T10:03:23Z",
            "deleted": false,
            "title": "文学"
        },
        {
            "id": "617083724571353088",
            "createdBy": 0,
            "createdAt": "2022-10-27T10:03:23Z",
            "updatedBy": 0,
            "updatedAt": "2022-10-27T10:03:23Z",
            "deleted": false,
            "title": "欧美"
        }
    ]
}