杰克逊:将一个物体称为财产

时间:2017-02-01 21:16:38

标签: java spring hibernate jpa jackson

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

这是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(){}


}

在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"}

当我删除上面一行时,该应用效果很好。

我认为,它抱怨因为它无法创建用户的实例。 那么,我该如何解决?

7 个答案:

答案 0 :(得分:8)

Jackson提供ObjectIdResolver接口,用于在反序列化期间解析来自ID的对象。

在您的情况下,您希望根据JPA / hibernate解析id。因此,您需要通过调用JPA / hierbate实体管理器来实现自定义解析程序来解析id。

以下是高级别的步骤:

  1. 实施自定义ObjectIdResolverJPAEntityResolver(您可以从SimpleObjectIdResolver延伸)。在解析对象期间,它将调用JPA实体管理器类以按给定的id和范围查找实体(参见.ObjectIdResolver#resolveId java docs)

    //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. 通过使用注释JsonIdentityInfo(解析器 = JPAEntityResolver.class )告诉Jackson为类使用自定义ID解析器。例如,

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

  4. 现在反序列化应该按预期工作。
  5. 希望这有帮助。

答案 1 :(得分:8)

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

JPA实体具有双向关系,JSON对象不具备,我也相信实体的职责与DTO非常不同,虽然将这些职责加入单个Java类是可能的,根据我的经验是一个非常糟糕的主意。

以下是几个原因

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

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

自2009年以来,我与JPA进行了广泛的合作,我知道大多数分离和合并的角落情况,并且使用实体作为DTO没有问题,但是我看到了当你提出这样的问题时出现的混乱和错误类型对那些不太熟悉JPA的人进行编码。 DTO所需的几行(特别是因为你已经使用了Lombok)非常简单,并且比试图保存一些文件和打破关注点分离更加灵活。

答案 2 :(得分:1)

我已将json文件更改为:

public class PrintContentControl : UserControl
{
    internal UIElement GetPrintContent()
    {
        UIElement printContent = Content as UIElement;
        Content = null;
        return printContent;
    }
}

但我仍然认为,最好的方法是使用外键来记录用户。 欢迎任何解决方案

答案 3 :(得分:1)

如果你的bean没有严格遵守JavaBeans格式,杰克逊就有困难。

最好为您的JSON模型bean创建一个显式的@JsonCreator构造函数,例如

class User {
    ...

   @JsonCreator
   public User(@JsonProperty("name") String name, 
               @JsonProperty("age") int age) {
            this.name = name;
            this.age = age;
   }

   ..
}

答案 4 :(得分:1)

1-1字段映射效果很好,但是当涉及到复杂的对象映射时,最好使用一些API。 您可以使用Dozer Mapping或Mapstruct来映射Object实例。 推土机也有弹簧整合。

答案 5 :(得分:1)

您可以指定非默认构造函数,然后使用自定义反序列化器。

这样的事情应该有用(它还没有经过测试)。

public class RolesDeserializer extends StdDeserializer<Roles> { 

    public RolesDeserializer() { 
        this(null); 
    } 

    public RolesDeserializer(Class<?> c) { 
        super(c); 
    }

    @Override
    public Roles deserialize(JsonParser jp, DeserializationContext dsctxt) 
      throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        long id = ((LongNode) node.get("id")).longValue();
        String roleName = node.get("role").asText();

        long userId = ((LongNode) node.get("user")).longValue();

        //Based on the userId you need to search the user and build the user object properly
        User user = new User(userId, ....);

        return new Roles(id, roleName, user);
    }
}

然后您需要注册新的反序列化器(1)或使用@JsonDeserialize注释(2)

(1)

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new RolesDeserializer());
mapper.registerModule(module);

Roles deserializedRol = mapper.readValue(yourjson, Roles.class);

(2)

@JsonDeserialize(using = RolesDeserializer.class)
@Entity
@Data
public class Roles {
    ...
}

Roles deserializedRol = new ObjectMapper().readValue(yourjson, Roles.class);

答案 6 :(得分:0)

public class Roles {

    @Id
    @GeneratedValue
    Long id;

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    @JsonIdentityReference(alwaysAsId = true)
    @OneToOne
    User user;

    String role;

    public Roles(){}

}