Spring Security를 JWT 기반으로 적용하면서 AuthUser 객체를 만들었고, 해당 객체를 JwtAuthenticationToken 생성자에 전달하는 과정에서 오류가 발생했다.
초기 AuthUser 클래스:
@Getter
public class AuthUser {
private final Long id;
private final String email;
private final UserRole userRole;
private final String nickname;
public AuthUser(Long id, String email, UserRole userRole, String nickname) {
this.id = id;
this.email = email;
this.userRole = userRole;
this.nickname = nickname;
}
}
JwtAuthenticationToken의 생성자는 다음과 같이 Collection<? extends GrantedAuthority>를 요구했으나:
public JwtAuthenticationToken(AuthUser authUser) {
super(authUser.getAuthorities()); // Collection을 기대
this.authUser = authUser;
setAuthenticated(true);
}
여기서 authUser.getUserRole()을 넘기려 했을 때 타입 불일치 문제가 발생했다.
super()는 Collection<? extends GrantedAuthority> 타입을 요구하는데, AuthUser 안에 UserRole이 단일 값으로 정의되어 있었음.UserRole은 enum 타입으로 GrantedAuthority가 아니며, Collection 타입도 아님.@Getter
public class AuthUser {
private final Long userId;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;
public AuthUser(Long userId, String email, UserRole role) {
this.userId = userId;
this.email = email;
this.authorities = List.of(new SimpleGrantedAuthority(role.name()));
}
}
전에 들었던 발제 강의대로 위의 코드처럼 만들고 싶었으나 아래 fromAuthUser()에서 authUser.getUserRole()d을 가져오지 못하는 문제가 생겼다.
public static User fromAuthUser(AuthUser authUser) {
return new User(authUser.getId(), authUser.getEmail(), authUser.getUserRole(), authUser.getNickname());
}
authUser.getUserRole()이 단일 값이 아니라 Collection<UserRole>이 되어 반환 및 매핑이 어려워짐.private final Collection<? extends GrantedAuthority> authorities;
로 필드를 만들고, 그에 맞춰 코드를 수정하고자 했으나 실패하였다.
AuthUser 클래스에 getAuthorities() 메서드를 추가하여 단일 권한을 Collection으로 변환했고, 결국 Collection<UserRole> 필드 대신 단일 UserRole을 유지하며 해결하였다.
코드를 수정하는 한이 있더라도 Collection으로 해결했어야 하는데, 그러지 못하고 단일 권한을 Collection으로 변환하는 방식으로 해결한 점이 아쉽다.
@Getter
public class AuthUser {
private final Long id;
private final String email;
private final UserRole userRole;
private final String nickname;
public AuthUser(Long id, String email, UserRole userRole, String nickname) {
this.id = id;
this.email = email;
this.userRole = userRole;
this.nickname = nickname;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(userRole.getUserRole()));
}
}
// QueryDSL 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
위와 같이 QueryDSL을 추가할 수 있도록 dependency를 추가 후 빌드를 했는데, Q클래스가 생성이 안됬다
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
로 버전을 명시적으로 수정하고 재빌드 하였으나 여전히 문제가 해결되지 않았다.

Enable annotation processing가 체크가 안되어 있길래 체크했으나 그 이후에도 해결이 안되어서 결국 gradle을 설치하고 ./gradlew clean build 로 해결했다.
기존에 가지고 있는 QClass는 모두 Entity 기반의 QClass였는데, ResponseDto(Todo 엔티티의 title, manager수, comment수)의 필드를 반환받기 위해 해당 dto를 QClass로 만들고 싶었다.
@Getter
public class TodoSearchResponse {
private final String title;
private final long totalManagers;
private final long totalComments;
@QueryProjection
public TodoSearchResponse(String title, long totalManagers, long totalComments) {
this.title = title;
this.totalManagers = totalManagers;
this.totalComments = totalComments;
}
}
@QueryProjection 을 붙이면 ./gradlew compileQuerydsl 실행 시 QTodoSearchResponse 클래스를 생성할 수 있다.new QTodoSearchResponse(...) 형태로 타입 안전하게 DTO를 반환할 수 있다.QueryDSL 내에서 서브쿼리를 작성할 때 사용하는 기능.
.select(new QTodoSearchResponse(
todo.title,
JPAExpressions.select(manager.count())
.from(manager)
.where(manager.todo.eq(todo)),
JPAExpressions.select(comment.count())
.from(comment)
.where(comment.todo.eq(todo))
))
JPAExpressions 를 통해 서브쿼리를 작성하면 groupBy 없이 각 항목별 count 값을 효율적으로 가져올 수 있다.where 절에서 .eq(todo) 처럼 엔티티 연결을 정확히 지정해주어야 한다.and(), or() 를 이용해 다양한 조합을 유연하게 적용 가능.예시
BooleanBuilder builder = new BooleanBuilder();
if (title != null) {
builder.and(todo.title.containsIgnoreCase(title));
}
if (nickname != null) {
builder.and(todo.managers.any().user.nickname.containsIgnoreCase(nickname));
}
예시
.where(
titleContains(title),
managerContains(nickname),
createdAtBetween(start, end)
)
private BooleanExpression titleContains(String title) {
return (title != null && !title.isBlank()) ? todo.title.containsIgnoreCase(title) : null;
}