Я использую Spring Data REST, Spring Boot 2, Spring HATEOAS, ElasticSearch.
Это моя модель:
public class Document extends AbstractEntity {
@NotNull
@Column(nullable = false, columnDefinition = "DATE")
private Instant date;
//Define if the invoice/credit note are electronic
@NotNull
@Column(nullable = false, columnDefinition = "BIT DEFAULT 0")
private boolean electronic;
// Expected delivery date
@Column(columnDefinition = "DATE")
private Instant deliveryDate;
@Column(nullable = false, updatable = false)
private int year;
@NotNull
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private DocumentType type = DocumentType.SALES_INVOICE;
@NotNull
@Enumerated(EnumType.STRING)
@ColumnDefault("'PENDING'")
@Column(nullable = false, length = 30)
private DocumentStatus status = DocumentStatus.PENDING;
@NotNull
@Enumerated(EnumType.STRING)
@ColumnDefault("'OPEN'")
@Column(nullable = false, length = 30)
private DocumentDeliveryStatus deliveryStatus = DocumentDeliveryStatus.OPEN;
....
....
....
и строки документа
public class DocumentRow extends AbstractEntity {
@NotNull
@OnDelete(action = OnDeleteAction.CASCADE)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Document document;
//used to keep rows sorted
@Column(nullable = false, columnDefinition = "INT DEFAULT 1")
private int index = 1;
//Used to group lines of a document. Expecially useful in assembly orders to keep additional information about products like the relation to the right or left eye
private String rowGroup;
//The product's type. It's used also in conjunction with note to define when
//the note represents a customer's frame for example
@Column(length = 30)
@Enumerated(EnumType.STRING)
private ProductType productType;
//Could be set also with a note to get info about the item even if it's not sold (customer's item for example)
@JsonDeserialize(using = ProductUriDeserializer.class)
@Any(fetch = FetchType.LAZY, metaDef = "ProductDocumentRowDef", metaColumn = @Column(name = "productGroup"))
@AnyMetaDef(name = "ProductDocumentRowDef", metaType = "string", idType = "long", metaValues = {@MetaValue(value = "OL", targetEntity = OphthalmicLens.class),
@MetaValue(value = "F", targetEntity = Frame.class), @MetaValue(value = "CL", targetEntity = ContactLens.class), @MetaValue(value = "GP", targetEntity = GenericProduct.class)})
@JoinColumn(name = "product_id")
private Product product;
//Link to the parent document
@JsonDeserialize(using = DocumentUriDeserializer.class)
@ManyToOne(fetch = FetchType.LAZY)
private Document linkedDocument;
//Link to the parent document's row for example
@JsonDeserialize(using = DocumentRowUriDeserializer.class)
@ManyToOne(fetch = FetchType.LAZY)
private DocumentRow linkedRow;
//Useful if the foreign key is broken
@Column(length = 50)
private String productCode;
// Row text description
@Size(max = 255)
@NotBlank
@Column(nullable = false)
private String description;
// Reference to another document row (order/ddt/)
@NotNull
@Min(0)
@Column(nullable = false)
private int qty = 1;
@NotNull
@Min(value = 0)
@ColumnDefault("0.000")
@Column(nullable = false, scale = 3, columnDefinition = "DECIMAL(13,3)")
private BigDecimal unitPrice = BigDecimal.ZERO;
@Column(scale = 3, columnDefinition = "DECIMAL(13,3)")
private BigDecimal purchaseUnitPrice;
// (unit price * qty) - percentageDiscount% on that amount
@NotNull
@Min(value = 0)
@ColumnDefault("0.000")
@Column(nullable = false, scale = 3, columnDefinition = "DECIMAL(13,3)")
private BigDecimal amount = BigDecimal.ZERO;
@Min(value = 0)
@Generated(value = GenerationTime.ALWAYS)
@Column(columnDefinition = "DECIMAL(13,3) AS (purchaseUnitPrice*qty)")
private BigDecimal purchaseAmount;
@JsonDeserialize(using = TaxRateUriDeserializer.class)
@ManyToOne(fetch = FetchType.EAGER)
private TaxRate taxRate;
// The total amount of taxes on taxable amount (unit price * qty) - percentageDiscount%
@NotNull
@Min(value = 0)
@ColumnDefault("0.00")
@Column(nullable = false, scale = 2, columnDefinition = "DECIMAL(12,2)")
private BigDecimal taxAmount = BigDecimal.ZERO;
// Total amount (amount+taxAmount)
@Generated(value = GenerationTime.ALWAYS)
@Column(columnDefinition = "DECIMAL(12,2) AS (amount+taxAmount)", scale = 2)
private BigDecimal totalAmount;
//says if the row is a note
@NotNull
@Column(nullable = false, columnDefinition = "BIT DEFAULT 0")
private boolean note = false;
У меня есть суперкласс для продуктов:
@MappedSuperclass
public class Product extends AbstractEntity {
@Column(nullable = false, unique = true, length = 50, updatable = false)
private String searchKey;
@NotBlank
@Size(min = 4, max = 25)
@Column(nullable = false, length = 30)
private String sku;
@Size(min = 9, max = 30)
//@Pattern(regexp = "[0-9]+")
@Column(nullable = true, length = 30)
private String barcode;
// produttore/supplier
@NotBlank
@Size(max = 255)
@Column(nullable = false)
private String manufacturer;
@Size(max = 30)
private String lineCode;
//commercial name/family name
@Size(max = 255)
private String lineDescription;
//@NotBlank
@Size(max = 255)
@Column(nullable = false)
private String description;
и конкретных таблиц:
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Frame extends Product {
@NotBlank
@Column(nullable = false)
private String model;
@Size(max = 30)
private String colorCode;
@Size(max = 100)
private String colorDescription;
@Min(value = 20)
@Max(value = 100)
private Integer caliber;
У меня есть собственный контроллер для получения строк документа:
@Api(tags = "Document Entity")
@RepositoryRestController
@PreAuthorize("isAuthenticated()")
@Log4j2
public class DocumentController {
@GetMapping(path = "/documents/{id}/rows")
public ResponseEntity<?> getRows(@PathVariable(value = "id") long id, Pageable pageable, Locale locale,
PersistentEntityResourceAssembler resourceAssembler) {
return new ResponseEntity<PagedResources<?>>(
pagedResourcesAssembler.toResource(documentRepository.rows(id, pageable), resourceAssembler), HttpStatus.OK);
}
Когда я пытаюсь получить свой ресурс с помощью GET http://localhost:8082/api/v1/documents/15/rows?page=0&size=10, я получаю в ответ:
{
"_embedded": {
"documentRows": [
{
"sid": "fd822eb2-8206-44b5-9115-ae83755626e0",
"createdBy": "system",
"createdDate": "2019-06-11T10:37:52.593Z",
"lastModifiedDate": "2019-06-11T10:37:52.593Z",
"lastModifiedBy": "system",
"createdByName": null,
"lastModifiedByName": null,
"index": 1,
"rowGroup": null,
"productType": "FRAME",
"product": {
"sid": "1d0d0dba-097e-43cd-a441-66ec319d4b0b",
"createdBy": "system",
"createdDate": "2019-06-07T18:21:24.466Z",
"lastModifiedDate": "2019-06-07T18:21:24.466Z",
"lastModifiedBy": "system",
"createdByName": null,
"lastModifiedByName": null,
"searchKey": "GLOBAL_FR002_AL00101_IT",
"sku": "AL00101",
"barcode": "8029224592933",
"manufacturer": "AVM",
"lineCode": "ALS",
"lineDescription": "AVM 1959",
"description": "AVM AVM 1959 AL00101 01 53□17 140 Vista",
"purchasePrice": 18,
"salesPrice": 40.984,
"taxRate": 22,
"preset": true,
"discontinued": false,
"trial": false,
"imageUrl": "https://api.testapp.com/api/v1/avm/AL00101/image?size=big",
"thumbUrl": "https://api.testapp.com/api/v1/avm/AL00101/image?size=small",
"model": "AL00101",
"colorCode": "01",
"colorDescription": null,
"caliber": 53,
"bridge": 17,
"arm": 140,
"sphere": null,
"type": "GLASSES",
"material": "ACETATE",
"gender": "MAN",
"productType": "FRAME"
},
"productVariant": null,
"project": null,
"productCode": "GLOBAL_FR002_AL00101_IT",
"description": "AVM AVM 1959 AL00101 01 53□17 140 Vista",
"qty": 1,
"reservedQty": 0,
"acceptedQty": 0,
"shippedQty": 0,
"receivedQty": 0,
"qtyToShip": 1,
"unitPrice": 33.59,
"purchaseUnitPrice": 18,
"percentageDiscount": 0,
"amount": 33.59,
"purchaseAmount": 18,
"taxAmount": 7.39,
"totalAmount": 40.98,
"note": false,
"stsTaxDeductionCategory": null,
"_links": {
"self": {
"href": "http://localhost:8082/api/v1/documentRows/22"
},
"documentRow": {
"href": "http://localhost:8082/api/v1/documentRows/22{?projection}",
"templated": true
},
"taxRate": {
"href": "http://localhost:8082/api/v1/documentRows/22/taxRate"
},
"document": {
"href": "http://localhost:8082/api/v1/documentRows/22/document{?projection}",
"templated": true
},
"linkedRow": {
"href": "http://localhost:8082/api/v1/documentRows/22/linkedRow{?projection}",
"templated": true
},
"linkedDocument": {
"href": "http://localhost:8082/api/v1/documentRows/22/linkedDocument{?projection}",
"templated": true
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8082/api/v1/documents/15/rows?page=0&size=10"
}
},
"page": {
"size": 10,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
Как видите, продукт показан inline .Я должен сказать, что мои продукты получены из ElasticSearch, а не из Mysql.На ES они извлекаются с использованием String searchKey , а не по их длинному идентификатору.
Я переопределяю ProductRepository, но ничего не происходит.Я хотел бы иметь URL вместо продукта.Любая подсказка будет оценена.