I have two JPA Entities:
@Entity
@Table(name = "registry_group")
public class RegistryGroupEntity {
@Id
@SequenceGenerator(name="registry_groups_gen", sequenceName="registry_groups_id_seq", allocationSize = 1)
@GeneratedValue(generator="registry_groups_gen")
private Long id;
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "registryGroup")
private Collection<ServiceEntity> services;
}
and
@Entity
@Table(name = "services")
public class ServiceEntity {
@Id
@SequenceGenerator(name="service_gen", sequenceName="services_id_seq", allocationSize = 1)
@GeneratedValue(generator="service_gen")
private Integer id;
@Column(nullable = false, length = 100)
private String name;
}
I'd like to represent the selection as RegistryGroupRow class object:
public class RegistryGroupRow {
private Long id;
private String name;
private String serviceNames;
public RegistryGroupRow(Long id, String name, List<String> serviceNames) {
this.id = id;
this.name = name;
this.serviceNames = serviceNames;
}
}
I have a function like that:
public List<RegistryGroupRow> getRegistryGroupRows(RegistryGroupFilter filter, Integer offset, Integer limit) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<RegistryGroupRow> c = cb.createQuery(RegistryGroupRow.class);
Root<RegistryGroupEntity> registryGroup = c.from(RegistryGroupEntity.class);
c.multiselect(registryGroup.get(RegistryGroupEntity_.id),
registryGroup.get(RegistryGroupEntity_.name),
registryGroup.get(RegistryGroupEntity_.services) //problem is here
);
It wouldn't work, as RegistryGroupEntity_.services is a collection of ServiceEntity, but I need a concatenation on ServiceEntity._name here. How I can call something like cb.concat(registryGroup.get(RegistryGroupEntity_.services).get(ServiceEntity._name)) for the last parameter of the RegistryRowGroup constructor? How to make CriteriaBuilder to run through the collection's objects specific field and collect/concat/sum its values?
For concat I'd like to concat with separator to String. For collect I'd like to collect into List<String>.
Approach
One way to solve this issue is to use a native sql function to do the aggregation of the service names.
E.g. in H2 database, there is a function named
LISTAGGwhich takes 2 parameters, first the field to aggregate and second the delimiter. I am certain whichever database you are using have something similar to aggregate data; e.g. in postgres there isstring_agg(expression, delimiter).Prerequisites
The function from the database needs to be registered and mapped to a name in hibernate. A custom dialect can be created or a
MetadataBuilderContributorcan used to achieve the same. TheMetadataBuilderContributoris neaty so here we are..Update the spring properties file to include the
MetadataBuilderContributorusig the fully qualified name of the class as such;Update the class
RegistryGroupRowto use this constructor;Querying part
Assuming
RegistryGroupEntitylooks as such:And
ServiceEntitylooks as such:The quering will be as follows;
Result Query generated by hibernate:
Result of
@ToStringfor theRegistryGroupRowretrieved.