Ссылки, отсутствующие во вложенных ресурсах с использованием Spring проекции данных REST HATEOAS - PullRequest
0 голосов
/ 07 июня 2019

Я использую Spring Boot 2.x, Spring Data REST, Spring HATEOAS, Spring JPA, Hibernate.Я знаю о этом вопросе , но он довольно старый, и я хочу лучше детализировать мой случай.

У меня есть две простые бобы, подобные этой:

@Entity
@Data
public class Contact extends AbstractEntity {
    private String sid;

    private String firstName;

    private String lastName;

    private String companyName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "store_id", updatable = false)
    private Store store;

    //more custom fields
}

и:

@Entity
@Data
public class Store extends AbstractEntity {

    private String code;

    private String name;

    private String sid;

    //more custom fields
}

Я раскрываю репозиторий через REST:

@Transactional
@PreAuthorize("isAuthenticated()")
public interface ContactRepository extends JpaRepository<Contact, Long> {
}

и

@Transactional
@PreAuthorize("isAuthenticated()")
public interface StoreRepository extends JpaRepository<Store, Long>, StoreCustomRepository<Store, Long> {
}

Если я позвоню http://localhost:8082/api/v1/contacts/1, я получу:

{
    "sid": "962732c2-68a8-413b-9762-f676d42046b4",
    "createdBy": "1ccf2329-4aa3-4d55-8878-25517edf1522",
    "createdDate": "2019-05-28T14:06:07.011Z",
    "lastModifiedDate": "2019-06-04T08:46:02.591Z",
    "lastModifiedBy": "system",
    "createdByName": "Agent",
    "lastModifiedByName": null,
    "type": "CUSTOMER",
    "personType": "NATURAL_PERSON",
    "firstName": "Daniele",
    "lastName": "LastName",
    "companyName": null,
    "fullName": "Full name",
    "gender": "MALE",
    "birthDate": "2019-05-21T00:00:00Z",
    "birthCity": null,
    "job": null,
    "billingAddress": "Via 123",
    "billingZipCode": "14018",
    "billingCity": "Roatto",
    "billingDistrict": "AT",
    "billingCountry": "IT",
    "shippingAddress": "Via 123",
    "shippingZipCode": "14018",
    "shippingCity": "Roatto",
    "shippingDistrict": "AT",
    "shippingCountry": "IT",
    "taxCode": "",
    "vatNumber": null,
    "landlinePhone": null,
    "mobilePhone": null,
    "fax": null,
    "email": "aaa@sdfg.it",
    "certifiedEmail": null,
    "survey": null,
    "iban": null,
    "swift": null,
    "publicAdministration": false,
    "sdiAccountId": "0000000",
    "preset": false,
    "_links": {
        "self": {
            "href": "http://localhost:8082/api/v1/contacts/1"
        },
        "contact": {
            "href": "http://localhost:8082/api/v1/contacts/1{?projection}",
            "templated": true
        },
        "notes": {
            "href": "http://localhost:8082/api/v1/contacts/1/notes"
        },
        "auditLogs": {
            "href": "http://localhost:8082/api/v1/contacts/1/auditLogs"
        },
        "media": {
            "href": "http://localhost:8082/api/v1/contacts/1/media"
        },
        "privacyAgreements": {
            "href": "http://localhost:8082/api/v1/contacts/1/privacyAgreements"
        },
        "eyeExams": {
            "href": "http://localhost:8082/api/v1/contacts/1/eyeExams"
        },
        "contactLensEyeTests": {
            "href": "http://localhost:8082/api/v1/contacts/1/contactLensEyeTests"
        },
        "eyeExamsCount": {
            "href": "http://localhost:8082/api/v1/contacts/1/eyeExams/count"
        },
        "documents": {
            "href": "http://localhost:8082/api/v1/contacts/1/documents"
        },
        "pendingSalesOrders": {
            "href": "http://localhost:8082/api/v1/contacts/1/pendingSalesOrders"
        },
        "latestPurchasedFrames": {
            "href": "http://localhost:8082/api/v1/contacts/1/latestPurchasedFrames"
        },
        "latestPurchasedItems": {
            "href": "http://localhost:8082/api/v1/contacts/1/latestPurchasedItems"
        },
        "latestSoldItems": {
            "href": "http://localhost:8082/api/v1/contacts/1/latestSoldItems"
        },
        "latestDocuments": {
            "href": "http://localhost:8082/api/v1/contacts/1/latestDocuments"
        },
        "store": {
            "href": "http://localhost:8082/api/v1/contacts/1/store{?projection}",
            "templated": true
        }
    }
}

Как описано в документе Spring Data REST, если есть хранилище для соответствующего ресурса (Store), оно отображается в виде ссылки и не встроено.

Я также использую https://github.com/bohnman/squiggly-java для динамического выбора полей.Конечная цель - вызвать конечную точку контакта, выбрав только несколько полей контакта и имея вложенное хранилище, содержащее только код и собственную ссылку.

Поэтому я хотел бы получить ответ, подобный этому:

{
    "store": {
        "code": "RO",
        "_links": {
            "self": {
                "href": "http://localhost:8082/api/v1/stores/1{?projection}",
                "templated": true
            }
        }
    },
    "lastName": "LastName",
    "_links": {
        "self": {
            "href": "http://localhost:8082/api/v1/contacts/1"
        },
        "contact": {
            "href": "http://localhost:8082/api/v1/contacts/1{?projection}",
            "templated": true
        },
        "store": {
            "href": "http://localhost:8082/api/v1/contacts/1/store{?projection}",
            "templated": true
        }
    }
}

Чтобы иметь вложенный вложенный ресурс, мне нужно было создать проекцию:

@ Projection (name = "inline", types = {Contact.class}) открытый интерфейс ContactInlineProjection {

String getFirstName();

String getLastName();

Store getStore();

//All other fields

}

но в результате отсутствуют ссылки на вложенный ресурс:

{
    "store": {
        "code": "RO"
    },
    "lastName": "LastName",
    "_links": {
        "self": {
            "href": "http://localhost:8082/api/v1/contacts/1"
        },
        "contact": {
            "href": "http://localhost:8082/api/v1/contacts/1{?projection}",
            "templated": true
        },
        "store": {
            "href": "http://localhost:8082/api/v1/contacts/1/store{?projection}",
            "templated": true
        }
    }
}

Чтобы заставить его работать, мне также пришлось создать проекцию для Store:

@Projection(name = "inline", types = {Store.class})
public interface StoreLightProjection {

    String getName();

    String getCode();
    //other fields
}

и измените проекцию контакта следующим образом:

@Projection(name = "inline", types = {Contact.class})
public interface ContactInlineProjection {


    String getFirstName();

    String getLastName();

    StoreLightProjection getStore();
}

При этом ответ выглядит так, как я хотел.Это нормально или потенциальная ошибка?Есть ли другой способ достичь цели с меньшим количеством кода?

...