I have two entitys, A and B. Lets say, that, A has some fields (name, location). B has some fields too + a #ManyToOne relationship to A.
Now if I run my app, I can see the entitys and their valus in ...myDomain/api and specific entitys in ...myDomain/api/A for example. Now if look at ...myDomain/api/B/1, I can see B-s values + under _links a reference to A. How can I get A to already be as a value in B, not a link.
End result should look smth like this:
{
"_embedded" : {
"B" : [ {
"id" : 1,
"someData" : "data",
"otherData" : "other",
"A" : {
"name": "myName",
"location": "myLoc"
} ]
UPDATE
#Entity
#Data //lombok
public class A extends SuperClass{
private String name;
private String location;
}
#Entity
#Data
public class B extends SuperClass {
private String someData;
private String otherData;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private A a;
}
public class SuperClass implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected long id;
}
Both entitys have a simple repository interface which extend CrudRepository.
UPDATE II
Now if I add #RestResourece( exported = false ) tag after #ManyToOne tag, I get the A entity "exposed" and I can access the data. But now, doing a POST on my B entity, I can't access it anymore because B isn't found by Resource<B> anymore. Why is that so?
Related
I have my data model that contains 3 tables: User, Profile, UserProfile.
public class User implements Serializable {
private Integer id;
......
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch =
FetchType.LAZY)
#JsonManagedReference
#JsonProperty("profiles")
private List<UserProfile> userProfiles = new ArrayList<UserProfile>();
}
public class Profile implements Serializable {
private Integer id;
......
#OneToMany(mappedBy="profile", cascade = CascadeType.ALL, fetch =
FetchType.LAZY)
#JsonBackReference
private List<UserProfile> userProfiles= new ArrayList<UserProfile>();
}
public class UserProfile implements Serializable {
private Integer id;
#ManyToOne
#JoinColumn(name = "idUser")
#JsonBackReference
private User user;
#ManyToOne
#JoinColumn(name = "idProfile")
#JsonManagedReference
private Profile profile;
}
And here’s my json feed back:
{
"id": 1,
.......
"profiles": [
{
"profile": {
"id": 1,
.....
},
{
"id": 2,
.....
}
}
]
}
I have two questions:
Is it possible to remove the profile attribute and have:
{
"id": 1,
.......
"profiles": [
{
"id": 1,
.....
},
{
": 2,
.....
}
]
}
In a manytomany relationship with an intermediate table that contains a primary key (id), 2 foreign key that are the ids of the 2 tables that have the manytomany relationship, is that how to do it?
For the 1st question, to hide profile attribute, there are 2 options:
1. If you don't need it in any json output, you can add a #JsonIgnore annotation to it;
2. If you need it elsewhere but don't want it here, you can use Projection. Check https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections and https://www.baeldung.com/spring-data-rest-projections-excerpts for reference on how to use projections.
Checked your code again. Your code has some problem.
You only need 2 entities: User and Profile. And just add #ManyToMany relationship to them.
Refer here for a complete sample on ManyToMany https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
I have two entity classes Country and Language having bi-directional one to many relationship.
Below are the entity classes:
#Entity
#Table(name = "COUNTRY")
public class Country {
#Id
#GeneratedValue
#Column(name = "COUNTRY_ID")
private Long id;
#Column(name = "COUNTRY_NAME")
private String name;
#Column(name = "COUNTRY_CODE")
private String code;
#JacksonXmlElementWrapper(localName = "languages")
#JacksonXmlProperty(localName = "languages")
#OneToMany(mappedBy = "country", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
List<Language> languages;
// getters and setters
}
And...
#Entity
#Table(name = "LANGUAGE")
public class Language {
#Id
#GeneratedValue
#Column(name = "LANGUAGE_ID")
private Long id;
#Column(name = "LANGUAGE_NAME")
private String name;
#ManyToOne
#JoinColumn(name = "COUNTRY_ID")
#JsonIgnore
private Country country;
//getters and setters
}
Below is my Rest controller:
#RestController
#RequestMapping("/countries")
public class CountryRestController {
private final ICountryRepository iCountryRepository;
#Autowired
public CountryRestController(ICountryRepository iCountryRepository) {
this.iCountryRepository = iCountryRepository;
}
#PostMapping("/country")
public ResponseEntity<?> postCountryDetails(#RequestBody Country country) {
Country savedCountry = this.iCountryRepository.save(country);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedCountry.getId()).toUri();
return ResponseEntity.created(location).build();
}
//other methods
}
I'm trying to save below JSON:
{
"name": "Ireland",
"code": "IRE",
"languages": [
{
"name": "Irish"
}
]
}
The problem is that the language (child) foreign key is always null but other properties are getting inserted. I have used #JsonIgnore on property Country country of Language class because it was causing issues with request size as I have another API fetching data of Country along with its Languages.
Please guide.
You can do it in this way :
Country newCountry = new Country(country.getName());
ArrayList < Language > langList = new ArrayList<>();
for (Language lang : country.getLanguages()) {
langList.add( new Language(language.getName(), newCountry ) ) ;
}
newCountry.setLanguages( langList );
iCountryRepository.save(newCountry);
PS : Don't forget to add appropriate constructors.
Also it is mandatory to add a default constructor if you are doing constructor overloading like this :
public Country() {}
public Country(String name) {this.name = name }
You can do it in this way also.
Here it doesn't create new objects. In the same object which is parsing it creates the relationship in language objects.
#PostMapping("/country")
public Country postCountryDetails(#RequestBody Country country) {
if( country.getLanguages().size() > 0 )
{
country.getLanguages().stream().forEach( countryItem -> {
countryItem.setCountry( country );
} );
}
return country;
}
Update the setter for languages in Country class to the below :
public void setLanguages(List<Language> languages) {
this.languages = languages;
languages.forEach(entity -> entity.setCountry(this));
}
I'm getting MySQLIntegrityConstraintViolationException while trying to add new Product. Each Product has Category which has unique value name. I'm getting this exception when I try to add new Product with already existing Category. Example below:
POST 1
{
"name" : "apple",
"categoryName" : "fruit"
}
Response
{
"name": "apple",
"categoryName": "fruit",
"kcal": null
}
Post 2:
{
"name" : "banana",
"categoryName" : "fruit"
}
Response:
{
"timestamp": 1533451793052,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.DataIntegrityViolationException",
"message": "could not execute statement; SQL [n/a]; constraint [UK_8f25rdca1qev4kqtyrxwsx0k8]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/product/add"
}
Which is obviously not what I'd expect, instead I want banana to use same category as apple.
Ok, the code, first entities and dto's
#Entity
#Table(name = "tbl_product")
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double kcal;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn
private Category category;
}
public class ProductDto {
private String name;
private String categoryName;
private Double kcal;
}
#Entity
#Table(name = "tbl_category")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
public Category(){ }
public Category(String name){
this.name = name;
}
}
Post from controller
#PostMapping("/add")
public ProductDto addProduct(#Valid #RequestBody ProductDto productDto){
Product product = productRepository.save(dtoToEntityTranslator.translate(productDto));
return entityToDtoTranslator.translate(product);
}
And pretty straight-forward translators
public class DtoToEntityTranslator {
public Product translate(ProductDto productDto){
Product product = new Product();
product.setName(productDto.getName());
product.setCategory(new Category(productDto.getCategoryName()));
product.setKcal(productDto.getKcal());
return product;
}
}
public class EntityToDtoTranslator {
public ProductDto translate(Product product){
ProductDto productDto = new ProductDto();
productDto.setName(product.getName());
if(product.getCategory() != null) {
productDto.setCategoryName(product.getCategory().getName());
}
productDto.setKcal(product.getKcal());
return productDto;
}
}
Not sure if it's worth mention, my repository for Product
#Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
}
The error is caused by this line
product.setCategory(new Category(productDto.getCategoryName()));
You are tolding Hibernate that this is a new Category because category name is not the Id for Category.
To solve this, you can get the Category with the provided and set to the Category.
Another way is that for existing Category, instead of sending the name to server, you can consider sending the category id.
I'm having troubles getting into Spring Data
I got entity Product which has Category (I'm guessing relation type is right? Product has one Category, Category has many products)
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id")
private Category category;
}
#Entity
class Category implements Serializable {
public Category() {
}
public Category(String name){
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Long id;
#Column(unique = true)
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Product> products;
}
Now I try to add new Product via Postman, calling my RestController
#PostMapping("/add")
public Product addProduct(#Valid #RequestBody Product product){
return repository.save(product);
}
With 2 following requests
{
"name" : "pork",
"category" : "meat"
}
{
"name" : "chicken",
"category" : "meat"
}
In the result I got 2 following responses
{
"id": 1,
"name": "pork",
"category": {
"id": 1,
"name": "meat",
"products": null
}
}
{
"id": 2,
"name": "chicken",
"category": {
"id": 2,
"name": "meat",
"products": null
}
}
And on database I actually got 2 categories named "meat" (even tho it should be unique. What's more, do I actually need Set<Product> in my Category class? TBH, Category has no intrest in that at all.
There are a few problems with your code.
You are directly using entity as the rest API model. Suggest to create a separate ProductModel with only fields that client has access to.
You mixing category creation together inside product creation, but your category in the request only contains name. To the backend, unless you check whether such a category exists, it's always treated as a new category.
Before you call repository.save, you need let category knows what's the product inside. In your current code, only product know its category.
You don't need Set products in your Category class (and it's recommended to use only #ManyToOne).
I have the follow problem
I have a basic configuration of spring data rest (Nothing fancy, nothing custom).
Using spring-data-rest-webmvc 2.0.0 RELEASE and spring-data-jpa 1.5.0 RELEASE
Class A
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id
private String name;
#ManyToMany
private List<B> b;
// getters setters
}
Class B
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id
private String nameb;
#ManyToMany(mappedBy = "b")
private List<A> a;
// getters setters
}
Repository A
#Repository
#RestResource(rel = "a", path = "a")
public interface ARepository extends PagingAndSortingRepository<A, Integer> {
}
Repository B
#Repository
#RestResource(rel = "b", path = "b")
public interface BRepository extends PagingAndSortingRepository<B, Integer> {
}
When I save an entity works fine, but I don't know how to save a relationship
e.g. save an "A" inside a "B" using http
This is the last thing I try from this answer https://stackoverflow.com/a/13031580/651948
POST http://localhost:8080/api/a
{
"name": "Name of A",
"b": {
"rel": "b",
"href": "http://localhost:8080/api/b/1"
}
}
I get an 201 http code but doesn't save the entity.
Did someone tried this already?
Try just using the URL.
POST http://localhost:8080/api/a
Content-Type: application/json
{
"name" : "Name of A",
"b": "http://localhost:8080/api/b/1"
}
or, in your case, it's probably
"b" : ["http://localhost:8080/api/b/1"]
because A.b is a list and hence you submit an array. Did not test this, though.
This should be the valid way since Spring 2.0 (see Spring Data Rest 2.0.0.RELEASE Breaks Code Working Previously With RC1) and it works for me well.