I have 4 entities:
Profile which has a relation with companyContract:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "profile", cascade = { CascadeType.ALL })
#Fetch(value = FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<CompanyContract> companyContracts;
CompanyContract which has a relation with timesheet:
#OneToMany(mappedBy = "companyContract", cascade = { CascadeType.ALL },orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<Timesheet> timesheets;
#ManyToOne
#JoinColumn(name = "IDPROFILE")
private Profile profile;
Timesheet which has a relation with invoice:
#OneToMany(mappedBy = "timesheet", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<Invoice> invoices;
#ManyToOne
#JoinColumn(name = "IDCONTRACT")
private CompanyContract companyContract;
Invoice:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_TIMESHEET")
private Timesheet timesheet;
So As you can see here, I'm using org.hibernate.annotations.CascadeType.DELETE_ORPHAN so I can delete the children of a parent.
If I execute this:
Profile p = companyContract.getProfile();
p.getCompanyContracts().remove(companyContract);
companyContract.setProfile(null);
profileService.update(p);
---> The order of removal should be:
Remove invoices --> Timesheets --> CompanyContract, No ?
And instead I'm getting this error:
org.hibernate.exception.ConstraintViolationException: Column 'IDCONTRACT' cannot be null
And I've checked, this error happens after profileService.updateProfile(p);
The problem seems to be that the column IDCONTRACTin the table that holds Timesheets has a NOT NULL restriction. Remove it and try again.
If you're autogenerating the schema, try adding #Basic(optional = true) to Timesheet.companyContract:
#Basic(optional = true)
#ManyToOne
#JoinColumn(name = "IDCONTRACT")
private CompanyContract companyContract;
This is working completely fine. I see all associated child entities getting successfully deleted.
Check the below code.
Use orphanRemoval = true like below instead of deprecated way
org.hibernate.annotations.CascadeType.DELETE_ORPHAN , which you are using.
#OneToMany(mappedBy = "companyContract", cascade = { CascadeType.ALL },orphanRemoval = true, fetch = FetchType.EAGER)
Find code below
public void check() {
System.out.println("Start check() in DummyDAOImpl");
Session session = sessionFactory.openSession();
CompanyContract companyContract = session.get(CompanyContract.class, 2);
Profile p = companyContract.getProfile();
p.getCompanyContracts().remove(companyContract);
companyContract.setProfile(null);
session.update(p);
session.flush();
session.close();
System.out.println("Executed check() in DummyDAOImpl");
}
Related
I have two entities BookingLegEntity and BookingEntity which reference each other. But anytime I try to retrieve them from the database (e.g. via findByUuid), BookingLegEntity.belongsTo remains null.
Here are my entities:
#Entity
#Table(name = "BOOKING_LEG")
#SQLDelete(sql = "UPDATE BOOKING_LEG SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingLegEntity {
#Id
#Column(name = "ID", unique = true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "UUID", nullable = false)
private UUID uuid;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "BELONGS_TO")
private BookingEntity belongsTo;
// ..
#ManyToOne
#JoinColumn(name = "DISTRIBUTOR")
private DistributorEntity distributor;
#Column(name = "TRANSPORT_TYPE")
#Convert(converter = TripTypeEnumConverter.class)
private TripTypeEnum transportType;
// ...
}
#Entity
#Table(name="BOOKINGS")
#SQLDelete(sql = "UPDATE BOOKINGS SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingEntity {
#Id
#Column(name="ID", unique=true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="BOOKING_ID")
#Convert(converter = BookingIdConverter.class)
private BookingId bookingId;
#ManyToOne
#JoinColumn(name ="BOOKED_BY")
private UserEntity bookedBy;
// ..
#OneToMany(mappedBy = "belongsTo", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BookingLegEntity> bookingLegs = new HashSet<>();
// ...
}
Here is my repository:
#Repository
public interface BookingLegRepository extends JpaRepository<BookingLegEntity, Long> {
Optional<BookingLegEntity> findByUuid(UUID id);
// ...
}
The values in the database itself look correct:
What is really strange is that this has worked before (belongsTo was not null) but suddenly stopped working. Does anyone has any idea as to what we might do wrong here?
Do not use cascade = CASCADEType.ALL on your ManyToOne annotation, because removing one BookingLeg will cause a removal of all in corresponding Booking
The solution should be to use
cascade = CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) in its stead.
I would Truncate Cascade or Delete from Bookings where original_itinerary is null before i move on to using the new entities.
Sincerely hope it helps. (No hate if it doesn't pls)
Edit : i didnt see that comment by #dey, its my own. :P saw his comment after posting my ans
#Entity
public class DocumentConsolidated {
#Id private UUID id;
#OneToOne(fetch = FetchType.EAGER, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "metadata_id")
private DocumentMetadata documentMetadata;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "documentConsolidated")
private DocumentConfiguration documentConfiguration;
}
#Entity
public class DocumentConfiguration {
#Id private UUID id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private DocumentConsolidated documentConsolidated;
}
// Service code:
QDocumentConsolidated qDoc = QDocumentConsolidated.documentConsolidated;
(JPQLQueryFactory) queryFactory
.select(Projections.fields(qDoc, /*qDoc.id, */qDoc.documentConfiguration, qDoc.documentMetadata))
.from(qDoc)
.innerJoin(qDoc.documentConfiguration)
.fetch();
This is only going 2 ways:
with qDoc.id: id is present, documentConfiguration is null
without qDoc.id: id is null, documentConfiguration is present
Why?
What I already checked: Hibernate query is always bringing documentConfiguration fields when I run it in a Postgres client. Bot documentMetadata is present in both cases.
Problem is not solved, but I worked around it by removing Projections from the mix:
// Service code:
QDocumentConsolidated qDoc = QDocumentConsolidated.documentConsolidated;
QDocumentConfiguration qCfg = qDoc.documentConfiguration;
QDocumentMetadata qMeta = qDoc.documentMetadata;
return queryFactory
.select(qDoc, qCfg, qMeta) // <-- get rid of Projections
.from(qDoc)
.innerJoin(qCfg) // <-- manual join on Lazy entity (qMeta is auto-joined)
.fetch().stream()
.map(tuple -> { // <-- entity creation, similar with Projections.bean()
DocumentConsolidated documentConsolidated = Objects.requireNonNull(tuple.get(qDoc));
documentConsolidated.setDocumentMetadata(tuple.get(qMeta));
documentConsolidated.setDocumentConfiguration(tuple.get(qCfg));
return documentConsolidated;
})
.collect(Collectors.toList());
I want to save a relationship of two or more persons to each other with specific elements. A person with its skill and on a specific address should be connected to each other. Normaly I creat a table to save the ID's of each element and create a row in the table (in normal MySQL with PHP).
How do I solve this problem in Java Spring Boot (JPA-Hibernate-MySQL)?
When I create (or better ask for) an "object" (detached) from the repository of each element and want to save it in a new "repository" (database) then I got an error.
PartnerConnectionServiceImplementation (.java)
#Service
public class PartnerConnectionServiceImpl implements PartnerConnectionService {
#Autowired
private PartnerConnectionRepository partnerConnectionRepository;
#Autowired
private DanceSkillServiceImpl danceSkillDatabaseService;
#Autowired
private AddressLocationServiceImpl addressLocationDatabaseService;
#Autowired
private UserProfileServiceImpl userProfileDatabaseService;
#Override
public Optional<PartnerConnection> connectPartnersWithDanceSkillAndAddressLocation(long userIdPartner1, long danceSkillIdPartner1, long addressLocationIdPartner1, long userIdPartner2, long danceSkillIdPartner2, long addressLocationIdPartner2) {
Optional<UserProfile> userProfile1 = this.userProfileDatabaseService.getUserById(userIdPartner1);
Optional<UserProfile> userProfile2 = this.userProfileDatabaseService.getUserById(userIdPartner2);
Optional<DanceSkill> danceSkill1 = this.danceSkillDatabaseService.getDanceSkillById(danceSkillIdPartner1);
Optional<DanceSkill> danceSkill2 = this.danceSkillDatabaseService.getDanceSkillById(danceSkillIdPartner2);
Optional<AddressLocation> addressLocation1 = this.addressLocationDatabaseService.getAddressLocationById(addressLocationIdPartner1);
Optional<AddressLocation> addressLocation2 = this.addressLocationDatabaseService.getAddressLocationById(addressLocationIdPartner2);
if (
(userProfile1.isPresent()) && (userProfile2.isPresent())
){
Optional<PartnerConnection> theConnection = getPartnerConnectionOfPartners(
userProfile1.get(),
userProfile2.get());
if (theConnection.isPresent()) {
return theConnection;
}
}
if (
(userProfile1.isPresent()) && (userProfile2.isPresent()) &&
(danceSkill1.isPresent()) && (danceSkill2.isPresent()) &&
(addressLocation1.isPresent()) && (addressLocation2.isPresent())
) {
PartnerConnection newPartnerConnection = new PartnerConnection(
null,
userProfile1.get(),
danceSkill1.get(),
addressLocation1.get(),
userProfile2.get(),
danceSkill2.get(),
addressLocation2.get()
);
this.partnerConnectionRepository.save(newPartnerConnection);
return Optional.of(newPartnerConnection);
}
return Optional.empty();
}
...
PartnerConnection (.java)
// indicates the connecitons between partners/ users
#NoArgsConstructor
#AllArgsConstructor
#Data
#Entity
#Table(name = "partner_connection")
public class PartnerConnection {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
#JoinColumn(
name = "firstmessage_fk", // foreign key
nullable = true
)
private UserMessage firstMessage;
#ManyToOne(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
#JoinColumn(name = "onepartner_fk", // foreign key
nullable = false)
private UserProfile firstPartner;
#OneToOne(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
#JoinColumn(
name = "firstpartnerdanceskill_fk", // foreign key
nullable = false
)
private DanceSkill firstPartnerDanceSkill;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinColumn(name = "firstpartneraddresslocation_fk", // foreign key
nullable = false)
private AddressLocation firstPartnerAddressLocation;
#ManyToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinColumn(name = "secondpartner_fk", // foreign key
nullable = false)
private UserProfile secondPartner;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinColumn(name = "secondpartnerdanceskill_fk", // foreign key
nullable = false)
private DanceSkill secondPartnerDanceSkill;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JoinColumn(name = "secondpartneraddresslocation_fk", // foreign key
nullable = false)
private AddressLocation secondPartnerAddressLocation;
public PartnerConnection(UserMessage firstMessage, UserProfile firstPartner, DanceSkill firstPartnerDanceSkill, AddressLocation firstPartnerAddressLocation, UserProfile secondPartner, DanceSkill secondPartnerDanceSkill, AddressLocation secondPartnerAddressLocation) {
this.firstMessage = firstMessage;
this.firstPartner = firstPartner;
this.firstPartnerDanceSkill = firstPartnerDanceSkill;
this.firstPartnerAddressLocation = firstPartnerAddressLocation;
this.secondPartner = secondPartner;
this.secondPartnerDanceSkill = secondPartnerDanceSkill;
this.secondPartnerAddressLocation = secondPartnerAddressLocation;
}
}
The error org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: ... nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: ... appears on this.partnerConnectionRepository.save(newPartnerConnection);
Do you have any easy to understand suggestions?
I think your method should contain #Transactional annotation. You have marked all relationships as LAZY, so if you want to get them You need a transaction to load them into managed state from detached one then be able to attach it to the object you want to save
Recently I created a project and in my models I have ManyToMany back references.
My model is like below:
#Entity
public class A {
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
private Set<A> children = new HashSet<>();
#ManyToMany
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JoinTable(
name = "link_a_recursion",
joinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id")
)
private Set<A> parents = new HashSet<>();
//I removed the rest ( setter, getter and other fields )
}
When I fetch this model and I want to load children it throws StackOverFlowException error ( Recursive exception )
I want to know is there any way to say to hibernate just load one level of associate and don't go deep.
For clarify:
A.children[0].children[0].children should be null to stop recursion
I want to load the first children not all the children inside of the other children
Edited:
I add another entity:
#Entity
public class B {
#OneToMany(mappedBy = "b")
private Set<A> entities = new HashSet<>();
//setter, getter
}
and add below to A entity:
#ManyToOne
private B b;
then I changed below:
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
to
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
and in my BService my findOne function is like below:
#Transactional(readOnly = true)
public B findOne(Long id) {
B b = repository.findOne(id);
for (A a: b.getEntities()) {
a.getChildren().size();
}
return b;
}
but again I'm getting error :(
Try lazy fetch instead
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
I have a error with Hibernate:
org.hibernate.AnnotationException: mappedBy reference
an unknown target entity property: ch.zkb.documenz.backend.model.Template.user
in ch.zkb.documenz.backend.model.User.templates
I have two Tables: User and Template, but in Template I need to use the id of the user in: createdBy, lockBy or lastUpdateBy, I think I have to use the #onetomany like in my example, but something it's incorrect, What is the best practice to do this then?
public class User implements Serializable {
#Id
#Column(name = "user_id", unique = true, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Template> templates;
public class Template implements Serializable {
#Id
#Column(name = "template_id", unique = true, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "createdBy")
private User createdBy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lastUpdateBy")
private User lastUpdateBy;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lockBy")
private User lockBy;
EDIT, I have now a Problem with the Bidirectional LAZY load, I want to get the user wo created the template but I can't.. always is NULL, but in the DB is stored properly
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "createdBy")
private Set<Template> createdTemplates;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "createdBy", referencedColumnName = "user_id", nullable = false)
private User createdBy;
You schould map to your private keys seperately, then in an transient method you can merge them.
private Set<Template> templates = new HasSet<Template>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "createdBy")
private Set<Template> createdByTemplates;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "lastUpdateBy")
private Set<Template> lastUpdateByTemplates;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "lockBy")
private Set<Template> lockByTemplates;
#Transient
public Set<Template> getTemplates(){
if(getCreatedByTemplates() != null){
templates.addAll(getCreatedByTemplates());
}
if(getLockByTemplates() != null){
templates.addAll(getLockByTemplates());
}
if(getLastUpdateByTemplates() != null){
templates.addAll(getLastUpdateByTemplates());
}
return templates;
}
The exception message is clear about what doesn't work, the mappedBy must refer to the attribute name that you do have in the other entity,
in your Template entity there's no attribute named "user", try to remove the mappedBy.
I believe mapped by is looking for bean name "user" in Template.
public class User implements Serializable {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Template> templates;
public class Template implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
Change is variable name createdby to user in Template Class would help