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.
Related
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.
So in this example scenario I have an attendance DTO, and a worker DTO, workers in this context are separated by department, and a worker can only ever be inside of one department. It is important to note that Worker {id='123-123-123', department='a'} is different to Worker {id='123-123-123', department='b'}, despite them both sharing the same Id.
I have the following class setup to try and separate functions by id and department
public class IdAndDepartmentPK implements Serializable {
private String id;
private String department;
public IdAndDepartmentPK() {}
...
}
This key class is shared between DTOs that require both the Worker's id and department, below are the two DTOs that are causing a problem.
#Entity
#IdClass(IdAndDepartmentPK.class)
public class AttendencetDto {
#Id private String id; // This is a departmentally unique attendenceId
#Id private String department;
#Column private String workerId;
#JoinColumns({
#JoinColumn(name = "workerId"),
#JoinColumn(name = "department")
})
#ManyToOne(fetch = FetchType.EAGER)
private WorkerDto workerDto;
....
}
#Entity
#IdClass(IdAndDepartmentPK.class)
public class WorkerDto {
#Id
private String id;
#Id
private String department;
...
}
WorkerDto does not need to have knowledge of AttendencetDto, but AttendencetDto, does need to have access to WorkerDto and the other data it contains.
Hibernate complains that fields like workerId should be mapped with insert="false" update="false", but if I was to do this then I wouldn't be able to persist those values to the database.
I essentially want to have those fields available whilst also having the WorkerDto available, is this possible?
You should remove #Column private String workerId; because you already map it by relation to WorkerDto.
If you want to create relation between that you should use setWorkerDto method in your AttendencetDto and just save. After transaction ends you will have your relation in DB.
I am using hibernate for database communication. I have one class as:
#Table(name="Person")
public class Person {
#Column(name="name")
private String name;
#OneToMany
#JoinColumn(name="Address_id)
private Set<Address> address;
... <other filed similarly>
}
Now I want to get this object using its primary key , but object should have only specific columns populate?
I tried using criteria and projection, it is returning a result but it is not mapped to Object i expect (Person Object)
Any idea how to solve this problem using hibernate query/criteria?
Thanks
The hibernate annotations used have to be correctly written.
Let assume that you have two Entities: Person and Address.
#Table(name="Person")
public class Person {
#Column(name="name")
private String name;
#OneToMany
#JoinColumn(name="Address_id")
private Set<Address> address;
... <other filed similarly>
}
And on the other side you have Address class
#Table(name="addresses")
public class Address{
#Column(name="name")
private String addressName;
#ManyToOne
private Person person;
}
Using this mapping when you have a method like:
public Encounter getAddressById(int idAddress) {
session = sf.getCurrentSession();
session.beginTransaction();
Address address = (Address ) session.load(Address .class, idAddress);
return address ;
}
This should return the address mapped with person; and the display of some columns will choosen by you because hibernate here returned the entire object.
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