I have defined two JPARepository with spring: "Person" and "Address". I also specified a relation between Person and Address.
I can fetch all persons with:
http://localhost:8080/person/
and all address with: http://localhost:8080/address/
Also I can get the address of a single person with http://localhost:8080/person/1/address
Now I'd like to get the address to every person as a nested address when I get request: http://localhost:8080/person/
How can I include the relations in the response?
My classes as requested by #nicolasl
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#OneToOne
#JoinColumn(name = "address_id")
private Address address;
//Getter/Setter here
}
#Entity
public class Address {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String location;
#OneToOne(mappedBy = "address")
private Person person;
//Getter/Setter here
}
Well, I guess you just can't, not using the automated rest engine provided by spring. The rest API will return links to retrieve the related entities in the "_links" section of your JSON response (you can read more about that in http://stateless.co/hal_specification.html). You'd have to call those links to retrieve your Addresses.
Another way to solve this, is implementing your own repository and assembling your own JSON response.
Related
I have the following #Entity:
#Entity
public class Person {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Date birthDate;
private String status;
private String city;
...
// many more attributes
}
I'm using the spring data rest as follow:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepositorio extends PagingAndSortingRepository<Person, Long>{
}
When I send the a post to the /api/person/ with a JSON containing all attributes of Person, only status is not set. Can someone help me?
Well, I think you're missing the get and set for status. Did you check it?
I'm trying to create an entity, User who has two Addresses, a home address and a work address.
Instead of storing the address info directly in the User class, I want to normalize it and store all addresses in one table and then link them to the user. Like so:
#Entity
public class User {
#Id
private Integer id;
private Address homeAddress;
private Address workAddress;
// getters and setters
}
#Entity
public class Address {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer id;
private String streetNumberAndName;
private String apartmentOrSuiteNumber;
private String city;
private String state;
private String zipcode;
// getters and setters
}
How do I do this using Spring JPA? I understand this is a ManyToOne relationship but I'm not sure how to map two ManyToOne relationships to one entity. Is this even possible?
Any help much appreciated.
Thanks!
That's really simple. Just map your User class like:
#Entity
public class User {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "fk_home_address")
private Address homeAddress;
#ManyToOne
#JoinColumn(name = "fk_work_address")
private Address workAddress;
// getters and setters
}
The table structure would be like this:
user(id, fk_home_address, fk_work_address)
Note that this is a unidirectional relationship.
The best place to look for examples if you want to learn more is here.
If you're looking for a bidirectional relation, learn here.
private Integer id;
private Address homeAddress;
private Address workAddress;
with first situation, your structure table will be
user(id,home_address_id,work_address_id)
You might consider about second structure
private Integer id;
private List<Address> userddress;//using one to many
your table structure will be
address(id,user_id)
It depend how do you want to organize the structure.
I have 2 domain models and one Spring REST Controller like below:
#Entity
public class Customer{
#Id
private Long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="COUNTRY_ID", nullable=false)
private Country country;
// other stuff with getters/setters
}
#Entity
public class Country{
#Id
#Column(name="COUNTRY_ID")
private Integer id;
// other stuff with getters/setters
}
Spring REST Controller:
#Controller
#RequestMapping("/shop/services/customers")
public class CustomerRESTController {
/**
* Create new customer
*/
#RequestMapping( method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public com.salesmanager.web.entity.customer.Customer createCustomer(#Valid #RequestBody Customer customer, Model model, HttpServletRequest request, HttpServletResponse response) throws Exception {
customerService.saveOrUpdate(customer);
return customer;
}
// other stuff
}
I am trying to call above REST service with below JSON as body:
{
"firstname": "Tapas",
"lastname": "Jena",
"city": "Hyderabad",
"country": "1"
}
Where country code 1 is already there in Country table. The problem is when I am calling this service getting below error:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.test.model.Customer.country -> com.test.model.Country; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.test.model.Customer.country -> com.test.model.Country
Any help will be appreciated!
Try putting CascadeType.ALL
#OneToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name="COUNTRY_ID", nullable=false)
private Country country;
I had a similar problem. Two entities: Document and Status.
Document had a relationship OneToMany with Status, that represented the history of Status the Document had.
So, there was a #NotNull #ManyToOne reference of Document inside Status.
Also, I needed to know the actual Status of Document. So, I needed another relationship, this time #OneToOne, also #NotNull, inside Document.
The problem was: how can I persist both entities the first time if both had a #NotNull reference to the other?
The solution was: remove #NotNull reference from actualStatus reference. This way, it was able to persist both entities.
Just to add an additional scenario that led me to this exact same error:
Make sure that any backward references that may exist are not null.
Specifically in my case, I was using Mapstruct to update some fields of the entity, e.g.
MyClass newInstance = //...
MyClass dbInstance = repository.findByField(someField);
MyClassMapper.MAPPER.update(dbInstance, newInstance);
repository.save(dbInstance);
And my poor implementation of MyClassMapper led the backward references of dbInstance fields to be set to null when they should be pointing back to dbInstance.
I got same error and this is how I solved it:
1st Entity:
#Entity
public class Person implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int personId;
private String name;
private String email;
private long phoneNumber;
private String password;
private String userType;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "personCustomer", cascade
= CascadeType.ALL)
private Customer customer;
2nd Entity:
#Entity
public class Customer implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int customerId;
#OneToOne(fetch = FetchType.LAZY, optional = false, cascade =
CascadeType.ALL)
#JoinColumn(name = "person_customer")
#JsonIgnore
private Person personCustomer;
My Controller:
#PostMapping("/customer/registration")
public PersonCustomer addCustomer(#RequestBody Person person)
{
Customer customer = new Customer(person);
person.setCustomer(customer);
Customer cust = customerRepo.save(customer);
logger.info("{}", cust);
Optional<Person> person_Cust =
personRepo.findById(cust.getPersonCustomer().getPersonId());
Person personNew = person_Cust.get();
PersonCustomer personCust = new PersonCustomer();
if(cust.equals(null))
{
personCust.setStatus("FAIL");
personCust.setMessage("Registration failed");
personCust.setTimestamp(personCust.timeStamp());
}
personCust.setStatus("OK");
personCust.setMessage("Registration OK");
personCust.setTimestamp(personCust.timeStamp());
personCust.setPerson(personNew);
return personCust;
}
The problem got solved when I added "person.setCustomer(customer);".
As both POJO classes has each others reference, so we have to "set" each others reference before using the JPA repository method(customerRepo.save(customer));
I had the exact same problem. The solution seems to be to send the JSON like this:
{
"firstname": "Tapas",
"lastname": "Jena",
"city": "Hyderabad",
"country": {"id":"1"}
}
I guess #RequestBody tries to map an entity not a single field since the Customer instance is referencing a Country instance.
(I have similarly two entities, joined. In the DB, records for the referenced entity (Country in your case) were already created but the entity creation (Customer in your case) with a json, provided the same error message. For me CascadeType.ALL not helped but the above written change in the JSON solved the problem. For further config of course CascadeType can be considered.)
you should change :
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="COUNTRY_ID", nullable=false)
private Country country;
to :
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="COUNTRY_ID")
private Country country;
just delete nullable setting.
I am a JPA newbie. I have what I believe is a very simple example, but I am having trouble implementing it. Basically, I have an entity that I want to "reuse" in other entities.
I have a User having two fields - Home and Office. Each of these in turn refers to an Address, like so:
#Entity
public class User extends Model {
#Column
private String name;
#OneToOne
private Home home;
#OneToOne
private Office office;
}
#Entity
public class Home extends Model {
#OneToOne
private Address address;
#OneToOne(mappedBy="home")
private User user;
// ...
}
#Entity
public class Office extends Model {
#OneToOne
private Address address;
#OneToOne(mappedBy = "office")
private User user;
// ...
}
#Entity
public class Address extends Model {
#Column
private String line1;
#Column
private String line2;
#Column
private String city;
/*
* Is this possible? Being owned by two entities?
*/
#OneToOne(mappedBy="address")
private Home home;
#OneToOne(mappedBy="address")
private Office office;
// ...
}
How do I achieve this?
EDIT
I was originally running into this exception:
org.hibernate.AnnotationException: #OneToOne or #ManyToOne on example.Home.address references an unknown entity: example.Address
It turns out that one of the classes had imported the #Entity annotation from org.hibernate instead of from javax.persistence. After fixing this, the original exception went away (this helped me pin point the issue)
However, I now have a new exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: example.User.home -> example.Home
I don't quite understand this exactly. Should I call save the Home object before I do so on the User instance?
I fixed the problem by adding cascade=CascadeType.ALL attribute to all the #OnetoOne annotations in all the entities.
Without this annotation, I would have to save each owned entity before saving the owning one. For example, I'd have to save a Home before saving the User that the home belongs to.
Adding the cascade attribute to the annotation takes care of this for you. You only need to save the User and the Home and Addresses are automatically saved for you.
This post helped me solve the problem.
Can you make only 1 class User:
#Entity public class User extends Model
{
#Column
private String name;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "line1", column = #Column(name="column11")),
#AttributeOverride(name = "line2", column = #Column(name="column21")),
#AttributeOverride(name = "city", column = #Column(name="column31"))
})
private Address homeAddress;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "line1", column = #Column(name="column12")),
#AttributeOverride(name = "line2", column = #Column(name="column22")),
#AttributeOverride(name = "city", column = #Column(name="column32"))
})
private Address officeAddress;
// .. setters, getters
}
#Embeddable
public class Address extends Model
{
private String line1;
private String line2;
private String city;
// .. getters, setters
}
is there a simple way to persist some of the fields in another class and table using hibernate.
For example, I have a Person class with name, surname, email, address1, address2, city, country fields. I want my classes to be:
public class Person
{
private String name;
private String surname;
private String email;
private Address address;
// ..
}
public class Address
{
private Person person; // to whom this belongs
private String address1;
private String address2;
private String city;
private String country;
// ..
}
and I want to store Address in another table. What is the best way to achieve this?
Edit: I am using annotations. It does not have to be the way I described, I am looking for best practices.
Edit 2: What will be the Id of Address?
PS. If there is a way to make Address immutable (to use as a value object) that is even better, or maybe not because I thought everything from wrong perspective :)
map Address as an entity and add a primary key (an auto-generated id)
map the relation between Person and Address as one-to-one (#OneToOne on each field)
With Hibernate 3.5 it is possible to define foreign generators (aka. JPA mapping), details are here.
It is pretty straight forward Person should implement Serializable then #Id annotation is added to person.
#Entity
#AccessType(value = "field")
#Table(name = "addresses")
public class Address
{
#Id
#OneToOne
#JoinColumn(name = "person_id")
private Person person;
// ...
}
There is an alternative but I really like the first one:
#Entity
#AccessType(value = "field")
#Table(name = "addresses")
public class Address
{
#Id
private int personId;
#MapsId
#OneToOne
#JoinColumn(name = "person_id")
private Person person;
// ...
}