tl;dr
My code gets an array of javascript/json objects from a Restful GET. How do I write code to loop and retrieve, for display, a description (or any value) from a HATEOAS "_link" attribute?
Context
I've inherited a small Spring-based project --it tracks servers, software installations, etc for our team's internal use. It uses Angular front end and Spring/java/mysql back end for a restful back end.
I'm in the process of converting the hand-coded SQL to JPA and 'spring starter data rest'
Current API Does
The current handcoded SQL joins tables to give "display friendly results".
Product
---------
Product ID
Product Name
Category ID
Category
---------
Category ID
Category Name
The 'retrieve products' sql joins product and category to give a 'display friendly name'. The Rest API retrieves an 'product object' with this 'category name' tacked on.
Spring Data Rest Domain Objects
Product
@Entity
@Table(name="products")
public class Products
{
private static final long serialVersionUID = 5697367593400296932L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public long id;
public String product_name;
@ManyToOne(optional = false,cascade= CascadeType.MERGE)
@JoinColumn(name = "category_id")
private ProductCategory productCategory;
public Products(){}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Products(String product_name) {
this.product_name = product_name;
}
public String getProduct_name() {
return product_name;
}
public void setProduct_name(String product_name) {
this.product_name = product_name;
}
public ProductCategory getProductCategory()
{
return productCategory;
}
public void setProductCategory(ProductCategory pProductCategory)
{
productCategory = pProductCategory;
}
}
Product Category
@Entity
@Table(name="productcat")
public class ProductCategory implements Serializable{
private static final long serialVersionUID = 890485159724195243L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public long id;
public String category;
@OneToMany(mappedBy = "productCategory", cascade = CascadeType.ALL)
@JsonBackReference
Set<Products> products;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Set<Products> getProducts() {
return products;
}
}
Problem
I've removed the joins, and added Spring repositories for Product and Category. The 'get products' restful API now returns a list of these:
{
"product_name" : "ForceFive 1.0",
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/rest/products/8"
},
"products" : {
"href" : "http://localhost:8080/api/rest/products/8"
},
"productCategory" : {
"href" : "http://localhost:8080/api/rest/products/8/productCategory"
}
}
Question
How do I display the category name for the "productCategory" link?
"productCategory" : {
"href" : "http://localhost:8080/api/rest/products/8/productCategory"
}
I initially thought I could retrieve the categories, then build a map of 'category url' to 'description.' However the url's differ:
The 'product category' url looks liks this: http://localhost:8080/api/rest/productcat/1
Whereas the Product has this: "productCategory" : { "href" : "http://localhost:8080/api/rest/products/8/productCategory" }
Problem Clarification
so if the javascript controller http gets the products:
requests.get(productsUrl, $scope).success(function(data, status){
$scope.models = data._embedded.products;
});
So then what? Are these the steps?
The javascript controller loops through each data._embedded.products. for each product, the code
http gets the product category url
productCategory" : { "href" : "http://localhost:8080/api/rest/products/8/productCategory" }
http gets the Category
"productCategory": {
"href": "http://localhost:8080/api/rest/productcat/4"
},
-stores the description somewhere for reuse on the page (i.e. adding it to the javascript object )
If these are the steps:
- that's a lot of code
- for a long list (e.g. 50 products), that's another 100 http get requests.
Even if I add a call to get/cache all product categories, that's still an extra 50 http gets
Bonus for:
- easy to code
- fast to run
Attempt #01: Added Projections
I added this projection:
public interface ProductRepository extends PagingAndSortingRepository<Products, Long>
{
@Projection(name = "dummyNameForProjection", types = { Products.class })
interface VirtualProjection
{
String getProduct_name() ;
//Get Product Category
@Value("#{target.productCategory.category}")
String getCategoryName();
}
}
However this url does not return the category name:
http://localhost:8080/api/rest/products/4?projection=dummyNameForProjection
Returned json
{
"product_name": "Force Five",
"_links": {
"self": {
"href": "http://localhost:8080/api/rest/products/4"
},
"products": {
"href": "http://localhost:8080/api/rest/products/4"
},
"productCategory": {
"href": "http://localhost:8080/api/rest/products/4/productCategory"
}
}
}
Additionally, I set debugging on for these
logging.level.org.springframework.data=DEBUG
logging.level.org.springframework.data.rest=DEBUG
logging.level.org.hibernate=DEBUG
Log/console do not mention any projections.
Attempt #02: Fixed Projections File under D for D'oh. I stuck projection on the repository not on the entity. Reviewing some sample code showed the issue. Projections are the ticket!
@Projection(name = "dummyNameForProjection", types = { Product.class })
public interface VirtualProjection {
// this will get the attribute name from the product entity
String getProductName();
//this will get the name of the category entity related to the product
@Value("#{target.category.name}")
String getCategoryName();
}
Just include projection name in your request ex: /products?projection=dummyNameForProjection
来源:https://stackoverflow.com/questions/44554979/how-to-loop-and-retrieve-value-from-hateoas-link-attribute-e-g-retrieve-a-des