How to update child entity when updating parent entity? - java

I have two entities Space as parent and SpaceInactivityDate as child with relation #OneToMany and #ManyToMany and with space_id as a foreign key.
The insert operation works fine but when I tried to update, the Space table was updated but in the SpaceInactivityDate table instead of updating rows he added new lines. I have used the CascadeType=All and cascade = {CascadeType.PERSIST,CascadeType.MERGE} but usually I had the same result.
Space.java
#Entity
#Table(name = "space")
public class Space {
#OneToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "space", fetch = FetchType.LAZY)
private Set<SpaceInactivityDate> spaceInactivityDates = new HashSet<SpaceInactivityDate>();
// getters+setters
}
SpaceInactivityDate .java
#Table(name="space_incativity_date")
#Entity
public class SpaceInactivityDate {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Integer id;
#Column(name = "inactivity_start_date", nullable = false, insertable = true, updatable = true)
#Type(type = "ec.ep.dit.sip.expomep.persistency.type.PersistentLocalDate")
private LocalDate inactivityStartDate;
#Column(name = "inactivity_end_date", nullable = false, insertable = true, updatable = true)
#Type(type = "ec.ep.dit.sip.expomep.persistency.type.PersistentLocalDate")
private LocalDate inactivityEndDate;
#JoinColumn(name="space_id",referencedColumnName="id")
#ManyToOne(optional=false,fetch=FetchType.LAZY)
private Space space;
}
SpaceServiceImpl.java
this.spaceDao.save(space);

Related

JPA Join Tables with two fields, one of them a primary key

the problems is when #ManyToOne make a #Joincolumn ID_REPORT (it´s a primary key ) and #Joincolumn ID_TEMPLATE_DEFAULT
Repeated column in mapping for entity: CurReport column: id_report (should be mapped with insert="false" update="false")
Code
First table CUR_TEMPLATE
CREATE TABLE CUR_TEMPLATE
(
ID_REPORT NUMBER(5,0) NOT NULL,
ID_TEMPLATE NUMBER(5,0) NOT NULL,
-- Other fields
);
ALTER TABLE CUR_TEMPLATE ADD CONSTRAINT PK_CUR_TEMPLATE PRIMARY KEY (ID_REPORT, ID_TEMPLATE)
-- CUR_TEMPLATE foreign keys
ALTER TABLE CUR_TEMPLATE ADD CONSTRAINT FK_CUR_PLAN_REFERENCE_CUR_REPO FOREIGN KEY (ID_REPORT)
REFERENCES CUR_REPORTS (ID_REPORT);
Second table CUR_REPORTS
-- CUR_REPORTS definition
CREATE TABLE CUR_REPORTS
(
ID_REPORT NUMBER(3,0) NOT NULL,
NAME_REPORT VARCHAR2(100) NOT NULL,
-- other fields
ID_TEMPLATE_DEFAULT NUMBER(5,0),
-- other fields
) ;
ALTER TABLE CUR_REPORTS ADD CONSTRAINT PK_CUR_REPORTS PRIMARY KEY (ID_REPORT)
ALTER TABLE CUR_REPORTS CONSTRAINT FK_CUR_REPO_REFERENCE_CUR_PLAN FOREIGN KEY (ID_REPORT, ID_TEMPLATE_DEFAULT)
REFERENCES CUR_TEMPLATE (ID_REPORT, ID_TEMPLATE)
First table CUR_REPORTS Entity CurReport
#Entity
#Table(name = "CUR_REPORTS")
#IdClass(CurPlantillaPK.class)
#Getter
#Setter
public class CurReport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_REPORT", nullable = false)
private Long id;
#Column(name = "NAME_REPORT", nullable = false, length = 100)
private String nombreReporte;
#ManyToOne(fetch = FetchType.LAZY) <---WHERE IS THE PROBLEM
#JoinColumn(name = "ID_REPORT", referencedColumnName = "ID_REPORTE")
#JoinColumn(name = "ID_TEMPLATE_DEFAULT", referencedColumnName = "ID_TEMPLATE")
private CurTemplate curTemplate;
#OneToMany(mappedBy = "curReport")
private Set<CurTemplate> curTemplates= new LinkedHashSet<>();
}
Second table CUR_TEMPLATE Entity CurReport
#Entity
#Table(name = "CUR_TEMPLATE")
#IdClass(CurPlantillaPK.class)
#Getter
#Setter
public class CurTemplate {
#Id
#Column(name = "ID_REPORT", nullable = false)
private Long idReport;
#Id
#Column(name = "ID_TEMPLATE", nullable = false)
private Long idTemplate;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "ID_REPORT", foreignKey = #ForeignKey(name = "FK_CUR_PLAN_REFERENCE_CUR_REPO"), referencedColumnName = "ID_REPORT", insertable = false, updatable = false)
private CurReport curReport;
}
When i add insertable=false, updatable=false
#JoinColumn(name = "ID_REPORT", referencedColumnName = "ID_REPORT", insertable=false, updatable=false)
said
Mixing insertable and non insertable columns in a property is not allowed: CurTemplate
How could i map those relationships?
How resolve the #JoinColumn when one field of the FK are column PK?
You can use a derived identity and map CurTemplate like this:
#Entity
#Table(name = "CUR_TEMPLATE")
#IdClass(CurTemplatePK.class)
#Getter
#Setter
public class CurTemplate {
#Id
#Column(name = "ID_TEMPLATE", nullable = false)
private Long idTemplate;
#Id
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "ID_REPORT", foreignKey = #ForeignKey(name = "FK_CUR_PLAN_REFERENCE_CUR_REPO"), referencedColumnName = "ID_REPORT", insertable = false, updatable = false)
private CurReport curReport;
}
Then you will need an #IdClass like this:
public class CurTemplatePK {
Long idTemplate; // matches name of #Id attribute
Long curReport; // matches name of #Id attribute and type of CurReport PK
}
Then you should use a basic mapping for the default template key and provide a getter for the default template object:
#Entity
#Table(name = "CUR_REPORTS")
#IdClass(CurPlantillaPK.class)
#Getter
#Setter
public class CurReport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_REPORT", nullable = false)
private Long id;
#Column(name = "NAME_REPORT", nullable = false, length = 100)
private String nombreReporte;
#Column(name = "ID_TEMPLATE_DEFAULT")
private Long idDefaultTemplate;
#OneToMany(mappedBy = "curReport")
private Set<CurTemplate> curTemplates= new LinkedHashSet<>();
public CurTemplate getDefaultTemplate() {
return this.curTemplates.stream()
.filter(template -> template.getIdTemplate().equals(idDefaultTemplate))
.findFirst()
.orElse(null);
{
}
If you want to allow clients to set the default template, you will need to implement a setter that first verifies that the new default template is already in the set curTemplates.

How to map only single column with hibernate?

I want to join a single column from another table to one of my #Entity classes:
Currently it works as follows:
#Entity
public class Product {
#Id
private long id; //autogenerated
String type; //used for mapping
#ManyToOne
#JoinColumn(name = "type", insertable = false, updatable = false)
ProductMapping mapping;
}
#Entity
public class ProductMapping {
#Id
String type;
String longname;
}
Question: how could I replace the #ManyToOne mapping to directly map to String longname?
//TODO: how to directly map to 'mapping.longname'?
#JoinColumn(name = "type", insertable = false, updatable = false)
String mapping.longname;
You can use #Formula annotation with a query like this :
#Formula("(select pm.longname from product_mapping pm where pm.COL_NAME = value)")
private String longName;
//give the column name of type from product_mapping table
OR
You can also use the below approach :
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "type", referencedColumnName = "COL_NAME", insertable = false, updatable = false)
ProductMapping mapping;
The other entity use #NaturalId annotation on the field.
#Entity
public class ProductMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
String type;
#NaturalId
#Column(name = "SOME_VALUE")
String longname;
}

OneToMany relationship does not store entity field

When saving my entities, child entities that work through the #OneToMany relationship are not saved to their tables. I can’t understand what’s the matter.
Employee:
#Entity
#Table(name = "EMPLOYEE", schema = PUBLIC)
public class Employee {
private String name;
private String lastname;
#OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, orphanRemoval = true)
List<EmployeePhoneNumber> employeePhoneNumbers = new ArrayList<>();
}
EmployeePhoneNumber:
#Entity
#Table(name = "EMPLOYEE_PHONES", schema = PUBLIC)
public class EmployeePhoneNumber {
#Id
#SequenceGenerator(allocationSize = 1, name = "SEQ_EMPLOYEE_PHONES", schema = PUBLIC,
sequenceName = "EMPLOYEE_PHONES_ID_SEQ")
#GeneratedValue(generator = "SEQ_EMPLOYEE_PHONES", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "employee_id", referencedColumnName = "id",
nullable = false, insertable = false, updatable = false)
private Employee employee;
#Column(name = "PHONE_NUMBER", unique = true, nullable = false)
private String phoneNumber;
#Enumerated(EnumType.STRING)
#Column(name = "NUMBER_TYPE", nullable = false)
private PhoneNumberType phoneNumberType;
}
How I set those fields and then save the entity:
EmployeePhoneNumber workPhone = new EmployeePhoneNumber();
workPhone.setPhoneNumber(workPhone);
workPhone.setPhoneNumberType(PhoneNumberType.WORK_PHONE);
EmployeePhoneNumber mobilePhone = new EmployeePhoneNumber();
mobilePhone.setPhoneNumber(mobilePhone);
mobilePhone.setPhoneNumberType(PhoneNumberType.MOBILE_PHONE);
EmployeePhoneNumber corporatePhone = new EmployeePhoneNumber();
corporatePhone.setPhoneNumber(corporatePhoneNumber);
corporatePhone.setPhoneNumberType(PhoneNumberType.CORPORATE_PHONE);
List<EmployeePhoneNumber> employeePhoneNumbers = employee.getEmployeePhoneNumbers();
employeePhoneNumbers.add(workPhone);
employeePhoneNumbers.add(mobilePhone);
employeePhoneNumbers.add(corporatePhone);
employee.setEmployeePhoneNumbers(employeePhoneNumbers);
employeeRepository.save(employee);
Upon completion of the method, I do not have a single error, everything works out correctly, only the tables are not filled - why?
You must also set the Employee reference in EmployeePhoneNumber because Hibernate will use this to save it.
workPhone.setEmployee(employee);
mobilePhone.setEmployee(employee);
corporatePhone.setEmployee(employee);
The best solution would be to create an addEmployeePhoneNumber method on the Employee like this:
public void addEmployeePhoneNumber(EmployeePhoneNumber phoneNumber) {
phoneNumber.setEmployee(this);
employeePhoneNumbers.add(phoneNumber);
}
That way you will not forget to set both sides of the relationship.

Spring Boot: How to create an Entity with a composite key

I was creating my classes for a project using a chart for practice purposes until I stumbled upon this order_items:
I had no problem creating an Entity like Orders or Products because I knew that for Orders I just had to do something like:
#Entity
#Table(name = "orders")
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private Integer orderId;
// rest of the code...
}
And for for Products something like:
#Entity
#Table(name = "products")
public class Products {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private Integer productId;
// rest of the code...
}
But the table order_items has the variables order_id and item_id, does that count as a composite key? If that is the case, how should those variables look in my OrderItems class?
#Entity
#Table(name = "order_items")
public class OrderItems {
#Column(name = "order_id")
private Integer orderId;
#Column(name = "item_id")
private Integer itemId;
// rest of the code...
}
I've checked different questions and they mention using #IdClass or #EmbeddableId for composite keys, but I'd like to confirm first if that is what I should do in this situation, unless it's not the case, maybe there are more approaches.
I'd really appreciate opinions and/or any article related to this, thank your for your time.
As you mentioned you can use #EmbeddableId.
Here is example :
#Embeddable
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class OrderItemsPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(insertable = false, unique = false, updatable = false, nullable = false,name = "order_id")
private Long orderId;
#Column(insertable = false, unique = false, updatable = false, nullable = false,name = "products_id")
private Long productsId;
}
And the Order Items Class.
#Entity
public class OrderItems {
#EmbeddedId
private OrderItemsPK id;
#OneToOne
#JoinColumn(name = "products_id", nullable = false, unique = false, insertable = false, updatable = false, referencedColumnName = "products_id")
private Products products;
#OneToOne
#JoinColumn(name = "orders_id", nullable = false, unique = false, insertable = false, updatable = false, referencedColumnName = "orders_id")
private Order order;
private Long itemId;
}

SpringData CrudRepository applies automaticlly cascade type

In my project I have to connect to existing database and do logic which updates two tables.
My setup is following:
#Entity
#Table(name = "DOCUMENTCONTENT")
#Getter
public class DocumentContent {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#OneToOne
#JoinColumn(name = "DOCUMENT_ID", insertable = false, updatable = false)
private Document document;
#Lob
#Column(name = "CONTENT")
#Setter
private byte[] content;
}
#Entity
#Table(name = "DOCUMENT")
#Getter
public class Document {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "document")
private DocumentContent documentContent;
#OneToMany(mappedBy = "document", fetch = EAGER)
private List<Attachment> attachments;
}
#Entity
#Table(name = "ATTACHMENT")
#Getter
public class Attachment {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne
#JoinColumn(name = "DOCUMENT_ID", insertable = false, updatable = false)
private Document document;
#ManyToOne
#JoinColumn(name = "CONTRACT_ID",updatable = false, insertable = false)
private Contract contract;
}
#Entity
#Table(name = "CONTRACT")
#Getter
public class Contract {
#Id
#Column(name = "ID", insertable = false, updatable = false)
private Long id;
#Column(name = "STATUS")
#Setter
private String status;
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID", insertable = false, updatable = false)
private Customer customer;
#OneToMany(mappedBy = "contract", fetch = EAGER)
private List<Attachment> attachments;
}
#Service
public class MyServiceImpl implements MyService {
#Autowired
private DocumentContentRepository documentContentRepository; // spring data Crud Repository
#Override
#Transactional
public void updateDocumentContent(SomeDto someDto) {
DocumentContent documentContent = documentContentRepository.findByDocumentId(someDto.getDocumentId());
documentContent.setContent(someDto.getBytes());
List<Contract> contracts = documentContent.getDocument().getAttachments()
.stream().map(Attachment::getContract).collect(toList());
contracts.forEach(contract -> contract.setStatus("SIGNED"));
documentContentRepository.save(documentContent);
}
}
When I fire method from above service I can notice those SQL in console output:
Hibernate: update documentcontent set content=? where id=?
Hibernate: update contract set status=? where id=?
I understand why jpa performed first update in documentcontent table, but I don't know why it did update in contract table aswell. As you can see I didn't use CascadeType.MERGE in any entity.
Can you explain me why this second update has been performed without declaring cascade type?
I doubt it has anything to do with Cascade at all, but with transactional write behind mechanism (more info). I believe you could also get rid of the line
documentContentRepository.save(documentContent);
since you are modifying two managed entities. At the end of the transaction hibernate persists all entities marked as modified by the dirty checking mechanism (more info).
You are getting 2nd query for the reason, you are modifying Status property of Contract.
JPA detect this change and try to update entity.
This is default CaseCadeType behaviour of #OneToMany
For further reading follow this link.

Categories

Resources