实体
接收前端参数,然后再持久化,有的时候前端参数可能是需要 结构化
的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": "欧美"
}
]
}