2017-02-01 2 views
11

В моем приложении java spring, я работаю с спящим и jpa, и я использую jackson для заполнения данных в DB.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(){} 


} 

В классе ролей у меня есть свойство пользователя , а затем я сделал файл 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"}] 

Unfortu сожалению, когда я запустить приложение он жалуется:

.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

В нем говорится, что проблема в том, что проблема: он не может отобразить целое число 1 в поле, которое имеет тип «Пользователь». Вы должны либо иметь пользовательский объект вместо ссылки внешнего ключа во входе. ИЛИ сделайте то, что я предпочитаю, напишите DTO, содержащий интересующие вас поля, затем примените любую бизнес-логику, необходимую для того, чтобы заставить сущность работать. –

+0

У меня не получилось. могли бы вы написать ответ? –

+0

С точки зрения бизнеса, это один к одному правильный? –

ответ

1

Я изменил файл JSON на:

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

Но я все еще думаю, лучшие способы используют внешний ключ для пользователя записи. Любое решение приветствуется

1

Если ваш компонент не строго придерживается формата JavaBeans, у Джексона есть трудности.

Лучше всего создать явный конструктор @JsonCreator для вашего компонента модели JSON, например.

class User { 
    ... 

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

    .. 
} 
+0

Вы уверены, что это должно быть на 'User', но' Roles'? –

1

1-1 сопоставление полей работает хорошо, но когда дело доходит до комплексного отображения объектов, лучше использовать некоторый API. Для сопоставления экземпляров объектов вы можете использовать картографирование бульдозера или Mapstruct. Бульдозер имеет весовую интеграцию.

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); 
    } 
} 

Затем вы должны зарегистрировать новый deserialiser (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); 
3

Jackson предоставляет интерфейс ObjectIdResolver для разрешения объектов из идентификаторов во время де-сериализации.

В вашем случае вы хотите разрешить идентификатор, основанный на JPA/спящем режиме. Таким образом, вам нужно реализовать настраиваемый преобразователь для разрешения id, вызвав диспетчер сущностей JPA/hierbate.

На высоком уровне ниже шаги:

  1. Реализовать обычай ObjectIdResolver сказать JPAEntityResolver (вы можете простирается от SimpleObjectIdResolver). При решении объекта он будет вызывать JPA менеджер объекта класс, чтобы найти объект по заданному идентификатору и объему (см. ObjectIdResolver # resolveId Java документов)

    //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 (resolver = JPAEntityResolver.class). Напр.

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, 
           property = "id", 
           scope = User.class, 
           resolver = JPAObjectIdResolver.class) 
    public class User { ... } 
    
  3. JPAObjectIdResolver обычай реализации и будет иметь зависимость от других ресурсов (JPA Entity Manager), которые не могут быть известны Джексон. Поэтому Джексону нужна помощь в создании объекта распознавателя. Для этой цели вам необходимо предоставить заказной экземпляр HandlerInstantiator в ObjectMapper. (В моем случае я использовал пружину, поэтому я попросил весну создать экземпляр JPAObjectIdResolver с помощью автоподготовки)

  4. Теперь де-сериализация должна работать должным образом.

Надеюсь, это поможет.

5

Сделайте себе одолжение и прекратите использовать свои сущности как DTO!

Объекты JPA имеют двунаправленные отношения, объекты JSON этого не делают, я также считаю, что обязанности Entity сильно отличаются от обязанностей DTO, и хотя объединение этих обязанностей в один класс Java возможно, по моему опыту, это очень плохая идея.

Вот несколько причин

  • Вы почти всегда нужно больше гибкости в слое DTO, поскольку он часто связан с пользовательским интерфейсом.
  • Вам не следует подвергать первичные ключи из вашей базы данных наружу, включая собственный пользовательский интерфейс. Мы всегда генерируем дополнительный uniqueId (UUID) для каждого публично открытого Entity, первичный ключ остается в БД и используется только для соединений.
  • Вам часто требуется несколько видов одного и того же объекта. Или один вид нескольких объектов.
  • Если вам нужно добавить новую сущность в отношение с существующим, вам нужно будет найти существующий в базе данных, поэтому публикация нового и старого объекта как единой структуры JSON не имеет преимущества. Вам просто нужен уникальный уникальный существующий, а затем новый.

Многие проблемы, с которыми сталкиваются разработчики с JPA, особенно в отношении слияния, связаны с тем, что они получают отдельную сущность после того, как их json был десериализован. Но этот объект обычно не имеет отношений OneToMany (и, если это так, это родитель, который имеет отношение к дочернему объекту в JSON, но в JPA это ссылка ребенка на родителя, который составляет отношение).В большинстве случаев вам всегда нужно загрузить существующую версию объекта из базы данных, а затем скопировать изменения из DTO в объект.

Я много работал с JPA с 2009 года, и я знаю большинство угловых случаев отсоединения и слияния и не имею проблем с использованием сущности как DTO, но я видел замешательство и типы ошибок, которые возникают, когда вы нажимаете такой код для тех, кто не знаком с JPA. Несколько строк, которые вам нужны для DTO (тем более, что вы уже используете Lombok), настолько просты и позволяют вам гораздо больше гибкости, чем пытаться сохранить несколько файлов и нарушить разделение проблем.

Смежные вопросы