Jackson:将对象作为属性引用

2022-09-03 05:36:26

在我的java spring应用程序中,我正在使用hibernate和jpa,我使用jackson在DB中填充数据。

下面是 User 类:

@Data
@Entity
public class User{

    @Id
    @GeneratedValue
    Long id;

    String username;
    String password;
    boolean activated;

    public User(){}
}

第二类是:

@Entity
@Data
public class Roles {

    @Id
    @GeneratedValue
    Long id;

    @OneToOne
    User user;

    String role;

    public Roles(){}


}

在类角色中,我有一个User属性,然后我做了一个json文件来存储数据:

[ {"_class" : "com.example.domains.User", "id": 1, "username": "Admin", "password": "123Admin123","activated":true}
,
  {"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}]

不幸的是,当我运行应用程序时,它会抱怨:

.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.example.domains.User: no int/Int-argument constructor/factory method to deserialize from Number value (1)
 at [Source: N/A; line: -1, column: -1] (through reference chain: com.example.domains.Roles["user"])

问题来自

{"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}

当我删除上面的行时,应用程序运行良好。

我认为,它抱怨是因为它不能成为用户的实例。那么,我该如何解决它?


答案 1

帮自己一个忙,停止使用你的实体作为DTO!

JPA实体具有双向关系,JSON对象没有,我也认为实体的职责与DTO非常不同,尽管将这些职责合并到单个Java类中是可能的,但根据我的经验,这是一个非常糟糕的主意。

以下是几个原因

  • 在 DTO 层中,您几乎总是需要更大的灵活性,因为它通常与 UI 相关。
  • 应避免将数据库的主键(包括你自己的 UI)公开给外部。我们始终为每个公开的实体生成一个额外的唯一 Id (UUID),主键保留在数据库中,仅用于联接。
  • 通常需要同一实体的多个视图。或多个实体的单个视图。
  • 如果需要将新实体添加到与现有实体的关系中,则需要在数据库中查找现有实体,因此将新旧对象作为单个 JSON 结构发布没有任何优势。您只需要现有的唯一 Id,然后需要新的。

开发人员在JPA方面遇到的许多问题,特别是在合并方面,来自于他们在json被反序列化后收到一个分离的实体。但是这个实体通常没有 OneToMany 关系(如果有,则父项在 JSON 中与子项有关系,但在 JPA 中,子项对父项的引用构成了关系)。在大多数情况下,您始终需要从数据库中加载实体的现有版本,然后将 DTO 中的更改复制到实体中。

自2009年以来,我一直与JPA广泛合作,我知道大多数分离和合并的极端情况,并且使用实体作为DTO没有问题,但是我已经看到了将此类代码交给不熟悉JPA的人时发生的混乱和错误类型。DTO所需的几行(特别是因为您已经使用龙目岛)非常简单,并且比尝试保存几个文件并打破关注点的分离提供了更大的灵活性。


答案 2

Jackson 提供 ObjectIdResolver 接口,用于在反序列化期间从 id 解析对象。

在你的情况下,你想根据JPA/休眠来解析id。因此,您需要实现一个自定义解析器,通过调用 JPA/hierbate 实体管理器来解析 id。

在下面的高级级别是步骤:

  1. 实现自定义说(您可以从 扩展)。在解析对象期间,它将调用 JPA 实体管理器类,以通过给定的 id 和作用域查找实体(请参阅。ObjectIdResolver#resolveId Java docs)ObjectIdResolverJPAEntityResolverSimpleObjectIdResolver

    //Example only;
    @Component
    @Scope("prototype") // must not be a singleton component as it has state
    public class JPAEntityResolver extends SimpleObjectIdResolver {
    //This would be JPA based object repository or you can EntityManager instance directly.
    private PersistentObjectRepository objectRepository;
    
    @Autowired
    public JPAEntityResolver (PersistentObjectRepository objectRepository) {
        this.objectRepository = objectRepository;
    }
    
    @Override
    public void bindItem(IdKey id, Object pojo) {
        super.bindItem(id, pojo);
    }
    
    @Override
    public Object resolveId(IdKey id) {
        Object resolved = super.resolveId(id);
        if (resolved == null) {
            resolved = _tryToLoadFromSource(id);
            bindItem(id, resolved);
        }
    
        return resolved;
    }
    
    private Object _tryToLoadFromSource(IdKey idKey) {
        requireNonNull(idKey.scope, "global scope does not supported");
    
        String id = (String) idKey.key;
        Class<?> poType = idKey.scope;
    
        return objectRepository.getById(id, poType);
    }
    
    @Override
    public ObjectIdResolver newForDeserialization(Object context) {
        return new JPAEntityResolver(objectRepository);
    }
    
    @Override
    public boolean canUseFor(ObjectIdResolver resolverType) {
        return resolverType.getClass() == JPAEntityResolver.class;
    }
    

    }

  2. 告诉 Jackson 通过使用注释 JsonIdentityInfo(resolver = JPAEntityResolver.class) 对类使用自定义 id 解析器。例如:

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
                  property = "id",
                  scope = User.class,
                  resolver = JPAObjectIdResolver.class)
    public class User { ... }
    
  3. JPAObjectIdResolver是一个自定义实现,将依赖于Jackson可能不知道的其他资源(JPA实体管理器)。因此,Jackson 需要帮助来实例化解析器对象。为此,您需要为实例提供自定义处理程序实例。(在我的情况下,我使用的是弹簧,所以我要求弹簧通过使用自动布线来创建实例)ObjectMapperJPAObjectIdResolver

  4. 现在,反序列化应按预期工作。

希望这有帮助。


推荐