JPA : How to deal with one to many association
After reading and experiencing lot of solutions for modeling a one to many association, I'll show you the ways you can REALLY do it:
1. Using bidirectional association
the parent entity :
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table(name = "MEASURING_DEVICE") | |
@NamedEntityGraph( | |
name = "measuringDeviceWithUncertainties", | |
attributeNodes = { | |
@NamedAttributeNode("uncertainties")}) | |
@NamedQuery(name = MeasuringDevice.FIND_DEVICE_BY_ID, | |
query = "SELECT distinct m FROM MeasuringDevice m " | |
+ "LEFT JOIN FETCH m.uncertainties " | |
+ "WHERE m.identification = :identification") | |
@Data | |
@NoArgsConstructor | |
public class MeasuringDevice implements Serializable { | |
public static final String FIND_DEVICE_BY_ID = "MeasuringDevice.findDeviceById"; | |
@Id | |
private String identification; | |
private String designation; | |
@Column(name = "control_date") | |
@Temporal(TemporalType.TIMESTAMP) | |
private Date controlDate; | |
@Column(name = "periodicity_value") | |
private Integer periodicityValue; | |
@Column(name = "periodicity_unit") | |
@Enumerated(EnumType.STRING) | |
private PeriodicityType periodicityUnit; | |
private Boolean active = true; | |
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) | |
private Set<MeasuringUncertainty> uncertainties = new HashSet<>(); | |
public void addUncertainty(MeasuringUncertainty uncertainty) { | |
//remove old if any | |
uncertainties.remove(uncertainty); | |
uncertainties.add(uncertainty); | |
uncertainty.setDevice(this); | |
} | |
} |
As you can see, I use the
@oneToMany
annotation with the following properties :mappedBy
--> parent property of the childcascade
-->ALL
by defaultorphanRemoval
-->true
by defaultfetch
--> we useLAZY
for performance reason
I use a
The child entity below implements also the
In order to retrieve the parent and the children with ONE query, we have 2 solutions:
This solution is far better for me because I can also paginate, filter and sort the results.
Set
for the collection. I provide also a helper method for adding a child to the parent (You can do the same for removing a child).The child entity below implements also the
equals
and hashCode
methods:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table(name = "MEASURING_UNCERTAINTY") | |
@Data | |
@NoArgsConstructor | |
public class MeasuringUncertainty implements Serializable { | |
@Id | |
private String identification; | |
@Enumerated(EnumType.STRING) | |
private Mode mode; | |
@Enumerated(EnumType.STRING) | |
private MeasuringUnit unit; | |
@Column(name = "uncertainty_value") | |
private BigDecimal uncertaintyValue; | |
@Column(name = "uncertainty_unit") | |
@Enumerated(EnumType.STRING) | |
private UncertaintyUnit uncertaintyUnit; | |
@ManyToOne(fetch = FetchType.LAZY) | |
@JoinColumn(name="MEASURING_DEVICE_ID") | |
private MeasuringDevice device; | |
@Override | |
public int hashCode() { | |
return new HashCodeBuilder(17, 37) | |
.append(this.identification) | |
.toHashCode(); | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (obj == null) { | |
return false; | |
} | |
if (obj == this) { | |
return true; | |
} | |
if (getClass() != obj.getClass()) { | |
return false; | |
} | |
MeasuringUncertainty object = (MeasuringUncertainty) obj; | |
return new EqualsBuilder() | |
.append(this.identification, object.identification) | |
.isEquals(); | |
} | |
@Override | |
public String toString() { | |
return new ReflectionToStringBuilder(this).toString(); | |
} | |
} |
In order to retrieve the parent and the children with ONE query, we have 2 solutions:
- @NamedEntityGraph : this annotation tell JPA which node to load automatically.
- @NamedQuery using
LEFT JOIN FETCH
These methods are both used in the below repository:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Repository | |
public interface MeasuringDeviceRepository extends JpaRepository<MeasuringDevice, String> { | |
//using @namedQuery | |
Optional<MeasuringDevice> findDeviceById(@Param("identification") String identification); | |
@EntityGraph("measuringDeviceWithUncertainties") | |
Optional<MeasuringDevice> findByIdentification(String identification); | |
} |
2. @ManyToOne ONLY in the child entity
The only think you need to do is to create a JPQL query for retrieving the child entities associated with the parent.
SELECT m FROM MeasuringUncertainty m WHERE m.device.identification = :identification
This solution is far better for me because I can also paginate, filter and sort the results.
Happy learning!!
Comments
Post a Comment