Home > database >  Create Map object from GROUP BY in JPA Query
Create Map object from GROUP BY in JPA Query

Time:10-21

I have 3 tables for simplicity:

  • COMPANY
  • EMPLOYEE
  • DEVICES

COMPANY has EMPLOYEES and each EMPLOYEE has a number of devices assigned to them. I am interested in a list of companyIds connected to a list of device names they have assigned to employees.

Example Java object:

@Getter
@Setter
@AllArgsConstructor
public class CompanyDevices {
  private Long companyId;
  private List<String> deviceNames;
}

Or even better a Map would be great: Map<Long, List where Long would be companyId and List would be deviceNames;

Is it even possible to create JPA Query, join all those tables and return list of custom objects or a map using @Query inside JPARepository?

What I have right now is slightly different class:

@Getter
@Setter
@AllArgsConstructor
public class CompanyDevice {
  private Long companyId;
  private String deviceName;
}
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {
    
    @Query("SELECT "  
            "new path.to.CompanyDevice(C.id, D.deviceName) "  
            "FROM Company C "  
            "JOIN C.employees E "  
            "JOIN E.devices D "  
            "WHERE C.id IN :companyIds"
    )
    List<CompanyDevice> findDevicesForCompanies(@Param("companyIds") List<Long> companyIds);

}

Because of this I need to manualy later group by and create a map. So what I would like to do is something like this but I just can't find enough information or it may not even be possible(sorry for pseudo code):

public interface SomeRepository extends JpaRepository<SomeEntity, Long> {
    
    @Query("SELECT "  
            "<create map where key is C.id and value is <list of D.deviceName from grouping>>"  
            "FROM Company C "  
            "JOIN C.employees E "  
            "JOIN E.devices D "  
            "WHERE C.id IN :companyIds"  
            "GROUP BY C.id"
    )
    Map<Long, List<String>> findDevicesForCompanies(@Param("companyIds") List<Long> companyIds);

}

CodePudding user response:

What you need is an intermediate function like this:

public interface SomeRepository extends JpaRepository<SomeEntity, Long> {
    
    @Query("SELECT "  
            "c.id, d.deviceName "  
            "FROM Company c "  
            "JOIN c.employees e "  
            "JOIN e.devices d "  
            "WHERE c.id IN :companyIds"
    )
    List<Object[]> findDevicesForCompanies0(@Param("companyIds") List<Long> companyIds);

    default Map<Long, List<String>> findDevicesForCompanies(List<Long> companyIds) {
        return findDevicesForCompanies(companyIds).stream()
            .collect(
                Collectors.groupingBy(
                    o -> (Long) o[0],
                    Collectors.mapping( o -> (String) o[1], Collectors.toList() )
                )
            );
    }

}

Another nice way to solve this would be to use Blaze-Persistence Entity Views which I think this is a perfect use case for.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(Company.class)
public interface CompanyDevices {
    @IdMapping
    Long getId();
    @Mapping("employees.devices.deviceName")
    Set<String> getDeviceNames();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

CompanyDevices a = entityViewManager.find(entityManager, CompanyDevices.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<CompanyDevices> findAll(Pageable pageable);

Or in your particular case

List<CompanyDevices> findByIdIn(List<Long> companyIds);

The best part is, it will only fetch the state that is actually necessary!

  • Related