I have a Flight class and a AircraftReport class. The AircraftReport class contains an inbound flight and an outbound flight which I would both like to be mapped as a #OneToOne. How do I correctly define the relationship?
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
#Table
public class Flight implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "flight_sequence"
)
#SequenceGenerator(
name = "flight_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
private String callsign;
private Date date;
private String origin;
private String destination;
private String registration;
private String aircraftType;
#OneToOne(mappedBy = "--what should it be mapped by here--")
private AircraftReport aircraftReport;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table
public class AircraftReport implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "taxsheet_sequence"
)
#SequenceGenerator(
name = "taxsheet_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "inbound_flight_id")
private Flight inboundFlight;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "outbound_flight_id")
private Flight outboundFlight;
...
}
IMHO you don't need to use a bidirectional relationship for a OneToOne mapping. If you want the AircraftReport just get the AircraftReport and the Flight instances will come with it. Otherwise if you're not working with a report just get the Flight and be happy.
Also, I don't recommend using Cascade operations on a OneToOne, or at any time for that matter. Do you really ever want the Flight deleted?
Map to either inboundFlight or outboundFlight. You can't have one bidirectional relationship take the place of two of them. If you want two bidirectional relationships then add another to Flight.
#OneToOne(mappedBy = "inboundFlight")
private AircraftReport aircraftReportInbound;
#OneToOne(mappedBy = "outboundFlight")
private AircraftReport aircraftReportOutbound;
As you have two separate relationships from AircraftReport two Flight, you also have to separate relationships from Flight to AircraftReport.
#OneToOne(mappedBy = "inboundFlight")
private AircraftReport aircraftReportInbound;
#OneToOne(mappedBy = "outboundFlight")
private AircraftReport aircraftReportOutbound;
Related
My two entities have one to one relation
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#EqualsAndHashCode.Exclude
private Long id;
// other fields
#OneToOne(mappedBy="user", cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
private PasswordResetToken token;
// getters/setters and equals/hashcode
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "password_reset_token")
public class PasswordResetToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// other fields
#OneToOne(targetEntity = AppUser.class, fetch = FetchType.EAGER, cascade={CascadeType.REMOVE,CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(nullable = false, name = "user_id")
private AppUser user;
// getters/setters and equals/hashcode
I tried to delete my user entity by this method
public void deleteUser(Long id) {
resetTokenRepository.deleteAllByUserId(id);
userRepository.deleteById(id);
}
PasswordResetTokenRepository class which method I called in my service method, for deleting user I used regular hibernate method deleteById(Long id)
#Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
void deleteAllByUserId(Long id);
}
But when I try to delete by this method I got this error:
not-null property references a null or transient value : kpi.diploma.ovcharenko.entity.user.PasswordResetToken.user
I read several websites how to delete one to one relation, but their advices didn't help me. For example, I tried a lot of variants of annotation cascade={CascadeType.ALL}, tried all the variants(CascadeType.REMOVE,CascadeType.PERSIST and so on), all time I got the same error. Help me pls, to understand what I do wrong.
try this:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication .
There's an entity Box:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#ToString
#Table(name="box")
public class Box {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idbox", updatable = false, nullable = false)
private int idbox;
#Column(name = "brand")
private String brand;
#Column(name = "size")
private BigDecimal size;
#OneToMany
private Set<Content> content;
}
and an entity Content:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#ToString
#Table(name="content")
public class Content {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idcontent", updatable = false, nullable = false)
private int idcontent;
#Column(name = "amount")
private Integer amount;
#Column(name = "shape")
private String shape;
#ManyToOne
#JoinColumn
private Box box;
}
If I place
(cascade=CascadeType.REMOVE)
In "#ManyToOne" from Box, I delete that Box, keeping children entities (making the box content available to be used for another Box).
Instead, if I use
(cascade = CascadeType.ALL)
the Box is deleted, including children entities (deleting the box and its content).
Let's suppose that in some scenarios, it is needed to delete the Box and its content. Although, in opposite, sometimes I just need to delete the Box but keep its content.
How can I select my desired cascading type based on the above?
You can't dynamically change the CascadeType. You'll need to go with the weakest CascadeType and cascade everything above that yourself manually in your application code, i.e. explicitly invoke delete on the children in this case.
I am trying to save entities with relationships. I annotated the attributes but hibernate doesn´t use the parent key of the parent entity, it's always 0.
My first entity is a TrafficJam that looks like this:
#Entity
#Table(name = "traffic_jam", schema = "public", catalog = "dwh")
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class TrafficJamEntity {
#Id
#Column(name = "traffic_jam_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="traffic_jam_seq")
#SequenceGenerator(
name="traffic_jam_seq",
sequenceName="traffic_jam_id_sequence",
allocationSize=1
)
private long trafficJamId;
#Basic
#Column(name = "reading_id")
private long reading_id;
#OneToMany(mappedBy = "trafficJamEntity", cascade = CascadeType.PERSIST)
#Builder.Default
private Set<DisturbanceCourseEntity> disturbanceCourseEntitySet = new LinkedHashSet<>();
}
The child entity DisturbanceCourse looks like this:
#Entity
#Table(name = "disturbance_course", schema = "public", catalog = "dwh")
#IdClass(DisturbanceCourseEntityPK.class)
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class DisturbanceCourseEntity {
#Id
#Column(name = "traffic_jam_id")
private long trafficJamId;
#ManyToOne
#JoinColumn(name = "traffic_jam_id", nullable = false, insertable = false, updatable =
false)
private TrafficJamEntity trafficJamEntity;
}
If I add a disturbance course object to a traffic jam like this:
trafficJamEntity.getDisturbanceCourseEntitySet().add(DisturbanceCourseEntity
.builder()
.latitude(latlong[0])
.longitude(latlong[1])
.orderNumber(shapeId)
.build());
And try to save I get this error:
Fail to write to Database because: A different object with the same identifier value was
already associated with the session
[space.rocit.trafficetl.entities.DisturbanceCourseEntity#DisturbanceCourseEntityPK(trafficJamId=0, orderNumber=1)]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [space.rocit.trafficetl.entities.DisturbanceCourseEntity#DisturbanceCourseEntityPK(trafficJamId=0, orderNumber=1)]
I'm thinking that my definition of the relationship is wrong, any suggestions?
Edit.:
Here is my DisturbanceCourseEntityPK Class:
#Data
public class DisturbanceCourseEntityPK implements Serializable {
private long trafficJamId;
private int orderNumber;
}
Edit 2.:
Yesterday i gave it another try and found out that the generated keys are set correctly when trying to save. The problem is in the setting of foreign keys with the #OnToMany Maping
instead on traffic_jam_id Generated Value notation change like this,
#GeneratedValue(strategy = GenerationType.AUTO)
Check it :)
In case anyone stumbels upon this, this is the way to go.
The TrafficJamEntity needs to look like this:
#Entity
#Table(name = "traffic_jam", schema = "public", catalog = "dwh")
#Setter
#Getter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class TrafficJamEntity {
#Id
#Column(name = "traffic_jam_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "traffic_jam_seq")
#SequenceGenerator(name = "traffic_jam_seq", sequenceName = "traffic_jam_id_sequence", allocationSize = 1)
private long trafficJamId;
#OneToMany(mappedBy = "trafficJamEntity", cascade = CascadeType.PERSIST)
#Builder.Default
private Set<DisturbanceCourseEntity> disturbanceCourseEntitySet = new LinkedHashSet<>();
}
And the child entity Disturbance Course needs to look like this:
#Entity
#Table(name = "disturbance_course", schema = "public", catalog = "dwh")
#IdClass(DisturbanceCourseEntityPK.class)
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class DisturbanceCourseEntity {
#ManyToOne
#Id
#JoinColumn(name = "traffic_jam_id")
private TrafficJamEntity trafficJamEntity;
}
The #Column(name = "traffic_jam_id") Attribute of my question is unnecessary because the #ID Annotation does the same.
Also the binding between the parent-child objects needs to be bidirectional so its not
trafficJamEntity.getDisturbanceCourseEntitySet().add(DisturbanceCourseEntity
.builder()
.latitude(latlong[0])
.longitude(latlong[1])
.orderNumber(shapeId)
.build());
but
DisturbanceCourseEntity disturbanceCourseEntity = DisturbanceCourseEntity
.builder()
.latitude(latitude)
.longitude(longitude)
.orderNumber(shapeId)
.trafficJamEntity(trafficJamEntity)
.build();
trafficJamEntity.getDisturbanceCourseEntitySet().add(disturbanceCourseEntity);
I have Product entity and ProductRating entity, each Product can have many ProductRatings. When Product is deleted I want to have associated ratings deleted too, but nothing works so far (also orphanRemoval set to true)...
Classes:
#Getter
#Setter
#Entity
#Table(name = "PRODUCT")
public class Product extends AbstractEntity<Long> {
#Column(nullable = false)
private String name;
private String description;
#Column(nullable = false)
#Min(value = 0)
private Float cost;
#OneToMany(mappedBy = "product",
orphanRemoval = true, cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
//#OnDelete(action = OnDeleteAction.CASCADE)
#Fetch(value = FetchMode.SELECT)
private Set<ProductRating> productRatings;
}
#Getter
#Setter
#Entity
#Table(name = "PRODUCT_RATING")
public class ProductRating extends Rating {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
#NotNull(message = "Rating must be in context of Product")
private Product product;
}
After Product deletion ratings stay with deleted Product's ID
AbstractEntity implementation:
#Getter
#Setter
#MappedSuperclass
public abstract class AbstractEntity<I> implements Serializable {
private static final long serialVersionUID = 1700166770839683115L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private I id;
}
In the #OneToMany relation you need to add the cascade type delete: cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
Or if you don't mind having all cascade types you can just put: cascade = CascadeType.ALL
EDIT:
Also check the name of the Product primary key in the database.
It should match the defined in the #JoinColumn annotation of ProductRating
The default database field for the attribute id of the Product class would be product_id.
However you have defined the id in AbstractEntity as name = "ID" so the #JoinColumn should be something like: #JoinColumn(name = "ID")
My alternative approach to fix this problem is to:
On parent-side relation create method with #PreRemove annotation
in this method iterate over collection with #[One/Many]ToMany annotation and call delete(obj) method for corresponding repository on child
On child-side relation create method with #PreRemove annotation
In this method set parent to null
I am trying to develop a system for managing dormitories. Since I don't have much experience with databases, I am stuck on a problem and I have a solution but I am not sure if this would be the right approach.
So I have Room and User. Each user can be accommodated in one room, but one room can accommodate more users. I would like to manage this relationship in one entity - Accommodation. Here I would have more properties, like start/end Date, etc.
I am using Hibernate to map the tables. From what I've read, persisting Collections from Java can be done in two ways, either by #OneToMany or by #ElementCollection. I am not quite sure if I should define this relationship in the Room entity or in the Accommodation entity? If I do it in the room entity then the Accommodation would hold just fk from the room/user tables?
Also, is it possible to only fetch the primary key when doing one-to-many relations instead of getting the whole object? I know that FETCH.LAZY does this, but in my Accommodation entity ideally I would want to store only Set studentsIds.
Thank you in advance.
#Table(name = "student")
#AllArgsConstructor
#Data
#Embeddable
#NoArgsConstructor
#javax.persistence.Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "role")
private String role;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "room", nullable = false)
private Room room_number;
}
Here is the Room entity
#javax.persistence.Entity
#Table(name = "room")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Room
{
#Id
#Column(name = "room_number")
private Long roomNumber;
#Column(name = "location_address")
private String locationAddress;
#Column(name = "dormitory_name")
private String dormitoryName;
}
Accommodation entity
#javax.persistence.Entity
#Table(name = "accommodation")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Accommodation extends Entity {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "room_number")
private Room room_number;
#OneToMany(mappedBy = "room_number", // I am not sure about this
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true) private List<Student> students;
#Column(name = "date_from")
private Date dateFrom;
#Column(name = "date_to")
private Date dateTo;
}