使用Springboot @TypeDiscriminator註解實現多型物件的查詢,jackson @JsonTypeInfo註解實現controller多型支援
背景:
最近專案中涉及到要實現繼承物件的獲取,由於習慣用註解實現mybatis物件對映,所以也想用@TypeDiscriminator實現。但是在百度中卻搜尋不到@TypeDiscriminator的應用例項,幸好能上國外網,Google之。下面以一個最簡單的例子來講@TypeDiscriminator用法。
例子:
有五個物件,Person,Teacher,Student,Answer,Scholarship,Teacher和Student繼承於Person,Teacher擴充套件answers屬性,Student擴充套件scholarship屬性。
// ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person; public class Person { protected Integer id; protected String type; protected String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person; import java.util.List; import org.apache.ibatis.annotations.*; import org.zyd.java.springboot.test.mybatis.person.student.Student; import org.zyd.java.springboot.test.mybatis.person.teacher.Teacher; @Mapper public interface PersonRepository { @Insert("insert into person (name) values (#{name})") int insert(Person person); @Delete("delete from person where id = #{id}") int delete(@Param("id") Integer id); @Update("update person set name=IFNULL(#{name}, name) where id = #{id}") int update(Person person); @Select("select * from person") @TypeDiscriminator(javaType = String.class, column = "type", cases = { @Case(value = "teacher", type = Teacher.class, results = { @Result(property = "id", column = "id"), @Result(property = "answers", column = "id", many = @Many(select = "org.zyd.java.springboot.test.mybatis.answer.AnswerRepository.searchByPerson")) }), @Case(value = "student", type = Student.class, results = { @Result(property = "id", column = "id"), @Result(property = "scholarship", column = "id", one = @One(select = "org.zyd.java.springboot.test.mybatis.scholarship.ScholarshipRepository.searchByPerson"), javaType = Student.class) }) }) List<Person> search(); } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class PersonService { @Autowired PersonRepository personRepository; int insert(Person person) { return personRepository.insert(person); } int delete(Integer id) { return personRepository.delete(id); } int update(Person person) { return personRepository.update(person); } List<Person> search() { return personRepository.search(); } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(path = "/person") public class PersonController { @Autowired PersonService personService; @PostMapping("insert") public int insert(@RequestBody Person person) { return personService.insert(person); } @PostMapping("delete") public int delete(@RequestParam("id") Integer id) { return personService.delete(id); } @PostMapping("update") public int update(@RequestBody Person person) { return personService.update(person); } @GetMapping("search") public List<Person> search() { return personService.search(); } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person.teacher; import java.util.List; import org.zyd.java.springboot.test.mybatis.answer.Answer; import org.zyd.java.springboot.test.mybatis.person.Person; public class Teacher extends Person { private List<Answer> answers; public List<Answer> getAnswers() { return answers; } public void setAnswers(List<Answer> answers) { this.answers = answers; } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.person.student; import org.zyd.java.springboot.test.mybatis.person.Person; import org.zyd.java.springboot.test.mybatis.scholarship.Scholarship; public class Student extends Person { private Scholarship scholarship; public Scholarship getScholarship() { return scholarship; } public void setScholarship(Scholarship scholarship) { this.scholarship = scholarship; } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.answer; public class Answer { private Integer id; private String subject; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.answer; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @Mapper public interface AnswerRepository { @Select("select * from answer where person_id=#{personId}") List<Answer> searchByPerson(@Param("personId") Integer personId); } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.scholarship; public class Scholarship { private Integer id; private Integer level; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getLevel() { return level; } public void setLevel(Integer level) { this.level = level; } } // ------------------------------------------------------------------------ package org.zyd.java.springboot.test.mybatis.scholarship; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @Mapper public interface ScholarshipRepository { @Select("select * from scholarship where person_id=#{personId}") Scholarship searchByPerson(@Param("personId") Integer personId); }
在實現過程中,填了兩個坑:
1. 需指明Student型別
javaType = Student.class
否則會報錯:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Could not set property 'scholarship' of 'class org.zyd.java.springboot.test.mybatis.person.student.Student' with value '
這個坑非常坑,糾結了很久,最開始我在資料庫裡有兩條person資料,一個為teacher,一個為student,我發現List<Answer>並沒有報錯,但是Scholarship卻不能對映,以為只能對映基本java型別,但是想想之前關聯查詢也有可以對映,說明並不是不能對映,而是mybatis在此時並不認識Student型別,所以解決之。
2. 需手動對映id
@Result(property = "id", column = "id"),
不然查出來的Teacher和Student的id都會為null。
現在,查詢實現了,但是呼叫介面的時候,比如插入一個Person(有可能是Teacher,或者Student),怎麼去統一介面呼叫呢?即實現post路徑都是 localhost:8080/person/insert。慣例,google之。實現中使用了一些java對映的trick:獲取到相應的子類service,同時轉化例項為子類物件,還是有點意思的,實現原始碼就不再貼一遍了,直接下載吧:
https://download.csdn.net/download/chunzhenzyd/10835621 (如果連結不對的話,可以去我的資源列表去找)
在實現過程中,填了一個坑:
1. 需指定visible=true
不然得不到Person中的屬性值,比如getType會返回null。