I have one entity Address and this entity is owned by two other entities.
Ex
#JoinColumn and mappedBy use foreign key either for User or Company.
How to fix this problem to work address for both user and company?
I've tried #JoinColumn and mappedBy, but no luck.
#Entity
class User {
...
#OneToMany
private List<Address> address;
...
}
#Entity
class Company{
...
#OneToMany
private List<Address> address;
...
}
#Entity
class Address {
private String street;
private String city;
private String state;
private String entityType; // User or Company
private String entityId; // id of User or Company
}
one-If you're not stuck with a database schema requiring owner type and id in address, then you should consider a "join" table. You can annotate for Hibernate using #JoinTable, as follows:
#Entity
class Company {
...
#OneToMany
#JoinTable( name="company_address",
joinColumns={#JoinColumn(name="idCompany")},
inverseJoinColumns={#JoinColumn(name="idAddress"} )
List<Address> addresses;
}
You need to add the company_address table (and another one for user_address), which will have idCompany and idAddress foreign key columns. Hibernate will do the rest.
Among other things, doing a one-to-many with a join table like this also allows more than one entity (users, company's etc) to refer to the same address, which would not be possible if Address has a single owner.
Related
In my application a user places an order and sets the billing address to one of the address mapped with him. Now in future he edits that address.So my order will map to that updated address.
My Order entity
#Entity
public class Orders{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#OneToOne
private Address address;
...
}
Address entity
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String address1;
...
}
Person entity
#Entity
public class Person{
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Address> addresses = new HashSet<>();
...
}
I want my Order entity to have a copy of the Address as it was during the creation of the order.Any changes in the Address by user in his profile, after order creation should have no impact on that Order.
I assume you allow user to pick the address from his address list(Person#address), so when you submit your order it contains the address that is already on database, including the id that creates a relationship, does not create an record:
{
user: {
id: 10,
email: "user#stackoverflow.com"
},
address: {
id: 10,
street: "5th Av"
}
}
If you want to "have a copy of the Address" then you should first update your relationship in Order class like:
#OneToOne(cascade = CascadeType.ALL)
private Address address;
Then send the address without id, that would indicate your repository to create a new entry into database.
Json option:
{
user: {
id: 10,
email: "user#stackoverflow.com"
},
address: {
street: "5th Av", ...
}
}
Or by removing the id on controller:
#PostMapping("/submit-order")
public Order submitOrder( #RequestBody Order order) {
// Remove Order#id to detatch current record and enforce create a new one
order.getAddress().setId(null);
return this.orderRepository.save(order);
}
This way your order has an exclusive copy of address.
ERROR: org.springframework.orm.jpa.JpaSystemException with message "Address was altered from 1 to null"
If you receive this error is because you are removing the id of the entity within the scope of a transaction or session. You should create a copy of the entity out of that scope or use entity manager to detach the entity, here is a example.
#embeddable solution
Another solution would be to use an embeddable object instead, that way you can store the address fields on order table but have them as a composite object:
First you create an order address object with all required fields and mark it with #Embeddable annotation:
#Embeddable
public class AddressOrder {
#Column("street")
private String street;
#Column("postal_code")
private String po;
#Column("city")
private String city;
#Column("country")
private String country;
// Getters and setters
}
Then you use the object on your order table as an attribute and mark it with #Embedded annotation.
#Entity
public class Orders {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Embedded
private AddressOrder address;
// Getters and setters
}
You need to choose the solution according to the database approach you want to use.
I have an Employee and Address with one-to-one bi-directional mapping:
#Entity
public class Employee {
#Id
#Column(name = "EMP_ID")
private long id;
private String firstName;
private String lastName;
private double salary;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ADDRESS_ID")
private Address address;
}
Below is my address entity:
#Entity
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private long id;
private String street;
private String city;
private String province;
private String country;
private String pinCode;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "address")
private Employee owner;
}
In Address I have set Fetch type as Lazy. So if I get an address then I am expecting hibernate to run select query on address only, but I see in logs that it is trying to get Employee also.
Below is my HQL query:
List<Address> emps = session.createQuery("from Address where id=20").list();
These are the queries run by Hibernate:
Hibernate:
/*
from
Address
where
id=20 */ select
address0_.ADDRESS_ID as ADDRESS_1_0_,
address0_.city as city2_0_,
address0_.country as country3_0_
from
Address address0_
where
address0_.ADDRESS_ID=20
Hibernate:
/* load Employee */ select
employee0_.EMP_ID as EMP_ID1_1_0_,
employee0_.ADDRESS_ID as ADDRESS_5_1_0_,
employee0_.firstName as firstNam2_1_0_,
employee0_.lastName as lastName3_1_0_
from
Employee employee0_
where
employee0_.ADDRESS_ID=?
Why hibernate loads Employee eagerly even when I set its fetching strategy as LAZY.
This great article describes the problem and a possible solution:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Possible solution: It must be a one-directional relationship from child to parent. The parent cannot have a #OneToOne field to access the child because:
"For every managed entity, the Persistence Context requires both the entity type and the identifier, so the child identifier must be known when loading the parent entity, and the only way to find the associated {child} primary key is to execute a secondary query."
Second solution: Use #OneToMany instead. Don't use #OneToOne because it has this complicated, subtle, quirky problem. You can alter the code to only allow one-to-one access and optionally add a unique key to enforce 1-1.
Lazy loading on one-to-one mapping is possible either by
Setting optional=false (If its not nullable) or
JoinColumn (not on PK and might require schema change)
You can refer to this link for moreinfo.
Explanation : You can refer to explanation link for detailed explanation about this.
Newer version of hibernate can't use trick such as optional=false.
Checkout the updated solution best way to map a onetoone relationship
I have the following entities... Customer, Card and Address
Customer:
{
private String cust_id; // Primary key
.....
#OneToOne
Address address; // I want to load address of the customer.
// Can change the association to #OneToMany, if needed
}
Card:
{
private String card_id; // Primary Key
private String cust_id; // Foreign Key to Customer.cust_id
....
}
Address:
{
private String card_id; // Foreign key to card.card_id
private String address1;
.....
}
When I load customers I want to load Addresses with association table Card. But the tricky part is Address does not have a primary key. It only has a Foreign key.
You could use the annotation #JoinTable after the #OneToOne to point the card's table, so you won't need an entity for card, but if the card's table isn't just a relational table, you could map card in User as #OneToOne and have a #Transient 'getAddress()' method that returns 'this.card.getAddress()', but on card's entity you must map the relation between Address and Card(#OneToOne(mappedBy='card_id')), and in Address you could map card_id as #Id.
First Alternative
Customer:
#OneToOne
#JoinTable(name="card", joinColumns = #JoinColumn(name="cust_id"),
inverseJoinColumns = #JoinColumn(name="card_id"))
private Address address;
Second alternative
Customer:
#OneToOne(mappedBy="cust_id")
private Card card;
...
#Transient
public Address getAddress(){
return this.card == null ? null : this.card.getAddress();
}
Card:
#OneToOne(mappedBy="card_id")
private Address address;
Address:
#Id
private String card_id;
In the Second case Card has a Embedded pk which is formed by two fks(Customer and Address)
Card:
#EmbeddedId
private CustomerAddressPK id;
CustomerAddressPK
#Embeddable
public class CustomerAddressPK(){
private String cust_id;
private String card_id;
}
The mapping between Customer and Address can be made by using the concept of Dependent objects.
Look at the documentation, you will find similar example where there is a mapping between Person entity with dependent object called Name.
I am trying to better familiarize myself with JPA so I created a very simple project. I have a User Class and an Address class. It appears that I have to persist both even though I am adding Address to my User class?
User:
import javax.persistence.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "usr") // #Table is optional, but "user" is a keyword in many SQL variants
#NamedQuery(name = "User.findByName", query = "select u from User u where u.name = :name")
public class User {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length = 32, unique = true)
// the optional #Column allows us makes sure that the name is limited to a suitable size and is unique
private String name;
// note that no setter for ID is provided, Hibernate will generate the ID for us
#OneToMany(fetch=FetchType.LAZY, mappedBy="user")
private List<Address> addresses;
Address:
#Entity
public class Address {
#Id // #Id indicates that this it a unique primary key
#GeneratedValue // #GeneratedValue indicates that value is automatically generated by the server
private Long id;
#Column(length=50)
private String address1;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
EntityManager:
EntityManager entityManager = Persistence.createEntityManagerFactory("tutorialPU").createEntityManager();
entityManager.getTransaction().begin();
User user = new User();
user.setName("User");
List<Address> addresses = new ArrayList<Address>();
Address address = new Address();
address.setAddress1("Address1");
addresses.add(address);
user.setAddresses(addresses);
entityManager.persist(user);
entityManager.persist(address);
entityManager.getTransaction().commit();
entityManager.close();
Probably doing something wrong...just not sure what it is?
Any suggestions would be appreciated.
Thanks,
S
Try the cascade element for the annotation.
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade=CascadeType.PERSIST)
private List<Address> addresses;
The documentation says that by default no operation is cascaded. It also states that the cascade operation is optional, so it really depends on the implementation that you are using.
Also, while setting the relationship, make sure you set both sides of the relationship. Set addresses to the user and user to the addresses.
What you're talking about it's called Cascading. That means doing the same action to nested objects, such as an Address in your User. By default, there is no cascading at all if you don't specify any CascadeType.
You can define various cascade types at once
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Address> addresses;
or just tell JPA to cascade every operation:
#OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = CascadeType.ALL)
private List<Address> addresses;
Both ways will result in, for example, a persisted Address while persisting an User or the deletion of the associated Address when an User is removed.
But!... if you remove CascadeType.REMOVE from the first example, removing an User won't remove its associated Address (The removal operation won't be applied to nested objects).
You are using a oneToMany annotation. From my understanding you have to persist the parent class (the USer) when you want to add a child class (Address) to it.
By persisting the User class, you do let know JPA know which row to update.
I am trying to figure out the best way to accomplish a relationship in hibernate. I have a Customer object. Each customer has a technical contact, a billing contact, and a sales contact. Each type of contact has the exact same data structure (phone, email, address, etc).
My first thought was to create a Contact table, and then have three columns in the Customer table - sales_contact, billing_contact, technical_contact. That would make three distinct foreign key one-to-one relationships between the same two tables. However, I have found that this is very difficult to map in Hibernate, at least using annotations.
Another thought was to make it a many to many relationship, and have a type flag in the mapping table. So, any Customer can have multiple Contacts (though no more than three, in this case) and any Contact can belong to multiple Customers. I was not sure how to map that one either, though. Would tere be a type field on the map table? Would this attribute show up on the Contact java model object? Would the Customer model have a Set of Contact objects. or three different individual Contact objects?
So I am really looking for two things here - 1. What is the best way to implement this in the database, and 2. How do I make Hibernate map that using annotations?
It can be as simple as :
#Entity
public class Contact {
#Id
private String id;
private String phome;
private String email;
private String address;
// ... Getters and Setters
}
#Entity
public class Customer {
#Id
#GeneratedValue
private String id;
#ManyToOne
#JoinColumn(name = "ID")
private Contact billingContact;
#ManyToOne
#JoinColumn(name = "ID")
private Contact salesContact;
#ManyToOne
#JoinColumn(name = "ID")
private Contact technicalContact;
public Customer() {
}
// ... Getters and Setters
}
Now, if you want to make the difference between a BillingContact and a SalesContact at the object level, you can make Contact abstract, and implement it with each type of contact. You will have to annotate the parent class with #Inheritance to specify the inheritance strategy of your choice (SINGLE_TABLE sounds appropriate here, it will use a technical discriminator column - see http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#d0e1168).
How about using #OneToOne and just naming the #JoinColumn differently for each type:
#Entity
public class Contact {
#Id
private String id;
private String phone;
private String email;
private String address;
// ... Getters and Setters
}
#Entity
public class Customer {
#Id
#GeneratedValue
private String id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="billingContact_ID")
private Contact billingContact;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="salesContact_ID")
private Contact salesContact;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="technicalContact_ID")
private Contact technicalContact;
public Customer() {
}
// ....
}
For each row in Customer table should create three rows in Contact table