I have a problem with update object from java code to postgresql.
When I'm updating object, I'm having a problem with id, because id is null. Thus in database is creating a new row instead of overwriting the same row.
It seems like, the row in database:
id = 1
name = "name"
then I'm sending a json, like:
{
name: "newName"
}
then I'm getting the new row and it seems:
id = 1, name = "name"
id = 2, name = "newName"
It's because when I'm sending a json there id is null, so postgresql is creating a new row, but I want to overwrite the row with id = 1. One of solution to add in #OneToOne(orphanRemoval = true), but I don't like it, because it deleting the old id then creating the new id. How do I can overwrite the initial row if I don't have id?
Thanks!
class A{
#Id
#GeneratedValue
UUID id;
#OneToOne(mappedBy = "A", cascade = CascadeType.ALL)
B b;
}
class B{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#OneToOne(optional = false)
#JoinColumn(name = "A_id")
A a;
}
updatedA.setInfo(jsonSend.getInfo());
Send the id to the client and when it call put/post, send it back. You wouldnt know which record should be updated without that (this is the regular solution).
Other solution, if the name is unique/natural id (i dont think so), you can post the old name and the new one too. Then select the record with old name, update the object and save.
I'm using CrudRepository.
Well some part of code, entities:
public class Human {
#Id
#GeneratedValue
Long id;
#Column(name = "first_name")
String firstName;
#Column(name = "last_name")
String lastName;
#JoinColumn(name = "data_document_id")
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#OneToOne(cascade = CascadeType.PERSIST)
DataDocument dataDocument;
#OneToMany(mappedBy = "human", cascade = CascadeType.ALL)
#Singular
List<Book> books;
}
public class DataDocument {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
}
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Boolean isReaded;
Integer type;
Integer cost;
Instant date;
}
Here I make POST to create a human:
{
"firstName": "firstName",
"lastName": "lastName",
"dataDocument": {
"name": "randomDocument"
},
"books": [
{
"isReaded": true,
"type": 0,
"cost": 100,
"date": "2019-06-11"
}
]
}
humanRepository.save(human);
So it's ok, I'm having to get the row in database:
TABLE HUMAN
human_id = 1, firstName = firstName, lastName = lastName, data_document_id = 1
TABLE DOCUMENT_DATA
data_document_id = 1, name = randomDocument
TABLE BOOK
book_id = 1, human_id = 1, type = 0, cost = 100, date = "2019-06-11", isReaded = true
Then I want to update my the human. For example json:
{
"id": 1,
"firstName": "firstName",
"lastName": "lastName",
"dataDocument": {
"name": "NEW_DOCUMENT"
},
"books": [
{
"isReaded": true,
"type": 0,
"cost": 100,
"date": "2019-06-11"
}
]
}
My method:
public Human updateHuman(Long humanId, updateHuman){
Human human = humanRepository.findById(humanId);
human.setDocumentData(updateHuman.getDocumentData());
humanRepository.save(human);
}
Then in the table, I will get:
TABLE HUMAN
human_id = 1, firstName = firstName, lastName = lastName, data_document_id = 2
TABLE DOCUMENT_DATA
data_document_id = 1, name = randomDocument
data_document_id = 2, name = NEW_DOCUMENT
That's the problem. I want to overwrite data_document_id = 1 to new name. I know some solutions like #OneToOne(orphanRemoval = true) but it would gives the next result:
TABLE DOCUMENT_DATA
data_document_id = 2, name = NEW_DOCUMENT
Also I'd can to write code like this:
public Human updateHuman(Long humanId, updateHuman){
Human human = humanRepository.findById(humanId);
Long id = human.getDocumentData().getId();
human.setDocumentData(updateHuman.getDocumentData());
human.getDocumentData().setId(id);
humanRepository.save(human);
}
Then it's would work fine. But I have like 40 fields and 10 of them Lists and 10 of them another entities with too many fields.
I just want to find the easiest way how I can update the object. I thought about query, but I'm not sure if it will work.
Just I have a HUMAN to UPDATED_SOME_FIELDS = HUMAN_WITH_UPDATED_SOME_FIELDS.
between, I solved my problem with 'spring beanutils copyproperties' maybe it helps to somebody.
Related
I have two entities: Customer and Address.
It is a bidirectional relationsship - one address can have many customers (oneToMany) and one customer can only have one address (manyToOne).
Executing GET request for customers returns:
[
{
"id": 1,
"name": "Foo",
"contact": "5512312",
"email": "Foo#gmail.com",
"address": {
"id": 1,
"street": "X",
"postalCode": 123,
"houseNo": "10",
"city": "New York"
}
}
]
When a new customer, with the exact same address properties as the one exists in DB - is being added with POST request, the json response returns the foreign key related to the existing object in DB instead of the object itself:
[
{
"id": 1,
"name": "Foo",
"contact": "5512312",
"email": "Foo#gmail.com",
"address": {
"id": 1,
"street": "X",
"postalCode": 123,
"houseNo": "10",
"city": "New York"
}
},
{
"id": 2,
"name": "Bar",
"contact": "5512312",
"email": "Bar#gmail.com",
"address": 1 <----------- it returns the foreign key instead of the object
}
]
So what I expect is that whenever a new customer, that has an address that already exists in the DB, is being added - it should return the address object instead of the foreign key from the json response.
Code:
Customer.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Customer {
#Id
#SequenceGenerator(
name = "customer_sequence",
sequenceName = "customer_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "customer_sequence"
)
private Long id;
private String name;
private String contact;
private String email;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", nullable = false)
private Address address;
[...]
Address.java
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#Entity
#Table
public class Address {
#Id
#SequenceGenerator(
name = "address_sequence",
sequenceName = "address_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "address_sequence"
)
private Long id;
private String street;
private int postalCode;
private String houseNo;
private String city;
#JsonIgnore
#OneToMany(mappedBy = "address")
private Set<Customer> customers;
[...]
CustomerController.java
//...
#PostMapping
public void createCustomer(#RequestBody Customer customer) {
customerService.createCustomer(customer);
}
[...]
And the service that saves the customer to the DB which also makes sure that no address is being created if already exists in database (it checks on every property to be equal from the param):
//...
public void createCustomer(Customer customer) {
Optional<Customer> customerWithExistingAddress = customerRepository.findAll()
.stream()
.filter(x -> x.getAddress().equals(customer.getAddress()))
.findFirst();
customerWithExistingAddress.ifPresent(c -> customer.setAddress(c.getAddress()));
customerRepository.save(customer);
}
[...]
You're probably getting this behavior because of JsonIdentityInfo, so it's a serialization problem you have not a persistence one. I'm assuming you're using a relational database (Hibernate for NoSql has Jpa like annotations but then that would make this a different problem) and that data is being persisted correctly.
See the javadocs:
In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values
I have following entities:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private ProfileContacts profileContacts;
...
}
and
#Entity
#Table(name = "profile_contacts")
public class ProfileContacts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
I am trying to update it by sending this JSON with update to REST controller:
{
"id": 1,
"description": "an update",
"profileContacts": {
"firstName": "John",
"lastName": "Doe"
}
}
so in the end it calls
profileRepository.save(profile);
where profileRepository is instance of ProfileRepository class:
public interface ProfileRepository extends JpaRepository<Profile, Long> {
}
which is spring-data-jpa interface.
But each time after such update it updates profile table but adds new row to profile_contacts table (table which corresponds to ProfileContactsentity) instead of updating existing ones.
How can I achieve updating?
As per your JSON structure. Yes it will create new profileContacts entry for every time.
The problem every time while saving profile entity you are passing "id": 1 that means Hibernate can identify the entity by this id value (primary key) but for profileContacts mapping you are not sending the id that's why Hibernate considering it has a new entity every time.
To update your profileContacts entity make sure to pass the id of it.
Example:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id" : yourEntityId
"firstName": "John",
"lastName": "Doe"
}
}
Well, that's the expected behavior.
You're not telling hibernate to update the profileContacts.
For the framework to be able to update it, you need to send the profileContact's primary key - which in your case is the ProfileContacts#id.
Something like this:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1
"firstName": "John",
"lastName": "Doe"
}
}
Need to specify the join column in the parent Entity.
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
**#JoinColumn(name = "id")** //
private ProfileContacts profileContacts;
...
}
Now when you try to save Profile entity it will save the child entity also.
And also needs to include Id in jason request for child entity also
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
}
Ok, I see the problem. As #Matheus Cirillo pointed out, you need to tell the hibernate to update the row.
Now, how do you tell the hibernate to update a row - By providing the primary key of the existing row.
But, creating an object with the primary key set is not enough. You need that entity class to be attached to the entity manager and the persistence context.
You can have something like,
//This attaches the entity to the entity manager
ProfileContacts existingProfileContacts = profileContactRepository.getOne(2);
Profile profile = new Profile();
....
....
profile.setProfileContacts(existingProfileContacts);
profileRepository.save(profile);
I hope this helps.
Does not save an enum value in the database, I think that's it, or I do not understand, I try to save since the console shows me a data, but when I ask from the database I get another value
#Entity
#Table(name = "User")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(unique = true, name = "email")
private String email;
#Column(name="rol")
Rol rol;
#Column(name = "life")
Life life;
public User() {
}
}
i have this in the .info, show this message "OWNER"
Set<User> users =new HashSet<>();
log.info("CONSOLE"+ user.getRol());
users.add(user);
meet.setUsers(users);
return meetRepository.save(meet);
but in swagger i get other value
ROL: PARTICIPANT
[
{
"id": 1,
"name": "string2",
"state": "pause",
"bet": {
"profit": 0,
"inversion": 0
},
"users": [
{
"id": 1,
"name": "string",
"password": "string",
"email": "ema",
"rol": "PARTICIPANT",
"life": "suspend"
}
]
}
]
If your fields rol and life are Enums you have to declare them as Enums with #Enumerated. There are two options. Default will store the ordinal number. Or you can choose to use the string name to store in the DB. That's the better option in terms of maintainability:
#Enumerated(EnumType.STRING)
#Column(name="rol")
Rol rol;
#Enumerated(EnumType.STRING)
#Column(name = "life")
Life life;
Two remarks:
When the database field has the same name as the attribute you can omit #Column. And if the table has the same name as the entity this is also true for the #Table annotatoin.
Read more about Enums and JPA and if you really should use it in one of my articles: https://72.services/de/should-you-use-enums-with-jpa/
When you save a Enum to the database as stated above you have the option of saving it as String or as Numerical Ordinal. The numerical option is very painfull because every time you need to update your enum values or change the order of the values you will mess with your database options for the field. Also another thing that is very painfull even when you store the strings on the database is that when you are using some ORM such as Hibernate and you have NULL values. So keep your enums as strings and avoid NULL value.
I have two tables one is customer and another one is customerDepartment. Customer is having one to many relationship with customerDepartment.
I have a specific search condition where i need to search for the department name, If it equals i need to retrieve all the customer department rows including the customers.
This is what i have tried to get the results
public interface CustomerRepository extends JpaRepository<Customer,Integer>{
#Query(value="select DISTINCT c from Customer c left join c.custDept cd where cd.deptName like %?1% ")
Page<Customer> findByName(String name, Pageable pageable);
}
Customer
#Entity
#Table(name="customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name= "customer_no",updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int customerNo;
#Column(name= "customer_name")
private String customerName;
#Column(name= "industry")
private String industry;
#JsonManagedReference
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL,
fetch=FetchType.LAZY)
private Set<CustomerDepartment> custDept;
}
CustomerDepartment:
#Entity
#Table(name = "customer_department")
public class CustomerDepartment implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "dept_id",updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int depId;
#Column(name = "dept_name")
private String deptName;
#Column(name = "primary_contact")
private String primaryContact;
#JsonBackReference
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
When i searched for DepartmentName=it, The above JPA Query returning the below results
{
"content": [
{
"customerNo": 33,
"customerName": "Test1",
"industry": "High-Tech",
"country": "Australia",
"state": "Sa-Jose",
"city": "Sydney",
"custDept": [
{
"depId": 34,
"deptName": "It",
"primaryContact": "Banglore,Kormangala",
},
{
"depId": 35,
"deptName": "sales",
"primaryContact": "Banglore,Kormangala",
}
]
}
]
}
What i am expecting is more like.
{
"content": [
{
"customerNo": 33,
"customerName": "Test1",
"industry": "High-Tech",
"country": "Australia",
"state": "Sa-Jose",
"city": "Sydney",
"custDept": [
{
"depId": 34,
"deptName": "It",
"primaryContact": "Banglore,Kormangala",
}
]
}
]
}
If this is not possible in JPA, Is there any way i can do this.
Thanks for the help
Yea, I thought so. I commented that "I'm thinking your query is okay but when the result is marshalled to JSON then all of the associated departments are being retrieved. You should look at your sql output and debug and check query results before marshalling to see if that is the case." I went ahead and played around with it and I was more or less correct.
The problem is that you have not fetched the custDept set with your query so when the customer is marshalled for your rest response an additional query is executed to get the values and the additional query just queries everything.
2019-05-25 14:29:35.566 DEBUG 63900 --- [nio-8080-exec-2] org.hibernate.SQL : select distinct customer0_.customer_no as customer1_0_, customer0_.customer_name as customer2_0_, customer0_.industry as industry3_0_ from customer customer0_ left outer join customer_department custdept1_ on customer0_.customer_no=custdept1_.customer_no where custdept1_.dept_name like ? limit ?
2019-05-25 14:29:35.653 DEBUG 63900 --- [nio-8080-exec-2] org.hibernate.SQL : select custdept0_.customer_no as customer4_1_0_, custdept0_.dept_id as dept_id1_1_0_, custdept0_.dept_id as dept_id1_1_1_, custdept0_.customer_no as customer4_1_1_, custdept0_.dept_name as dept_nam2_1_1_, custdept0_.primary_contact as primary_3_1_1_ from customer_department custdept0_ where custdept0_.customer_no=?
If you want only what your query supplies you need to do a fetch so the custDept the set is initialized before being marshalled. There are also other issues with your query. You should use a sql parameter :deptName and you should declare that, and you should supply a countQuery since you are returning a Page.
public interface CustomerRepository extends JpaRepository<Customer,Integer>{
#Query(value="select DISTINCT c from Customer c left join fetch c.custDept cd where cd.deptName like %:deptName% ",
countQuery = "select count ( DISTINCT c ) from Customer c left join c.custDept cd where cd.deptName like %:deptName% ")
public Page<Customer> findByName(#Param("deptName") String deptName, Pageable pageable);
Worked for me. Now only the original query is executed and the result is correct.
{
"content": [
{
"customerNo": 1,
"custDept": [
{
"deptName": "it"
}
]
}
],
Finally note that it is better to use Integer for the #Id in your entities as per spring documentation.
I have the ff. pojos: i make it short for simplicity
public class CaseOutlineHeader{
#Id
#GeneratedValue(generator = "COH_SequenceStyleGenerator")
#Column(name = "outline_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "outline_id", nullable = true, referencedColumnName = "outline_id",
foreignKey = #ForeignKey(name = "FK_COH_REF_COD_01"))
private List<CaseOutlineDetails> caseOutlineDetails;
}
public class CaseOutlineDetails{
#Id
#GeneratedValue(generator = "COD_SequenceStyleGenerator")
#Column(name = "ID")
private Long id;
#ManyToOne(targetEntity = MaintCaseEvent.class)
#JoinColumn(name = "EVENT_ID", nullable = false,
foreignKey = #ForeignKey(name = "FK_COD_REF_CASE_EVENT"),
referencedColumnName = "CASE_EVENT_ID")
private MaintCaseEvent eventId;
#Column(name = "outline_id")
private Long outlineId;
}
And then my service: It's pretty straightforward if the passed json of COD contains id that is not null it will update COH along with the COD . else if i passed a null id of COD i will save as a new entry in COD.
#Override
#Transactional
public CaseOutlineHeader update(CaseOutlineHeader caseoutline) {
LOG.info("SERVICE: Updating CaseOutlineHeader...{}", caseoutline);
if (!CollectionUtils.isEmpty(caseoutline.getCaseOutlineDetails())) {
caseoutline.getCaseOutlineDetails().forEach(cod -> {
if (cod.getId() != null) {
cod.setUpdatedBy(cod.getUpdatedBy());
cod.setUpdatedDate(new Date());
caseOutlineHeaderRepository.update(caseoutline);
} else {
cod.setOutlineId(caseoutline.getId());
cod.setCreatedBy("dwade");
caseOutlineDetailsRepository.save(cod);
}
});
}
return caseoutline;
}
Passed json data in swagger:
{
"id": 1,
"caseOutlineDetails": [
{
"eventId": { "id": 1 },
"updatedBy": "kobe",
"id" : 1,
"outlineId" : 1
},
{
"eventId": { "id": 2},
"outlineId" : 1
}
],
"caseType": "string",
"code": "updateC",
"description": "updateD",
"updatedBy": "kobe",
"updatedDate": "2018-07-23T00:55:16.767Z"
}
As you can see i passed a second data in COD which dont have an id property it will be saved. while the first one will be updated which contains an id.
Here is my repo:
#Override
public CaseOutlineHeader update(CaseOutlineHeader caseOutlineHeader) {
em.merge(caseOutlineHeader);
em.flush();
return caseOutlineHeader;
}
#Override
public CaseOutlineDetails save(CaseOutlineDetails caseOutlineDetails) {
LOG.info("REPOSITORY : SAVE = " + caseOutlineDetails);
em.persist(caseOutlineDetails);
return caseOutlineDetails;
}
But in my logger i dont know why it inserts a new record first with id and foreign key only as the value. But on the last line of insert statement is successful
Hibernate: insert into case_outline_details (access_level...
Hibernate: update case_outline_header set access_level=?...
Hibernate: update case_outline_details set access_level=?...
Hibernate: select seq_cod.nextval from dual
Hibernate: insert into case_outline_details (access_level...
It should just be update,update,then insert.
You declared caseOutlineDetails with cascade = CascadeType.ALL. So when you're updating a CaseOutlineHeader, hibernate will automatically update the list, inserting rows if needed. Then you're doing your stuff and insert it again.
when deserializing the serialized entity instance, need to get a persistent
entity instance from a persistence context and update its fields with
new values from this detached instance
Hibernate merge finds an entity instance by id taken from the passed object (either
an existing entity instance from the
persistence context is retrieved, or a new instance loaded from
the database); copies fields from the passed object to this
instance; returns newly updated instance.
so if id is null that does not finds instance of the object so merge inserts the new
record and returns the object.