묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
영속성 컨텍스트 생명주기의 신기한 부분이 있습니다.
1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]// @Transactional public void update(Long id, String name, String city, String street, String zipcode) { Address address = new Address(city, street, zipcode); Member member = memberRepository.findOne(id); member.setName(name); member.setAddress(address); }위와 같이 update 메서드의 @Transactional 을 주석 처리하고 해봤습니다.제 예상이라면 응답값이 새로워진 값이 아니라 기존의 값이 응답값으로 내려와야 할 것 같았습니다. @PutMapping("/v2/members/{id}") public UpdateMemberResponse updateMemberV2( @PathVariable Long id, @RequestBody @Validated UpdateMemberRequest request ) { memberService.update( id, request.name, request.city, request.street, request.zipcode ); Member findMember = memberService.findOne(id); return new UpdateMemberResponse(findMember.getId(), findMember.getName()); }왜냐하면 결론적으로 엔티티 자체는 수정되었지만 DB에 쿼리가 날라가지 않았고위 컨트롤러 코드에서 find 메서드는 엄연히 다른 트랜잭션에서 진행되니(애초에 업데이트는 트랜잭션이 없었지만) 다른 영속성 컨텍스트가 존재할 것이며,1차 캐시또한 비워져있을 테니(애초에 업데이트는 영속성 객체를 가지고 있지 않았기에 1차캐시에 없음) 즉, 컨트롤러에서 find한 Member 객체는 새로 DB에서 가져온 객체 == 기존 객체 일테니응답 값이 기존 값과 같아야할텐데 아래와 같이 달라진 값을 응답으로 내려주고 있었습니다.왜 그런걸까요?가입 응답{ "name": "hello", "city": "city", "street": "street", "zipcode": "12345" }수정 응답{ "name": "hello@", "city": "city@", "street": "street@", "zipcode": "12345@" }
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
마이그레이션 오류입니다.
강의에서 강사님께서 11에서 17로 바꾸라는 모든 설정 확실하게 수정 완료했고 gradle 코드 수정하여 실행하니 A problem occurred configuring root project 'library-app'.> java.util.concurrent.ExecutionException: org.gradle.api.GradleException: Failed to create Jar file C:\Users\seong\.gradle\caches\jars-9\3baa90cc2341a9ffd1656b87b2a3526e\spring-core-6.2.11.jar.아래와 같은 jar 파일 오류가 떴습니다. 구글링과 GPT를 써보니 캐시 파일 버전 충돌 오류라고 하여 캐시 파일을 삭제 하려고 해도 디렉터리가 비어있지 않다는 오류가 뜨며 삭제되지 않고, 또 .\gradlew wrapper 버전이 현재 7.5인데 스프링 부트 3.5.6은 최소 8.x를 사용한다고 하여 이것 또한 업그레이드 하려고 하였으나 PS C:\Project\Study-SpringBoot\library-app> .\gradlew wrapper --gradle-version 8.3 --distribution-type allStarting a Gradle Daemon, 5 stopped Daemons could not be reused, use --status for detailsFAILURE: Build failed with an exception.* What went wrong:A problem occurred configuring root project 'library-app'.> java.util.concurrent.ExecutionException: org.gradle.api.GradleException: Failed to create Jar file C:\Users\seong\.gradle\caches\jars-9\3baa90cc2341a9ffd1656b87b2a3526e\spring-core-6.2.11.jar.동일한 이런 jar 파일 오류가 떴습니다.이 오류를 어떻게 해결해야 할까요 .... 도와주세요 ㅜㅜ
-
미해결스프링 시큐리티 OAuth2
JWT 조회 에 대한 질문
안녕하십니까. 스프링 시큐리티 강의를 듣고 있는 대학생입니다. 현재 강의를 들으면서 팀 프로젝트에 소셜 로그인을 적용하던 중 궁금증이 생겨 글을 작성하게 되었습니다. 현재 사용자가 로그인 후 토큰을 발급받고 MySQL에 리프레시 토큰을 저장한 뒤에, 사용자가 API에 접근할 때마다 매번 사용자 정보를 조회하는 것이 좋은 방법인지 궁금합니다. 현재 매번 조회하는 이유는 토큰의 사용자 정보가 서버 데이터베이스에 올바르게 저장된 사용자인지 검증하기 위해서 이 방식을 채택했습니다 package backend_lingua.linguas.infrastructure.security.filter; import backend_lingua.linguas.infrastructure.security.token.enumerated.TokenType; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Slf4j @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getBearerToken(request); // 토큰이 있는 경우에만 검증 if (StringUtils.hasText(token)) { try { if (jwtTokenProvider.validateAccessToken(token)) { Authentication authentication = jwtTokenProvider.getAuthentication(token, TokenType.ACCESS_TOKEN); SecurityContextHolder.getContext().setAuthentication(authentication); log.debug("Security Context에 '{}' 인증 정보를 저장했습니다.", authentication.getName()); } } catch (ExpiredJwtException e) { log.error("토큰 인증 실패: {}", e.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed"); return; } } filterChain.doFilter(request, response); } private String getBearerToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } public TokenInfo generateToken(Authentication authentication) { String accessToken = createAccessToken(authentication); String refreshToken = createRefreshToken(authentication); Date accessTokenExpiryDate = createExpiryDate(accessTokenExpiration); return TokenInfo.from(accessToken, refreshToken, accessTokenExpiryDate.getTime(), refreshTokenExpiration); } public String createAccessToken(Authentication authentication) { return createToken(authentication, accessTokenExpiration, accessTokenSecret); } public String createRefreshToken(Authentication authentication) { String token = createToken(authentication, refreshTokenExpiration, refreshTokenSecret); Date expiryDate = createExpiryDate(refreshTokenExpiration); // DB에 리프레시 토큰 저장 tokenService.createRefreshToken(token, expiryDate, authentication); return token; } private String createToken(Authentication authentication, long expirationTime, String secretKey) { String authorities = authentication.getAuthorities() .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); return Jwts.builder() .setSubject(authentication.getName()) // email .claim("auth", authorities) .setIssuedAt(new Date()) .setExpiration(createExpiryDate(expirationTime)) .signWith(createKey(secretKey)) .compact(); } public String getUsernameFromToken(String token, TokenType tokenType) { String secretKey = (tokenType == TokenType.ACCESS_TOKEN) ? accessTokenSecret : refreshTokenSecret; return Jwts.parserBuilder() .setSigningKey(createKey(secretKey)) .build() .parseClaimsJws(token) .getBody() .getSubject(); } public Authentication getAuthentication(String token, TokenType tokenType) { String username = getUsernameFromToken(token, tokenType); UserPrincipal userPrincipal = (UserPrincipal) userDetailsService.loadUserByUsername(username); return new UsernamePasswordAuthenticationToken( userPrincipal, token, userPrincipal.getAuthorities() ); } public boolean validateAccessToken(String token) { return validateToken(token, accessTokenSecret); } public boolean validateRefreshToken(String token) { return validateToken(token, refreshTokenSecret); } private boolean validateToken(String token, String secretKey) { try { Jwts.parserBuilder() .setSigningKey(createKey(secretKey)) .build() .parseClaimsJws(token); return true; } catch (ExpiredJwtException e) { log.error("만료된 JWT 토큰입니다."); throw e; // Filter에서 401 처리하도록 } catch (SecurityException | MalformedJwtException e) { log.error("잘못된 JWT 서명입니다."); } catch (UnsupportedJwtException e) { log.error("지원되지 않는 JWT 토큰입니다."); } catch (IllegalArgumentException e) { log.error("JWT 토큰이 잘못되었습니다."); } return false; } private Date createExpiryDate(long expirationTime) { return new Date(System.currentTimeMillis() + expirationTime); } private SecretKey createKey(String secret) { byte[] keyBytes = Decoders.BASE64.decode(secret); return Keys.hmacShaKeyFor(keyBytes); } @Service @RequiredArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private final MemberRepository memberRepository; @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + email)); return UserPrincipal.create(member); } }public class TokenServiceImpl implements TokenService { private final RefreshTokenRepository refreshTokenRepository; private final MemberRepository memberRepository; @Override @Transactional public RefreshToken createRefreshToken(String token, Date expiryDate, Authentication authentication) { String email = authentication.getName(); Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다: " + email)); // 새 토큰 생성 RefreshToken refreshToken = RefreshToken.builder() .token(token) .member(member) .expiryDate(convertToLocalDateTime(expiryDate)) .build(); return refreshTokenRepository.save(refreshToken); } @Override @Transactional public void deleteRefreshToken(String email) { Member member = memberRepository.findByEmail(email) .orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다: " + email)); deleteRefreshTokenByUser(member); } @Override @Transactional public void deleteRefreshTokenByUser(Member member) { refreshTokenRepository.findByMemberId(member.getId()) .ifPresent(refreshTokenRepository::delete); } private LocalDateTime convertToLocalDateTime(Date date) { return Instant.ofEpochMilli(date.getTime()) .atZone(ZoneId.systemDefault()) .toLocalDateTime(); } }
-
해결됨6주 완성! 백엔드 이력서 차별화 전략 4가지 - 똑같은 이력서 속에서 돋보이는 법
이력서 첨삭
안녕하세요 딩코님 제가 완강을 하여 이력서를 작성해봤는데보완·추가 할만한 부분을 알려주실수 있을까요https://earthy-chalk-721.notion.site/Lee-Jeongwon-1aa176858bf380cca9cce9625a5ad1e4?pvs=73강의중 살짝 봤는데 포공 졸업하셨나요,,, 역시 너무 잘배웠습니다
-
해결됨스프링 핵심 원리 - 기본편
appConfig.xml 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.라이브 코딩 하면서 따라가고 있는데 appConfig.xml 작성 도중 오류가 발생했습니다...
-
해결됨백엔드 6주 실전 미션과 1:1 피드백으로 완성하는 합격 포트폴리오
오타 발견 제보
3주차 수업 자료(notion) category 안에 Training 이 없습니다. 글 내용 흐름에 따라 Training 대신 Fiction 으로 수정하면 될 것 같습니다 🙂 아래 GROUP BY 를 통해서도 없다는 것을 다시 한 번 확인할 수 있습니다. GROUP BY 조회 결과
-
미해결스프링 핵심 원리 - 기본편
7:46에 테스트 부분
질문이라기엔 뭐 하지만, 현재 MemberService를 동일 이름으로 2개 이상 가지고 있지 않으니MemberService memberService = ac.getBean("memberService", MemberService.class);해당 부분을MemberService memberService = ac.getBean(MemberService.class);이렇게 가져가도 괜찮을까요?테스트에는 성공하는데 혹시 따로 "memberService" 오버로딩 메소드를 사용하신 이유가 있으신가 해서요!
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
단건 주문만 가능하게 한건 의도한 부분이신가요?
1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]단순 궁금증입니다.도메인 설계에서 여러 OrderItem을 만들 수 있도록 설계했으면서 정작 Service에선 한 개의 ItemId, count를 받게 하신건 의도하신건가요? 의도하신거였네요.뒷 내용을 확인하지 않고 질문을 달았었네요..
-
미해결스프링 시큐리티 OAuth2
password grant 방식 에러 응답
강의보고 실습하고 있는데요, password grant 타입은 아래와 같이 에러가 발생합니다. 유저 consent도 등록이 되어있는데, 진행이 안됩니다.
-
미해결스프링 핵심 원리 - 고급편
AspectV3 aop 실행 순서 문의 드립니다
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]여기에 질문 내용을 남겨주세요. aspect3 로 test 를 돌렸을 때 자꾸 트랜잭션 aop 가 먼저 실행 되길래 무엇이 잘못인지 보다가 doTransaction 이걸 -> dioTransaction 로 작성을 했길래 수정을 하고 실행 했더니 log 가 먼저 나오는걸 확인할수 있었습니다. 메서드 이름으로 실행 순서가 변경 되는 매커니즘도 있나요 ?
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
dto 필드 속 엔티티 여부
1. 강의 내용과 관련된 질문인가요?예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요, dto에 대해서 질문드리고 싶어 남깁니다. order 테이블에는 연관관계 필드가 있는데 orderDto에서는 연관관계 필드를 제외한 데이터들이 필드로 들어가있습니다.보통 Dto를 작성할 때는 엔티티 연관관계 필드들은 양방향 편의 메서드같은것으로 수정하고 dto에서는 int, String같은 기본 필드들만 받는것이 일반적일까요 ? 스프링부트 1 버전에서 가급적 단방향 연관관계로 설계하라 하셨는데, 단방향으로 설계하게 되면 연관관계 편의 메서드를 작성하지 않을텐데 이때는 dto필드에 엔티티까지 작성하는지 궁금합니다.좋은 강의 제공해주셔서 감사드립니다 !
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
MVC 패턴의 적용 단위
[질문]MVC 패턴을 어느 단위로 쪼개서 적용을 해야하는지 궁금합니다.URL이 나뉜다면 적용각 기능마다 적용 예를 든다면 회원 관련 로직으로 저장,조회,삭제 등이 있는데 각각의 기능마다 MVC를 적용해서저장 버튼 클릭 -> 저장 로직 실행 후 모델에 정보를 담아서 뷰로 전달조회 버튼 클릭 -> 조회 로직 실행 후 모델에 정보를 담아서 뷰로 전달인 것인지 아니라면저장,조회,삭제를 같은 자바 클래스에서 로직 수행, 모델에 담은 후 각각 뷰로 전달인지 궁금합니다!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
이너 클래스로 구현하는 이유
킬구형 안녕!지금 실무에서 Spring Batch를 사용해서 배치 기능 구현하고 있는데 궁금한 점이 있어.강의의 예제 코드들을 보다보면 Job에 필요한 항목들을 따로 클래스 파일로 빼서 구현하지 않고 JobConfig 클래스 내에서 이너 클래스로 구현하던데 특별한 이유가 있을까!?@Slf4j public static class BrainwashProcessor implements ItemProcessor<InFearLearnStudents, BrainwashedVictim> {강의가 너무 재밌어서 점심시간에도 공부 중이야 ㅎㅎ
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
멀티모듈
안녕하세요 토비님배운 내용을 바탕으로 기존 프로젝트 구조를 변경하는 연습하고 있습니다.기존 프로젝트가 멀티 모듈로 되어있어 멀티모듈 구조는 그대로 가져가고 싶은데 분리한다면어떻게 나누어야 할까요?모듈을 두개로 나누어 api와 core로 구성했는데 adapter, application, domain를api 모듈에 adapter core 모듈에 application, domain 이렇게 구성하였는데 이렇게 구분해도 괜찮은지 잘 모르겠습니다
-
미해결스프링 핵심 원리 - 기본편
생성자 주입 관련하여 기초적인 질문 드리고합니다.
부모 클래스 타입에, 자식클래스의 생성자를 주입하는 상황에대해 궁금한게 생겨 질문드립니다. 부모 클래스 타입으로 선언된 인스턴스는자식 클래스에게만 있는 메서드를 사용하지 못하는거로 알고있는데요, 그렇다면 만약에 여러 인터페이스를 implement 받은 클래스라면, 이떄는 생성자 주입방식을 쓰면 안되는게 아닌가 하는 의문이 들어 질문드립니다. 예를 들면 아래와 같이 구현된 A에 대해서public class A implements D, E { ... 코드 생략 } 아래 처럼 생성자 주입을 한다면, 이 a 인스턴스는 E 인터페이스에 정의된 메서드들은 사용하지 못하는게 아닌가해서 여쭤보고자합니다.( 실제 클래스들을 보면 다중상속을 받은 경우에도 생성자 주입방식을 잘 쓰고 있는것 같아 이부분이 궁금합니다.. ).. 코드 생략 D a = new A(); .. 코드생략
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
DTO를 서비스 레이어에서 사용할 수 밖에 없다면
엔티티를 쓰지 못하는 상황JDBC Template나 nativeQuery처럼 직접 조회가 필요한 경우에는 엔티티를 사용하기 힘들 것 같은데이런 경우에 DTO를 사용하게 되면 서비스 레이어에 해당 부분이 생길거 같은데 이 정도는 괜찮은 걸까요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
Whitelabel Error Page 오류가 났습니다.
h2-console 웹에 접속하려고 하니 Whitelabel Error Page 오류가 뜹니다. 그래서 구글링하니 Spring Security에서 h2 console을 허용하라는데 어떻게 하는지 잘 모르겠습니다 ㅜㅜ.. 도와주세요 ㅜㅜ
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
RequestMapping을 이용한 핸들러, 어댑터
앞서 예제로 했던 FrontController의 코드에서는 handlerMapping에서 url에 해당하는 구현체가 핸들러여서 구현체가 곧 핸들러로 이해하고 있었습니다.ai한테 물어보니까 RequestMapping에서의 핸들러는 메소드가 핸들러라 설명하더라구요. 맞는지 궁금합니다.그럼 RequestMapping을 이용한 핸들러의 알맞는 어댑터는 무조건 RequestMappingHandlerAdapter가 사용되는건가요?
-
해결됨가장 쉽고 깊게 알려주는 Kafka 완벽 가이드 [ By. 비전공자 & Kakao 개발자 ]
자바 17로 하셨던데 21로 해도 되나요?
자바 17로 하셨던데 21로 해도 되나요?
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
save 후 결과화면
mvc를 이용해 save를 하고 저장 완료 결과화면을 보여주잖아요?근데 그 결과화면에서 새로고침을 하면 다시 Post가 일어나면서 중복 등록이 일어나게 되는데MvcMemberSaveServlet에서 jsp를 쓴다면 redirect를 할 수 없는건가요?