Spring Boot @ResponseBodyがエンティティIDをシリアル化しない


95

奇妙な問題があり、それに対処する方法を理解することができません。単純なPOJOを持っている:

@Entity
@Table(name = "persons")
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "middle_name")
    private String middleName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "comment")
    private String comment;

    @Column(name = "created")
    private Date created;

    @Column(name = "updated")
    private Date updated;

    @PrePersist
    protected void onCreate() {
        created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
        updated = new Date();
    }

    @Valid
    @OrderBy("id")
    @OneToMany(mappedBy = "person", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PhoneNumber> phoneNumbers = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Date getCreated() {
        return created;
    }

    public Date getUpdated() {
        return updated;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void addPhoneNumber(PhoneNumber number) {
        number.setPerson(this);
        phoneNumbers.add(number);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

@Entity
@Table(name = "phone_numbers")
public class PhoneNumber {

    public PhoneNumber() {}

    public PhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @ManyToOne
    @JoinColumn(name = "person_id")
    private Person person;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

残りのエンドポイント:

@ResponseBody
@RequestMapping(method = RequestMethod.GET)
public List<Person> listPersons() {
    return personService.findAll();
}

jsonレスポンスには、Idを除くすべてのフィールドがあります。これは、個人を編集/削除するためにフロントエンド側で必要です。Idもシリアル化するようにSpring Bootを構成するにはどうすればよいですか?

これが応答の現在の様子です。

[{
  "firstName": "Just",
  "middleName": "Test",
  "lastName": "Name",
  "comment": "Just a comment",
  "created": 1405774380410,
  "updated": null,
  "phoneNumbers": [{
    "phoneNumber": "74575754757"
  }, {
    "phoneNumber": "575757547"
  }, {
    "phoneNumber": "57547547547"
  }]
}]

UPD双方向の休止状態のマッピングがあります。多分それは何らかの理由で問題に関連しています。


春のセットアップについてもっと洞察をいただけませんか?どのjsonマーシャラーを使用していますか?デフォルトのもの、ジャクソン、...?
大田14

実際には特別な設定はありません。Spring Bootを試してみたかった:) pomにspring-boot-starter-data-restを追加し、@ EnableAutoConfigurationを使用するだけです。チュートリアルをいくつか読んでください。すべてをそのまま使用する必要があるようです。そしてそれは、Idフィールドを除いてです。エンドポイントの応答を含む更新された投稿。
コンスタンティン

1
Spring 4では@RestController、コントローラクラスでも使用して@ResponseBody、メソッドから削除する必要があります。また、ドメインオブジェクトの代わりにJSON要求/応答を処理するDTOクラスを用意することをお勧めします。
Vaelyr 2014

回答:


139

私は最近同じ問題を抱えていましたが、それはそれspring-boot-starter-data-restがデフォルトでどのように機能するかです。私のSO質問を参照してください-> アプリをSpring Bootに移行した後でSpring Data Restを使用しているときに、@ IdのエンティティプロパティがJSONにマーシャリングされないことがわかりました

動作をカスタマイズするために、RepositoryRestConfigurerAdapter特定のクラスのIDを公開するように拡張できます。

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Person.class);
    }
}

1
そして、エンティティクラスでIDのゲッターとセッターを今すぐサポートすることを忘れないでください!(忘れていたため、多くの時間を探していました)
phil

3
coudnt find RepositoryRestConfigurerAdapter inorg.springframework.data.rest.webmvc.config
Govind Singh

2
収量:The type RepositoryRestConfigurerAdapter is deprecated。また、動作していないようです
GreenAsJade

2
実際RepositoryRestConfigurerAdapter、最新バージョンでは非推奨です。RepositoryRestConfigurer直接実装する必要があります(implements RepositoryRestConfigurer代わりに使用extends RepositoryRestConfigurerAdapter
Yann39

48

すべてのエンティティの識別子を公開する必要がある場合:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;

@Configuration
public class RestConfiguration implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .toArray(Class[]::new));
    }
}

以前のバージョンのSpring Boot では、直接実装するのではなく(現在は非推奨)を2.1.0.RELEASE拡張する必要があることに注意してください。 org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapterRepositoryRestConfigurer


特定のスーパークラスまたはインターフェイスを拡張または実装するエンティティの識別子のみを公開する場合:

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(Identifiable.class::isAssignableFrom)
                .toArray(Class[]::new));
    }

特定のアノテーションを持つエンティティの識別子のみを公開したい場合:

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(c -> c.isAnnotationPresent(ExposeId.class))
                .toArray(Class[]::new));
    }

注釈の例:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}

コードの最初のブロックを使用する場合、どのディレクトリとファイルに配置できますか?
David Krider

1
@DavidKrider:コンポーネントスキャンの対象となるディレクトリ内の独自のファイルにある必要があります。メインアプリの下にあるすべてのサブパッケージ(@SpringBootApplication注釈付き)は問題ありません。
lcnicolau

Field entityManager in com.myapp.api.config.context.security.RepositoryRestConfig required a bean of type 'javax.persistence.EntityManager' that could not be found.
Dimitri Kopriwa

@ConditionalOnBean(EntityManager.class)in を追加しようとしましたMyRepositoryRestConfigurerAdapterが、メソッドが呼び出されず、IDはまだ公開されていません。SpringデータmybatisでSpringデータを使用しています:github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa

@DimitriKopriwa:EntityManagerはJPA仕様の一部であり、MyBatisはJPAを実装していません(MyBatisはJPAに準拠していますか?をご覧ください)。だから、私はあなたが受け入れられた答えのconfig.exposeIdsFor(...)ような方法を使用して、すべてのエンティティを1つずつ構成する必要があると思います。
lcnicolau

24

@ eric-peladanからの回答はそのままでは機能しませんでしたが、かなり近いものでした。おそらく、以前のバージョンのSpring Bootで機能しました。これが代わりに設定されることになっている方法です。間違っている場合は修正してください:

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(User.class);
        config.exposeIdsFor(Comment.class);
    }
}

3
このソリューションは、@ eric-peladanによって提案されたものとは異なり、Spring Boot v1.3.3.RELEASEで正常に機能することを確認しました。
Poliakoff

4

Spring Boot では、RepositoryRestMvcConfigurationを使用する場合、SpringBootRepositoryRestMvcConfigurationを拡張する必要があります。application.propertiesで定義された構成が機能しない場合があります。

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

@Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.exposeIdsFor(Project.class);
}
}

しかし、一時的に必要な場合は、次のようにプロジェクションを使用して、シリアル化にIDを含めることができます。

@Projection(name = "allparam", types = { Person.class })
public interface ProjectionPerson {
Integer getIdPerson();
String getFirstName();
String getLastName();

}


春のブーツ2では、これは機能しません。クラスRepositoryRestMvcConfigurationには、オーバーライドするconfigureRepositoryRestConfigurationがありません。
pitchblack408

4

このクラスRepositoryRestConfigurerAdapterは3.1以降廃止され、RepositoryRestConfigurer直接実装されています。

@Configuration
public class RepositoryConfiguration implements RepositoryRestConfigurer  {
	@Override
	public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
		config.exposeIdsFor(YouClass.class);
		RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config);
	}
}

フォント:https : //docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html


2

簡単な方法:変数の名前private Long id;private Long Id;

私のために働く。あなたはそれについてここでもっと読むことができます


13
ああ、男...それはそのようなコードの匂いです...本当に、それをしないでください
Igor Donin

@IgorDoninは、変数/クラス/関数名からセマンティクスをスニッフィングしてクロールするのが好きなJavaコミュニティに...いつでもどこでもそうだと言っています。
EralpB 2017

2
@Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        try {
            Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
            exposeIdsFor.setAccessible(true);
            ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    class ListAlwaysContains extends ArrayList {

        @Override
        public boolean contains(Object o) {
            return true;
        }
    }
}

3
SOへようこそ!解答にコードを投稿する場合、コードがOPの問題をどのように解決するかを説明すると役立ちます:)
Joel

1

ええと、私は解決策を見つけたようです。spring-boot-starter-data-restをpomファイルから削除し、@ JsonManagedReferenceをphoneNumbersに追加し、@ JsonBackReferenceをpersonに追加すると、目的の出力が得られます。応答でのJsonはもうかなり印刷されていませんが、現在はIDを持っています。マジックスプリングブートがこの依存関係でフードの下で何を実行するのかわかりませんが、それは好きではありません:)


これは、Spring DataリポジトリをRESTエンドポイントとして公開するためのものです。その機能が不要な場合は、省略できます。IDの問題はModule、SDRに登録されているジャクソンと関係があると思います。
Dave Syer 2014

1
RESTful APIは代理キーIDを公開すべきではありません。外部システムには何も意味しないからです。RESTfulアーキテクチャでは、IDはそのリソースの正規URLです。
Chris DaMour 2014

4
これを以前に読んだことがあるのですが、正直言って、IDなしでフロントエンドとバックエンドをリンクする方法がわかりません。削除/更新操作のためにormにIDを渡す必要があります。多分あなたはいくつかのリンクを持っています、それを真のRESTfulな方法でどのように実行することができますか:)
Konstantin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.