Hibernate, Cascaded operations, Duplicates - java

I have a many to many relation between 2 tables Person and Address with a middle table Person_Address. I have Daos and Services for these entities
What i want to have is when a person is added with an address list if the addresses already exists in the database system should just add id's of those addresses to the Person_Address table.
What happens is duplicate values with different ids.
Possible way to do this is to check the database before adding but if i were to do that then i would be writing my own SQL queries instead of hibernate. Is there a way for me to achieve my objective in hibernate?
Person Class
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "personID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String surName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "PERSON_ADDRESS", joinColumns = {
#JoinColumn(name = "PERSONID")}, inverseJoinColumns = {
#JoinColumn(name = "ADDRESSID")})
private List<Address> addresses = new ArrayList<>();
Address Class
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#Column(name = "addressID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "addresses")
private List<Person> residents;
private String street;
private String state;
private String country;
private String postCode;

If Entity have no ID. Hibernate creates it differently updates.
You should use prefilling of Address in frontend. Or use Criteria API for searching of entered of Address before saving

Maybe one solution can be to change your database and set the complete adress as the primary key, you can get it by concatenating the others fields. To be sure that the adresses will be identical i think you will have to format it to lower or to upper case.
So when you will put the adresse it will be the primary key and you will not have duplicate datas.

Related

JPA Hibernate : A different object with the same identifier value was already associated with the session

I am using two address for a person i.e. present and permanent address.
Each address has an District.
I am showing a district list in a dropdown at the frontend so that user can select a district among them.
So this case must be occur that one has present and permanent address same. So one can select two same district for both address.
Now come to the point. If one set same district for both address, JPA Hibernate show an error following as at the same time two same district are being updated.
nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session
Person.java
#Table(name = "person")
public class Person implements Serializable {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "permanent_adrs_id", referencedColumnName = "id")
private Address permanentAddress;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "present_adrs_id", referencedColumnName = "id")
private Address presentAddress;
}
Address.java
#Table(name = "address")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String village;
#ManyToOne(fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "district_id", referencedColumnName = "id")
private District district;
}
District.java
#Table(name = "district")
public class District implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
My guess is, you have two District or Address objects that have the same value for id, but are different objects i.e. obj1 != obj2.
This is illegal in JPA. The usual solution to this is, to load objects which you refer to from other objects.
For example, do something like this:
Person p = ...
p.getPermanentAddress().setDistrict(
entityManager.find(District.class, p.getPermanentAddress().getDistrict().getId())
);
p.getPresentAddress().setDistrict(
entityManager.find(District.class, p.getPresentAddress().getDistrict().getId())
);
repository.save(p);

can't find the pattern of setting cascade logic

I have 3 tables which are Person Login and Account.
Person and Login is OneToOne relation and Login has one FK which is connected Person's id column called PERSON_ID.
Person(one) and Account(many) is OneToMany relation and Account has one FK which is connected Person's id column called PERSON_ID as well .
what i want to do is when i delete one data from Account , nothing happen to Person and Login.
if i delete one data from Person which id=1, Login's PERSON_ID=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
if i delete one data from Login which PERSON_ID=1, Person 's id=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
how should i set the cascade ?
i've tried dozens of times and still can't find the logic in there, thanks!!
here's my code of all 3 tables without setting cascade:
`
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "ID", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "SEX")
private String sex;
#OneToMany(mappedBy = "person",fetch = FetchType.LAZY)
private List<Account> account;
#OneToOne(mappedBy = "person")
private Login login;
#get..
#set..
}
`
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "ACCOUNT")
private String account;
#Column(name = "AMOUNT")
private String amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID",referencedColumnName = "ID")
public Person person;
#get..
#set..
}
`
#Entity
#Table(name = "LOGIN")
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "USERNAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", referencedColumnName = "ID")
private Person person;
#get..
#set..
}
It's been a while, but if I'm not mistaken you need to use the cascade=REMOVE option on the OneToMany and OneToOne relationships. In the OneToOne I think you need to specify cascade=REMOVE on the side that does NOT own the relationship, that is, the side that also contains the "mappedBy" property.
Finally, I believe JPA will NOT automatically load lazy relationships and then cascade them. I'm thinking you may need to fetch the relationship before you delete the parent entity (otherwise JPA will not know what to delete).

How to manage OnetoOne inserting data in child only

I am very new to hibernate and I am working with JPA and Hibernate4. Trying to insert parent object in child as onetoone relationship.
I went through some tutorials but All the example in the web shows, inserting both parent and child tables.
I want to insert data in child table only.
I have two tables called user and department.
User table consists of user details with department as onetoone relationship, as follows,
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department departmentId;
// getters and setters...
}
Below is my Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
// getters and setters...
}
In department table there is only 4 data. I want to insert data only in user data while insert into it and don't want to insert in Department.
How can I do that.Please assist.
You have to use mappedBy for this, as mentoned below in child Table, Department in your case
#OneToOne(mappedBy="department")
private UserEntity user;
These posts explain you better this,
JPA JoinColumn vs mappedBy
Understanding mappedBy annotation in Hibernate
You need to specify the relationship owner using mappedBy property in the OneToOne mapping in the owner side, here in your case in the Department class, you should add:
#OneToOne(mappedBy="department")
private UserEntity user;
I updated your code, to included the stated annotation and also renamed the Department property in your UserEntity class from departmentId to department to avoid confusion between relationship owner and its id:
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department department;
// getters and setters...
}
Below is the Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy="department")
private UserEntity user;
// getters and setters...
}
This will give you the right mapping with the expected behaviour.
In the #OneToOne annotation, the default value for parameter optional is true. So your annotation is the same as #OneToOne(fetch = FetchType.EAGER, optional = true). This means you can simply leave the Department in a UserEntity instance empty. In that case, persisting it results in persisting only a user entity and no department.
Even if you created a Department instance and assigned it to a UserEntity instance, persisting the UserEntity would not automatically persist the Department, since you don't have any cascade parameter in your annotation. If you don't automatically cascade persists, you would have to persist the Department first and then persist the corresponding user entity.
Maybe you're asking about using existing departments for your user entities. In that case, you first need to get the department via Hibernate (or the JPA API) from an entity manager. The entity instance you get is managed by Hibernate, and you can then set it in a UserEntity and persist that, to have it refer to the department.
Finally, I think one department will probably have more than one user. It might make more sense to have a #ManyToOne annotation instead of #OneToOne, indicating multiple users can refer to the same department, but that depends on your domain model.

What hibernate / jpa annotation is required

I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}

Hibernate one to many automatically insert id

So i have a very basic construction where i have a client and this client could have multiple addresses.
So in hibernate i did something like this
#Entity
#Table(name ="tbl_clients")
#Access(value = AccessType.FIELD)
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_client")
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "fkIdClientAddress", cascade = CascadeType.ALL)
private List<AddressClient> addressClientList = new ArrayList<>();
And the other class looks like this :
#Entity
#Table(name ="tbl_clients_address")
#Access(value = AccessType.FIELD)
public class AddressClient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_client_address")
private Integer id;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="id_client")
private Client Client;
#Column
private Integer fkIdClientAddress;
When inserting a client into the database who has 2 addresses it works but the fields fkIdClientAddress and id_client is are empty in the database. So i have no idea to who the address belong.
How can i fix this? And what is wrong with this construction?
first improvements
Class AddressClient
#Entity
#Table(name ="tbl_clients_address")
#Access(value = AccessType.FIELD)
public class AddressClient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_client_address")
private Integer id;
#ManyToOne
#JoinColumn(name="id_client")
private Client client;
Class Client
#Entity
#Table(name ="tbl_clients")
#Access(value = AccessType.FIELD)
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_client")
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.ALL)
private List<AddressClient> addressClientList = new ArrayList<>();
This is looking better but the field id_client is still null
When i created a for each and save the AddressClients again the id is successful saved.
#RequestMapping(value = "/addclient",method = RequestMethod.POST)
public void addClient(#AuthenticationPrincipal Principal user,#RequestBody Client client) {
//FIND THE ACTIVE USER
LOGGER.info("SQL: GET PRINCEPAL USER");
User getuser = userDao.findByEmail(user.getName());
for (AddressClient addressClient : client.getAddressClientList())
{
addressClient.setClient(client);
}
clientDao.save(client);
}
Your mapping is wrong. First of all, you don't need two different columns (id_client and fkIdClientAddress) to know that a given address belongs to a given client. So the first thing to do is to remove fkIdClientAddress.
Then the mappedBy attribute in OneToMany is usd to tell Hibernate which field in address represents the owning ManyToOne association. So it must be set to Client. (or, if you respect the Java naming conventions and rename the field to client, it must be set to client).
Finally, having cascade=ALL on a ManyToOne doesn't make much sense: you don't want to delete the client when you delete one of its addresses. That would fail anyway, since other addresses would still reference the client.
Your mappings are wrong. When you define the collection in Client class, you should indicate the field in the AddressClient class which points back to the Client and that is the field client.
#OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.ALL)
private List<AddressClient> addressClientList = new ArrayList<>();
And in ClientAddress class you have:
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="id_client")
private Client client;
You do not need the field fkIdClientAddress
In some cases, you still can keep column
#Column
private Integer fkIdClientAddress;
But you have to set data for this column instead of setting value for
private Client client;
But this approach is not appropriate.

Categories