Я хочу создать запрос JPQL для сопоставления данных этой структуры с этим DTO:
@AllArgsConstructor
class UserDTO {
long userId;
long countOfContacts;
Map<String,Long> countOfActions; // by type
}
Я не знаю, как чтобы извлечь счетчики для каждого типа действия в JPQL, вот где я застрял (видите, меня зовут? :)):
public interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT new example.UserDTO( "
+ " u.id, "
+ " COUNT(contacts), "
--> + " ???group by actions.type to map<type,count>??? " <---
+ " ) "
+ " FROM User u "
+ " LEFT JOIN u.actions actions "
+ " LEFT JOIN u.contacts contacts "
+ " GROUP BY u.id")
List<UserDTO> getAll();
}
Я использую postgres, и он также подойдет для нативного запроса если это невозможно в JPQL.
На самом деле, я могу решить это с помощью собственного запроса и сопоставить данные действий в Java, но это плохо:
SELECT
u.id,
COALESCE(MIN(countOfContacts.count), 0) as countOfContacts,
ARRAY_TO_STRING(ARRAY_REMOVE(ARRAY_AGG(actions.type || ':' || actions.count), null),',') AS countOfActions
FROM user u
LEFT JOIN (
SELECT
user_id, COUNT(*) as count
FROM contact
GROUP BY user_id
) countOfContacts
ON countOfContacts.user_id = u.id
LEFT JOIN (
SELECT
user_id, type, COUNT(*)
FROM action
GROUP BY user_id, type
) actions
ON actions.user_id = u.id
GROUP BY u.id
;
В результате такие результаты:
id | countOfContacts | countOfActions
--------+-----------------+-------------------------
11728 | 0 | {RESTART:2}
9550 | 0 | {}
9520 | 0 | {CLEAR:1}
12513 | 0 | {RESTART:2}
10238 | 3 | {CLEAR:2,RESTART:5}
16531 | 0 | {CLEAR:1,RESTART:7}
9542 | 0 | {}
...
Поскольку в собственном запросе мы не можем отобразить в POJO, я возвращаю List<String[]>
и преобразую все столбцы самостоятельно в конструктор UserDTO
:
@Query(/*...*/)
/** use getAllAsDTO for a typed result set */
List<String[]> getAll();
default List<UserDTO> getAllAsDTO() {
List<String[]> result = getAll();
List<UserDTO> transformed = new ArrayList<>(result.size());
for (String[] row : result) {
long userId = Long.parseLong(row[0]);
long countOfContacts = Long.parseLong(row[1]);
String countOfActions = row[2];
transformed.add(
new UserDTO(userId, countOfContacts, countOfActions)
);
}
return transformed;
}
Затем я сопоставляю countOfActions
с Java Map<String, Long>
в конструкторе DTO:
class UserDTO {
long userId;
long countOfContacts;
Map<String,Long> countOfActions; // by type
/**
* @param user
* @param countOfContacts
* @param countOfActions {A:1,B:4,C:2,..} will not include keys for 0
*/
public UserDTO(long userId, long countOfContacts, String countOfActionsStr) {
this.userId = userId;
this.countOfContacts = countOfContacts;
this.countOfActions = new HashMap<>();
// remove curly braces
String s = countOfActionsStr.replaceAll("^\\{|\\}$", "");
if (s.length() > 0) { // exclude empty "arrays"
for (String item : s.split(",")) {
String[] tmp = item.split(":");
String action = tmp[0];
long count = Long.parseLong(tmp[1]);
countOfActions.put(action, count);
}
}
}
}
Могу ли я решить его уже на слое БД?