2017-02-01 2 views
11

자바 스프링 애플리케이션에서 나는 최대 절전 모드와 jpa로 작업 중이며, jackson을 사용하여 DB에 데이터를 채 웁니다. 여기 Jackson : 속성으로 객체 참조하기

은 사용자 클래스입니다 :

@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 인 필드에 정수 1을 매핑 할 수 없습니다. 입력에 외래 키 참조 대신 사용자 객체가 있어야합니다. 또는 내가 선호하는 것을 수행하고 관심있는 필드가 들어있는 DTO를 작성한 다음 엔티티를 작동시키는 데 필요한 비즈니스 로직을 적용하십시오. –

+0

나는 u를 얻지 않았다. 대답을 써주시겠습니까? –

+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

bean이 JavaBeans 형식을 엄격히 준수하지 않으면 Jackson이 어려움을 겪습니다.

예를 들어 JSON 모델 빈에 대한 명시적인 @JsonCreator 생성자를 만드는 것이 가장 좋습니다.

class User { 
    ... 

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

    .. 
} 
+0

'User'에 있어야한다고 확신하지만' 역할'? –

1

필드의 1-1 매핑이 잘 작동하지만 복잡한 개체 매핑의 경우 일부 API를 사용하는 것이 좋습니다. Dozer 매핑 또는 Mapstruct를 사용하여 Object 인스턴스를 매핑 할 수 있습니다. 도저에는 스프링 통합도 있습니다.

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

Jackson은 역 직렬화 중에 ID에서 객체를 확인하기 위해 ObjectIdResolver 인터페이스를 제공합니다.

경우에 따라 JPA/최대 절전 모드에서 ID를 해결할 수 있습니다. 따라서 JPA/hierbate 엔티티 관리자를 호출하여 ID를 확인하기 위해 사용자 정의 리졸버를 구현해야합니다.

  1. ObjectIdResolverJPAEntityResolver라고 정의를 구현 (당신은 할 수있다 SimpleObjectIdResolver에서 확장) : 높은 수준에서

    다음 단계입니다. 객체를 해결하는 동안은 지정된 ID 및 범위에 의해 실체를 찾기 위해 JPA 엔티티 관리자 클래스를 호출합니다 (참조하십시오. ObjectIdResolver # resolveId 자바 문서를)

    //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)를 사용하여 클래스를 정의합니다. 예 :

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, 
           property = "id", 
           scope = User.class, 
           resolver = JPAObjectIdResolver.class) 
    public class User { ... } 
    
  3. JPAObjectIdResolver 사용자 정의 구현 잭슨 알려진되지 않을 수 있습니다 다른 자원 (JPA 엔티티 관리자)에 대한 의존성이있을 것이다. 그래서 잭슨은 리졸버 객체를 인스턴스화하는 데 도움이 필요합니다. 이를 위해 사용자 지정 HandlerInstantiator ~ ObjectMapper 인스턴스를 제공해야합니다. (내 경우에는 스프링을 사용하여 autowiring을 사용하여 JPAObjectIdResolver의 인스턴스를 생성하도록 요청했습니다.)

  4. 역 직렬화가 예상대로 작동해야합니다.

희망이 도움이됩니다.

5

DTO로 자신의 엔티티 사용을 중단하십시오!

JPA 엔티티는 양방향 관계를 가지고 있지만 JSON 객체는 그렇지 않습니다. 엔티티의 책임은 DTO와 매우 다르며 이러한 책임을 단일 Java 클래스로 결합 할 수는 있지만 실제로는 경험상입니다. 아주 나쁜 아이디어. 그것은 종종 UI 관련이 있기 때문에 여기에

는, 당신은 거의 항상 DTO 계층에 더 많은 유연성을 필요 이유

  • 몇입니다.
  • 사용자 고유의 UI를 포함하여 데이터베이스의 기본 키가 외부에 노출되지 않도록해야합니다. 우리는 항상 공개적으로 노출 된 모든 Entity에 대해 추가 uniqueId (UUID)를 생성합니다. 기본 키는 DB에 있으며 조인에만 사용됩니다.
  • 같은 엔티티의 여러보기가 자주 필요할 수 있습니다. 또는 여러 항목의 단일보기.
  • 기존 엔터티가있는 관계에 새 엔터티를 추가해야하는 경우 데이터베이스에있는 기존 엔터티를 찾아야하므로 새 개체와 이전 개체를 단일 JSON 구조로 게시하면 아무런 이점이 없습니다. 당신은 기존의 고유 식별자가 필요하고 새로운 것이 필요합니다.

JPA와 관련된 많은 문제, 특히 병합과 관련하여 json이 직렬화 해제 된 후에 분리 된 엔터티를 수신한다는 사실이 병합과 관련되어 있습니다. 그러나이 엔티티는 일반적으로 OneToMany 릴레이션을 가지고 있지 않습니다 (그렇다면 JSON에서 아이와 관계가있는 부모이지만 JPA에서는 관계를 구성하는 부모에 대한 아이의 참조입니다).대부분의 경우 항상 데이터베이스에서 엔티티의 기존 버전을로드 한 다음 DTO의 변경 사항을 엔티티에 복사해야합니다.

2009 년부터 JPA에서 광범위하게 작업 해 왔으며 분리 및 병합의 대부분의 경우를 알고 있으며 DTO로 엔티티를 사용하는 데 아무런 문제가 없지만 손을 때 혼란과 오류 유형을 보았습니다 그러한 코드는 JPA에 친숙하지 않은 사람에게 넘어갑니다. DTO에 필요한 몇 줄 (특히 이미 롬복을 사용하고 있기 때문에)은 매우 간단하고 몇 가지 파일을 저장하고 관심사를 분리하는 것보다 훨씬 더 많은 유연성을 제공합니다.

관련 문제