Save entity which has one-to-one mapping in spring data rest - spring-data-rest

I have a very simple spring boot + spring data rest app. I tried to save the entity which has a one-to-one mapping using spring data rest but looks like only the parent is saved, the child is not.
Below is my code
#SpringBootApplication
public class Application{
#Autowired
private PersonRepository personRepo;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
CommandLineRunner init(){
Address address = new Address();
address.setCountry("US");
address.setState("SV");
Person person = new Person();
person.setName("Vincent");
person.setAddress(address);
personRepo.save(person);
return null;
}
}
#Entity
public class Address implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private int id;
private String country;
private String state;
}
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private int id;
private String name;
#OneToOne(cascade={CascadeType.ALL})
private Address address;
}
#Projection(name="inlineAddress",types={Person.class})
public interface InlineAddress {
String getName();
Address getAddress();
}
#RepositoryRestResource(excerptProjection=InlineAddress.class)
public interface PersonRepository extends JpaRepository<Person, Integer> {
Person findByName(#Param("name") String name);
Person findById(#Param("id") int id);
Page<Person> findByNameStartsWith(#Param("name") String name, Pageable page);
}
public interface AddressRepository extends JpaRepository<Address, Integer> {
}
After startup, if I visit http://localhost:8080/api/
I saw below response
{
_links: {
addresses: {
href: "http://localhost:8080/api/addresses{?page,size,sort}",
templated: true
},
persons: {
href: "http://localhost:8080/api/persons{?page,size,sort,projection}",
templated: true
},
profile: {
href: "http://localhost:8080/api/profile"
}
}
}
Then I visit http://localhost:8080/api/persons, so far every thing is good
{
"_embedded": {
"persons": [
{
"address": {
"country": "US",
"state": "SV"
},
"name": "Vincent",
"_links": {
"self": {
"href": "http://localhost:8080/api/persons/1"
},
"person": {
"href": "http://localhost:8080/api/persons/1{?projection}",
"templated": true
},
"address": {
"href": "http://localhost:8080/api/persons/1/address"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/persons"
},
"profile": {
"href": "http://localhost:8080/api/profile/persons"
},
"search": {
"href": "http://localhost:8080/api/persons/search"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
However, after I did a post http://localhost:8080/api/persons/ with
{
"name": "Michael",
"address": {
"country": "US",
"state": "SV"
}
}
It shows below, looks like the address is not get inserted for Michael
{
"_embedded": {
"persons": [
{
"address": {
"country": "US",
"state": "SV"
},
"name": "Vincent",
"_links": {
"self": {
"href": "http://localhost:8080/api/persons/1"
},
"person": {
"href": "http://localhost:8080/api/persons/1{?projection}",
"templated": true
},
"address": {
"href": "http://localhost:8080/api/persons/1/address"
}
}
},
{
"address": null,
"name": "Michael",
"_links": {
"self": {
"href": "http://localhost:8080/api/persons/2"
},
"person": {
"href": "http://localhost:8080/api/persons/2{?projection}",
"templated": true
},
"address": {
"href": "http://localhost:8080/api/persons/2/address"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/persons"
},
"profile": {
"href": "http://localhost:8080/api/profile/persons"
},
"search": {
"href": "http://localhost:8080/api/persons/search"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
Is there anything wrong with my code? I have tried using old approach without using spring data rest but using rest controller, the same json I posted was working fine. Not sure why spring data rest does not work here.

OK. Seems no way to do this. I have to first post a person by
{"name"="Michael"}
and then post an address by
{"country":"US,"state":"SV:} ,
and last put address to this person
{ "name":"Michael", "address":"localhost:8080/addresses/1" }

Related

How to returning only as string column in json response object from eloquent relationship using eager loading

I'm trying to figure out how to eager load data as a string instead of array object from a related table.
I have 3 models and here are the relations
Product.php
class Product extends Model
{
public function ProductTag()
{
return $this->hasMany(ProductTag::class);
}
public function Category()
{
return $this->belongsTo(Category::class);
}
}
ProductTag.php
class ProductTag extends Model
{
public function Product()
{
return $this->belongsTo(Product::class);
}
}
Category.php
class Category extends Model
{
public function Product()
{
return $this->hasMany(Product::class);
}
}
I've tried doing it like so:
public function tag(){
return $this->hasMany(ProductTag::class)
->selectRaw('GROUP_CONCAT(tag) as tag,id')
->groupBy('id');
}
public static function list(){
$result = Category::with(['Product'=>function($q){
$q->with(['tag'=>function($q1){
$q1->first();
}]);
}])->get();
}
Here is the reponse:
{
"data": {
"categories": [
{
"id": 1,
"category": "test 1",
"product": [
{
"id": 46,
"name": "test prod 1",
"tag": []
},
{...}
]
},
{
"id": 2,
"category": "test 2",
"product": [
{
"id": 45,
"name": "test prod 2",
"tag": [
{
"product_tag": "Test1, test12, test123"
}
]
},
{...}
]
},
{
"id": 3,
"category": "test 3",
"product": []
}
]
}
}
The Response is as expected except tag array so, instead of an array named "tag" can I get "product_tag" within the "product" array
"product": [
{
"id": 45,
"name": "test prod 2",
"tag": [
{
"product_tag": "Test1, test12, test123"
}
]
}
]
Here is what I want it to look like:
"product": [
{
"id": 45,
"name": "test prod 2",
"product_tag": "Test1, test12, test123"
}
]
Is it possible and any smart way of doing this in Laravel using Eloquent?
Simple :)
Btw, if you can - rename product_tag in response to tag_line or same - it's not right way to take same name for relation and mutator.
class Product extends Model
{
public function getTagLineAttribute()
{
//tag - is "name" field in ProductTag
return $this->ProductTag->pluck('tag')->implode(',');
}
public function ProductTag()
{
return $this->hasMany(ProductTag::class);
}
public function Category()
{
return $this->belongsTo(Category::class);
}
}
//eager loading with tags
Product::with('ProductTag')->find(..)->tagLine;

How to return only queried items from table?

I have an H2 database ran in a spring-boot application with the following table of data.
I am able to send get requests from postman to get the entire output of the table.
[
{
"id": 1,
"category": "AI",
"title": "AI 2021",
"content": "Potential to take over the world"
},
{
"id": 33,
"category": "Sports",
"title": "Super Bowl",
"content": "What a boring game"
},
{
"id": 65,
"category": "Sports",
"title": "Cleveland Browns",
"content": "Future Cahmpions"
},
{
"id": 66,
"category": "Sports",
"title": "Cleveland Browns",
"content": "Future Cahmpions"
},
{
"id": 67,
"category": "Sports",
"title": "Cleveland Browns",
"content": "Future Cahmpions"
},
{
"id": 68,
"category": "Sports",
"title": "",
"content": ""
},
{
"id": 69,
"category": "Sports",
"title": null,
"content": null
},
{
"id": 70,
"category": "Sports",
"title": "aasdf",
"content": "asdfasdav"
},
{
"id": 97,
"category": "Sports",
"title": "Fitness Life Hack",
"content": "Get Ripped"
},
{
"id": 98,
"category": "Sports",
"title": "Spring FCS Games",
"content": "Wish this happened every year"
},
{
"id": 99,
"category": "Sports",
"title": "Football",
"content": "Sometimes called soccer"
},
{
"id": 100,
"category": "Sports",
"title": "Rocket League",
"content": "That's a real sport right?"
},
{
"id": 101,
"category": "Education",
"title": "Engineering",
"content": "Get Smart"
},
{
"id": 102,
"category": "Education",
"title": "Math",
"content": "2+2=4"
}
]
When I try to add a query string at the end for example http://localhost:8080/Posts/?category=Sports I still get the same output.
I've tried querying for id and other variables and nothing seems to make a difference I always get the entire table as output. I am trying to get it to only return the values where category equals sports. I know findAll() is not the right method for this. I have tried findCategory() from reading the JPQL docs but I was not able to get that to compile. Is there a method I am missing that I should be using?
PostRepository.java
package com.open.forum;
import org.springframework.data.jpa.repository.JpaRepository;
interface PostRepository extends JpaRepository<Post, Long> {
}
PostController.java
package com.open.forum;
import java.util.List;
import java.util.Optional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
class PostController {
private final PostRepository repository;
PostController(PostRepository repository) {
this.repository = repository;
}
// Aggregate root
// tag::get-aggregate-root[]
#GetMapping("/Posts")
#ResponseBody
List<Post> all(#RequestParam String category) {
return repository.findAll();
}
// end::get-aggregate-root[]
#PostMapping("/Posts")
Post newPost(#RequestBody Post newPost) {
return repository.save(newPost);
}
// Single item
#GetMapping("/Posts/{id}")
Optional<Post> one(#PathVariable Long id) {
return repository.findById(id);
// .orElseThrow(() -> new PostNotFoundException(id));
}
#PutMapping("/Posts/{id}")
Post replacePost(#RequestBody Post newPost, #PathVariable Long id) {
return repository.findById(id)
.map(Post -> {
Post.setCategory(newPost.getCategory());
Post.setTitle(newPost.getTitle());
Post.setContent(newPost.getContent());
return repository.save(Post);
})
.orElseGet(() -> {
newPost.setId(id);
return repository.save(newPost);
});
}
#DeleteMapping("/Posts/{id}")
void deletePost(#PathVariable Long id) {
repository.deleteById(id);
}
}
Post.java
package com.open.forum;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String category;
private String title;
private String content;
Post() {}
public Post(String category, String title, String content) {
this.category = category;
this.title = title;
this.content = content;
}
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 String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

I have a model class in which when price type is "fixed" it return object and when it is "percentage" it return string, I don't know how to it?

I have a model which receive a response in which when i have option and furthur values, In values i have a priceType which can be percentage or fixed.
When pricetype is percentage the response is Sting but when price type is fixed the response id object and i receive error..
Accept response
JSONObject jsonResponse = new JSONObject(response);
Gson gson = new GsonBuilder().create();
product = gson.fromJson(String.valueOf(jsonResponse), Product.class);
JSON REsponse for Percentage
{
"id": 73,
"option_id": 21,
"price": "7.0000",
"price_type": "percent",
"position": 0,
"created_at": "2019-10-22 04:23:16",
"updated_at": "2019-10-30 04:57:45",
"label": "Red",
"translations": [
{
"id": 73,
"option_value_id": 73,
"locale": "en",
"label": "Red"
}
]
}
Json Response for Fixed Price
{
"id": 74,
"option_id": 21,
"price": {
"amount": "5.0000",
"formatted": "$5.00",
"currency": "USD"
},
"price_type": "fixed",
"position": 1,
"created_at": "2019-10-22 04:23:16",
"updated_at": "2019-10-22 04:23:16",
"label": "White",
"translations": [
{
"id": 74,
"option_value_id": 74,
"locale": "en",
"label": "White"
}
]
}
Price can either be class or String but i don't know how to put in my model class..
You can declare your pojo class like below :--
public class Product<T> {
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getOption_id() {
return option_id;
}
public void setOption_id(int option_id) {
this.option_id = option_id;
}
private int id;
private int option_id;
public T getPrice() {
return price;
}
public void setPrice(T price) {
this.price = price;
}
T price;
.............
}
When response is parsed then you can check the type of the T then extract the data in that followed way.
Hope it solves your problem.Let me know if further code required.

How to fix: AtlassianHostUser is null in Jira cloud plugin

How to fix: AtlassianHostUser is null in Jira cloud plugin.
My controller looks like this
#Controller
#IgnoreJwt
public class MainController {
private final StorageService storageService;
#Autowired
public MainController(StorageService storageService) {
this.storageService = storageService;
}
.
.
.
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public String handleFileUpload(#AuthenticationPrincipal
AtlassianHostUser hostUser,
#RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
hostUser.getUserKey(); // Causing NPE because hostUser is null
}
My descriptor is shown below
{
"key": "copy-paste-plugin",
"baseUrl": "${addon.base-url}",
"name": "Copy Paste (Spring Boot)",
"authentication": {
"type": "jwt"
},
"lifecycle": {
"installed": "/installed",
"uninstalled": "/uninstalled"
},
"scopes": [
"READ",
"ACT_AS_USER"
],
"modules": {
"generalPages": [
{
"url": "/upload",
"key": "upload",
"location": "none",
"name": {
"value": "Attach a file"
},
"conditions": [
{
"condition": "user_is_logged_in"
}
]
}
]
}
}
This is Jira cloud plugin. I am not sure what part of it I am missing.
Looking for your help/pointer on this.
Thank you!

Jackson module to handle abstract aggregate root and its subclasses in Spring Data REST

I have Spring Data REST based application with repository
public interface CriterionRepository extends JpaRepository<Criterion, Long> {
}
whereas Criterion is base class:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Criterion extends AbstractEntity {}
and NameCriterion is its subclass
#Entity
public class NameCriterion extends Criterion {
private final String name;
}
Spring Data REST exports the repository as REST resource and one can access it at http://localhost:8080/api/criteria/
Exported resource looks as follows:
{
"_embedded": {
"nameCriteria": [{
"_links": {
"self": {
"href": "http://localhost:8080/api/nameCriterion/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/nameCriterion/1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria"
},
"profile": {
"href": "http://localhost:8080/api/profile/criteria"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
When I try to follow self link, there is no mapping for http://localhost:8080/api/nameCriterion/1
I can follow http://localhost:8080/api/criteria/1 though and I get response without name field from NameCriterion
{
"_links": {
"self": {
"href": "http://localhost:8080/api/nameCriterion/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/nameCriterion/1"
}
}
}
My assumption is it is a problem with Jackson mapper defined in REST exporter which is not tweaked correctly to handle abstract class Criterion used in JpaRepository as aggregate root.
What Jackson customization should I apply to make it working properly?
In other words, what Jackson module should I create?
There is no need to create a Jackson module. To use a single table for inherited entities we can use #RestResource annotation to mark them as the same resources:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "criteria")
public abstract class Criterion extends AbstractEntity {
}
#RestResource(rel = "criteria", path = "criteria")
#Entity
public class NameCriterion extends Criterion {
private String name;
}
#RestResource(rel = "criteria", path = "criteria")
#Entity
public class TitleCriterion extends Criterion {
private String title;
}
#RepositoryRestResource(path = "criteria", collectionResourceRel = "criteria", itemResourceRel = "criterion")
public interface CriterionRepository extends JpaRepository<Criterion, Long> {
}
So it becomes possible to obtain all the resources (NameCriterion and TitleCriterion) in one output:
GET http://localhost:8080/api/criteria
{
"_embedded": {
"criteria": [
{
"name": "name1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/criteria/1"
}
}
},
{
"title": "title1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/2"
},
"titleCriterion": {
"href": "http://localhost:8080/api/criteria/2"
}
}
}
]
}
}
GET http://localhost:8080/api/criteria/1
{
"name": "name1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/1"
},
"nameCriterion": {
"href": "http://localhost:8080/api/criteria/1"
}
}
}
GET http://localhost:8080/api/criteria/2
{
"title": "title1",
"_links": {
"self": {
"href": "http://localhost:8080/api/criteria/2"
},
"titleCriterion": {
"href": "http://localhost:8080/api/criteria/2"
}
}
}
Working example.