【问题标题】:Can't get MappedSuperClass abstract entity to work with Spring Boot and JPA无法让 MappedSuperClass 抽象实体与 Spring Boot 和 JPA 一起使用
【发布时间】:2018-10-23 07:23:55
【问题描述】:

我做了一些研究,我已经通过this guide

这是我的实体结构:

@MappedSuperClass
public abstract class Item{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(nullable = false)
    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "item_id")
    private Set<Picture> pictures = new HashSet<>();

}

示例项目 1:

@Entity
public class Coke extends Item{

    @Column
    private String cokeProperty;

    @Column
    private String cokeProperty2;
}

示例项目 2:

@Entity
public class Sprite extends Item{

    @Column
    private String spriteProperty;

    @Column
    private String spriteProperty2;
}

抽象类Item使用的图片类:

@Entity
public class Picture {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;
}

所有这些都有构造函数、getter 和 setter 以及 toString 覆盖。我这样做是因为我想要Items 的表,然后为CokeSprite 分开表,每个表都包含来自Items_table 的外键id。此外,我假设它还会为Pictures 创建一个表,该表还具有来自Items_table 的外键。然而,我想不通的是如何为此实现 Spring Boot 和 JPA。我以为就这么简单

在 application.properties 中配置了 h2 数据库的项目的我的存储库

@Repository
public interface ItemRepository<Item, Long>{
}

实现ItemService 接口并接受ItemRepository bean 的我的服务:

@Service
public class ItemServiceImpl implements ItemService{

private ItemRepository repository;

public ItemServiceImpl(ItemRepository repository) {
    this.repository = repository;
}

@Override
public ResponseEntity<List<Item>> getAll(){
    return new ResponseEntity<>(repository.findAll(), HttpStatus.OK);
}

@Override
public ResponseEntity<Item> getById(Long id){
    Optional<Item> item = repository.findById(id);
    return new ResponseEntity<>(item.orElse(null), HttpStatus.OK);
}

@Override
public ResponseEntity<Item> create(Item item){
    return new ResponseEntity<>(repository.save(item), HttpStatus.CREATED);
}    }

然后是我的控制器,它接收一个ItemService bean:

@RestController
@RequestMapping("/api/items")
public class ItemController{

    private ItemService service;

    public ItemController(ItemService service) {
        this.service = service;
    }

    @GetMapping("")
    public ResponseEntity<List<Item>> findAll(){
        return service.getAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Item> findById(@PathVariable(name = "id") Long id){
        return service.getById(id);
    }

    @PostMapping("")
    public ResponseEntity<Item> save(@RequestBody Item item){
        return service.create(item);
    }
}

但是当我POST 使用 Json Data 之类的

{
    "name": "Toyota Civic 3 days sale!!",
    "pictures": [
      {
        "name": "picture 1"
      },
      {
        "name": "picture 2"
      }
    ],
    "cokeProperty": "sweet",
    "cokeProperty2": "not healthy"
}

我收到此错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 不能 构造com.example.demo.entities.product.Item 的实例(没有 创建者,如默认构造,存在):抽象类型要么需要 映射到具体类型,具有自定义反序列化器,或包含 [Source: (PushbackInputStream); 处的附加类型信息;线: 1、栏目:1]

我还在我的抽象类上尝试了另一种策略:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

仍然没有雪茄。同样的错误。有人可以告诉我将 Spring Boot 和 JPA 用于由其他实体扩展的抽象实体的正确方法吗?

【问题讨论】:

  • errm,那个错误是 JSON,而不是 JPA。 JSON 是一个单独的过程。也许您应该将您的问题标记为 jsonjackson 而不是 JPA
  • 首先,您似乎刚刚开始使用 java 并为此竖起大拇指。其次.. 错误消息是说 you cannot directly insert data into **Item class.. 因为,它被注释为 @MappedSuperClass 和抽象,你可以通过扩展来插入数据你用 cokesprite 做的。像使用 Item 一样制作 cokesprite 的存储库...第三,您在 private ItemRepository 存储库中缺少 @Autowired 注释ItemServiceImpl私有ItemService服务;ItemController
  • @RujalShrestha 我只是简单地为可乐和雪碧创建一个存储库吗?我可以问一下是什么让我看起来好像刚开始使用 Java 吗?我想知道我可以改进的地方。而且,我认为从版本 4 开始,Spring Boot 中不再需要 @Autowired 注释。

标签: java json spring spring-boot spring-data-jpa


【解决方案1】:

javax.persistence 的@MappedSuperClass 与此问题没有直接关系。正如错误消息指出的那样,原因在于 jackson-databind。

由于Item 是一个抽象类,Jackson 无法确定它应该实例化哪个具体类以便从 JSON 中反序列化实体。这就是为什么Item 必须使用对实现的引用进行注释:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.PROPERTY, property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Coke.class, name = "coke"),
    @JsonSubTypes.Type(value = Sprite.class, name = "sprite")
})
public abstract class Item {

   // no changes

}

还需要对 JSON 进行一些小改动,因为它应该指向实现类型:

{
  "name": /* no changes  */,
  "type": "coke", /* new property */
  "pictures": /* no changes  */
}

请注意@JsonTypeInfo 提供了一些如何将其与实现绑定的选择,不一定与“type”字段绑定。有各种可用的示例和文档。

一个简短的测试表明 Item 现在已正确反序列化:

String json = "{\n" +
    "    \"name\": \"Toyota Civic 3 days sale!!\",\n" +
    "    \"type\": \"coke\"," +
    "    \"pictures\": [\n" +
    "      {\n" +
    "        \"name\": \"picture 1\"\n" +
    "      },\n" +
    "      {\n" +
    "        \"name\": \"picture 2\"\n" +
    "      }\n" +
    "    ],\n" +
    "    \"cokeProperty\": \"sweet\",\n" +
    "    \"cokeProperty2\": \"not healthy\"\n" +
    "}";

ObjectMapper mapper = new ObjectMapper();

Item item = mapper.readValue(json, Item.class);

System.out.println(item);

// outputs:
// Coke(super=Item(id=0, name=Toyota Civic 3 days sale!!, pictures=[Picture(id=0, name=picture 2), Picture(id=0, name=picture 1)]), cokeProperty=sweet, cokeProperty2=not healthy)

但这种更改不足以使您的服务发挥作用。正如@Rujal Shrestha 在他的评论中已经提到的那样,存储库定义和缺少自动装配也存在问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-05
    • 1970-01-01
    • 2014-10-21
    • 2019-04-26
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    • 2020-07-04
    相关资源
    最近更新 更多