5.1 회원 가입
Ex) 보안 관련 메서드, 클래스 추가
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
- 권한 정보를 만든다.
<hide/>
package com.dayone.model.constants;
public enum Authority {
ROLE_READ,
ROLE_WRITE
}
- security
- 사용자가 여러 권한을 가질 수 있으므로 List<String> 타입으로 roles를 만든다.
- 스프링 시큐리티를 사용하기 위해 UserDetails를 구현한다.
<hide/>
package com.dayone.model;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "MEMBER")
public class MemberEntity implements UserDetails {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private List<String> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(SimpleGrantedAuthority::new) // 스프링 시큐리티를 쓰기 위해 사용
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
========================= 매핑 정보 추가해야 리스트에 오류 나지 않을 것 같다 =====================
- Repo
- 회원 가입 시 이미 존재하는 아이디인지 확인하는 메서드
<hide/>
package com.dayone.persist;
import com.dayone.model.MemberEntity;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
Optional<MemberEntity> findByUsername(String username);
boolean existsByUsername(String username);
}
- Auth - 검증 위한 클래스
- 비밀번호는 암호화해서 넣어야한다. indoding된 패스워드
<hide/>
package com.dayone.model;
import java.util.List;
import lombok.Data;
public class Auth {
@Data
public static class SignIn{
private String username;
private String password;
}
// signUp 클래스의 내용을 엔티티로 만든다.
@Data
public static class SignUp{
private String username;
private String password;
private List<String> roles;
public MemberEntity toEntity(){
return MemberEntity.builder()
.username(this.username)
.password(this.password)
.roles(this.roles)
.build();
}
}
}
- AppConfig에 password incoder에 대한 실체 구현체를 정의한다.
- 민감 정보이므로 인코딩한다.
<hide/>
package com.dayone.config;
import org.apache.commons.collections4.Trie;
import org.apache.commons.collections4.trie.PatriciaTrie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class AppConfig {
@Bean
public Trie<String, String> trie(){
return new PatriciaTrie<>();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 멤버 서비스
<hide/>
package com.dayone.service;
import com.dayone.model.Auth;
import com.dayone.model.MemberEntity;
import com.dayone.persist.MemberRepository;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@AllArgsConstructor
public class MemberService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.memberRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자 " + username + "을(를) 찾을 수 없습니다."));
}
// 회원 가입
public MemberEntity register(Auth.SignUp member){
boolean exists = this.memberRepository.existsByUsername(member.getUsername());
if(exists){
throw new RuntimeException("이미 사용중인 아이디입니다.");
}
member.setPassword(this.passwordEncoder.encode(member.getPassword()));
var result = this.memberRepository.save(member.toEntity());
return result;
}
// 로그인 검증 메서드
public MemberEntity authenticate(Auth.SignIn signIn){
return null;
}
}
5.2 JWT (JSon Web Token)
JSon Web Token
- 사용자 인증 및 식별에 사용되는 토큰(token)을 말한다.
- 사용자 정보를 포함하다.
- Java, JavaScript, C++, Python 다양한 언어로 지원한다.
- JWT가 아닌 세션 처리로 사용자 인증을 처리할 수 있으나 JWT가 더 일반적이고 장점이 많다.
- JWT는 한번 만들어지면 상태나 토큰에 대한 상세 정보를 서버에서 관리하지 않는다.
- 따라서, 만료 시간이 꼭 필요하다. 만약 토큰이 유출되는 경우, 서버에서 삭제 불가능
JSon Web Token 구조
- Header - 토큰의 타입이 들어간다. 어떤 암호 알고리즘이 적용됐는지 들어간다.
- Payload - 사용자나 토큰에 대한 속성 정보 (토큰만 있으면 누구나 Decoding 가능하므로 민감 정보는 포함 X)
- Signature - 서명은 토큰이 유효한지, 위조됐는지 여부를 확인한다. 서명을 위해 비밀 key가 필요하다.
Ex)
- yml 파일
- jwt: secret은 문자를 인코딩한 값을 사용한다.
- 터미널로 생성한다.
<hide/>
spring:
application:
name: dayone-financial
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create
use-new-id-generator-mappings: false
show-sql: true
defer-datasource-initialization: true
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:dayone;DB_CLOSE_DELAY=-1
username: sa
password:
redis:
host: localhost
port: 6379
jwt:
secret: "터미널로 생성"
scheduler:
scrap:
yahoo: "0 0 0 * * *"
- TokenProvider
- 토큰 유효 시간을 설정한다. 생성한 지 1시간 내에 유효하도록 설정한다.
- parseClaims() 메서드: 시간이 경과했는데 반환하려는 경우에 대해 오류처리
<hide/>
@Component
@RequiredArgsConstructor
public class TokenProvider {
@Value("${spring.jwt.secret}")
private String secretKey;
private static final String KEY_ROLES = "roles";
private static final long TOKEN_EXPIRE_TIME = 1000 * 60 * 60; // 1 시간
// 토큰 생성
public String generateToken(String username, List<String> roles){
Claims claims = Jwts.claims().setSubject(username);
claims.put(KEY_ROLES, roles);
var now = new Date(); // 생성된 시간
var expiredDate = new Date(now.getTime() + TOKEN_EXPIRE_TIME);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiredDate)
.signWith(SignatureAlgorithm.HS512, this.secretKey) // 사용할 알고리즘과 비밀키
.compact();
}
public String getUsername(String token){
return this.parseClaims(token).getSubject();
}
public boolean validateToken(String token){
if(!StringUtils.hasText(token)){
return false;
}
var claims = this.parseClaims(token);
return !claims.getExpiration().before(new Date()); // 토큰의 만료 시간을 현재 시간과 비교해서 만료 여부 체크
}
private Claims parseClaims(String token){
try{
return Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
}catch (ExpiredJwtException e){
return e.getClaims();
}
}
}
5.3 컨트롤러 구현
- MemberService
- 로그인 검증 메서드
<hide/>
public MemberEntity authenticate(Auth.SignIn member){
// user의 비번은 인코딩된 상태이다.member의 비번은 암호화되지 않은 상태
var user = this.memberRepository.findByUsername(member.getUsername())
.orElseThrow(() -> new RuntimeException("존재하지 않는 ID입니다."));
// 비번 확인
if(!this.passwordEncoder.matches(member.getPassword(), user.getPassword())){
throw new RuntimeException("비밀 번호가 일치하지 않습니다.");
}
return user;
}
- AuthController
<hide/>
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AutoController {
private final MemberService memberService;
private final TokenProvider tokenProvider;
// 회원가입용 API
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody Auth.SignUp request){
var result = this.memberService.register(request);
return ResponseEntity.ok(result);
}
// 로그인용 API
@PostMapping("/signin")
public ResponseEntity<?> signin(@RequestBody Auth.SignIn request){
var member = this.memberService.authenticate(request);
var token = this.tokenProvider.generateToken(member.getUsername(), member.getRoles());
return ResponseEntity.ok(token);
}
}
5.4 인증 구현
- JwtAuthenticationFilter 클래스
- OncePerRequestFilter를 상속한다.
- bearer 뒤에 한 칸 띄우고 토큰이 붙는다.
- 요청이 들어올 때마다 필터가 컨트롤러보다 먼저 실행되면서 요청의 헤더에 토큰이 있는지 유효한지 확인한다.
- 유효하면 인증 정보를 컨텍스트에 담는다. 유효하지 않으면 바로 실행된다.
=========auth 에러 ==========
- Authentication를 import 하는 주소를 명시해주니까 오류가 해결
<hide/>
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter { // 요청 하나당 필터 한 번 실행
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer "; // 인증 타입을 나타낸다. jwt는 토큰 앞에 Bearer을 붙인다.
public final TokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = this.resolveTokenFromRequest(request);
if(StringUtils.hasText(token) && this.tokenProvider.validateToken(token)){
// 토큰 유효성 검증
org.springframework.security.core.Authentication auth = this.tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
// request 의 내부에서 헤더를 꺼내온다.
private String resolveTokenFromRequest(HttpServletRequest request){
String token = request.getHeader(TOKEN_HEADER);
if(!ObjectUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)){ // 토큰 존재하는 경우
return token.substring(TOKEN_PREFIX.length()); // 앞에 고정 문자열 뒤의 실제 토큰
}
return null;
}
}
- TokenProvider
<hide/>
package com.dayone.security;
import com.dayone.service.MemberService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.hibernate.cfg.NotYetImplementedException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
@Component
@RequiredArgsConstructor
public class TokenProvider {
private static final long TOKEN_EXPIRE_TIME = 1000 * 60 * 60; // 1 시간
private static final String KEY_ROLES = "roles";
private final MemberService memberService;
@Value("${spring.jwt.secret}")
private String secretKey;
// 토큰 생성
public String generateToken(String username, List<String> roles){
Claims claims = Jwts.claims().setSubject(username);
claims.put(KEY_ROLES, roles);
var now = new Date(); // 생성된 시간
var expiredDate = new Date(now.getTime() + TOKEN_EXPIRE_TIME);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiredDate)
.signWith(SignatureAlgorithm.HS512, this.secretKey) // 사용할 알고리즘과 비밀키
.compact();
}
// jwt 로부터 인증 정보를 넣어주는 메서드
public Authentication getAuthentication(String jwt){
UserDetails userDetails = this.memberService.loadUserByUsername(this.getUsername(jwt));
// 사용자 정보, 권한 정보를 포함한다.
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(String token){
return this.parseClaims(token).getSubject();
}
public boolean validateToken(String token){
if(!StringUtils.hasText(token)){
return false;
}
var claims = this.parseClaims(token);
return !claims.getExpiration().before(new Date()); // 토큰의 만료 시간을 현재 시간과 비교해서 만료 여부 체크
}
private Claims parseClaims(String token){
try{
return Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
}catch (ExpiredJwtException e){
return e.getClaims();
}
}
}
- SecurityConfig
- WebSecurityConfigurerAdapter를 상속한다.
- configure(WebSecurity)
- antMatchers(): 괄호 안에 있는 "h2-console"로 시작하는 페이지에 대해서는 인증을 무시하겠다는 의미이다.
- configure(HttpSecurity) 에 넣은 signin, signup을 ignoring()에 넣어도 상관없다.
- 주로 개발 관련은 => Web 에 넣고 / API관련 정보는 => Http에 넣는다.
- configure(HttpSecurity)
- 사용하지 않을 부분은 disable 처리한다.
- 세션도 stateless로 처리한다.
- jwt는 상태 정보를 저장하지 않는 특징이 있다
- 회원 가입과 로그인 창은 인증 없이 접근 가능하도록 한다.
<hide/>
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationFilter authenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/**/signin", "/**/signup").permitAll()
.and()
.addFilterBefore(this.authenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/h2-console/**"); // h2-console 로 시작하는 어떤 경로든 포함한다.
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
- 컨트롤러
- READ 권한 있어야만 접근 가능
- WRITE 권한 있는 user만 접근 가능
Note) 실행 결과
====================== 오류 ======================
<hide/>
2022-09-20 19:20:16.733 ERROR 13040 --- [ main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
2022-09-20 19:20:16.736 ERROR 13040 --- [ main] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'jwtAuthenticationFilter' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\security\JwtAuthenticationFilter.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tokenProvider' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\security\TokenProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberService' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\service\MemberService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'memberRepository' defined in com.dayone.persist.MemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot create inner bean '(inner bean)#5c18d6d4' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
2022-09-20 19:20:16.759 INFO 13040 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2022-09-20 19:20:16.763 WARN 13040 --- [ main] o.a.c.loader.WebappClassLoaderBase : The web application [ROOT] appears to have started a thread named [HikariPool-1 housekeeper] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.base@11.0.16.1/jdk.internal.misc.Unsafe.park(Native Method)
java.base@11.0.16.1/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:234)
java.base@11.0.16.1/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2123)
java.base@11.0.16.1/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)
java.base@11.0.16.1/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1054)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1114)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
java.base@11.0.16.1/java.lang.Thread.run(Thread.java:834)
2022-09-20 19:20:16.763 WARN 13040 --- [ main] o.a.c.loader.WebappClassLoaderBase : The web application [ROOT] appears to have started a thread named [HikariPool-1 connection adder] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.base@11.0.16.1/jdk.internal.misc.Unsafe.park(Native Method)
java.base@11.0.16.1/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:234)
java.base@11.0.16.1/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2123)
java.base@11.0.16.1/java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:458)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1053)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1114)
java.base@11.0.16.1/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
java.base@11.0.16.1/java.lang.Thread.run(Thread.java:834)
2022-09-20 19:20:16.767 WARN 13040 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
2022-09-20 19:20:16.768 INFO 13040 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-09-20 19:20:16.772 INFO 13040 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2022-09-20 19:20:16.785 INFO 13040 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-09-20 19:20:16.825 ERROR 13040 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:163) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:577) ~[spring-context-5.3.12.jar:5.3.12]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.6.jar:2.5.6]
at com.dayone.Application.main(Application.java:18) ~[main/:na]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:142) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:104) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:450) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:199) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:182) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:160) ~[spring-boot-2.5.6.jar:2.5.6]
... 8 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtAuthenticationFilter' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\security\JwtAuthenticationFilter.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tokenProvider' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\security\TokenProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberService' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\service\MemberService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'memberRepository' defined in com.dayone.persist.MemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot create inner bean '(inner bean)#5c18d6d4' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:212) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:175) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAsRegistrationBean(ServletContextInitializerBeans.java:170) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addAdaptableBeans(ServletContextInitializerBeans.java:155) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:87) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:260) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:234) ~[spring-boot-2.5.6.jar:2.5.6]
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:53) ~[spring-boot-2.5.6.jar:2.5.6]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5219) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na]
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:263) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:432) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:927) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:486) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ~[spring-boot-2.5.6.jar:2.5.6]
... 13 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tokenProvider' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\security\TokenProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberService' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\service\MemberService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'memberRepository' defined in com.dayone.persist.MemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot create inner bean '(inner bean)#5c18d6d4' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.12.jar:5.3.12]
... 56 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberService' defined in file [C:\Users\Ran\Desktop\R\zerobase\Spring\sample\build\classes\java\main\com\dayone\service\MemberService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'memberRepository' defined in com.dayone.persist.MemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot create inner bean '(inner bean)#5c18d6d4' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.12.jar:5.3.12]
... 70 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'memberRepository' defined in com.dayone.persist.MemberRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot create inner bean '(inner bean)#5c18d6d4' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:389) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:134) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.12.jar:5.3.12]
... 84 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#5c18d6d4': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:693) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:510) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:374) ~[spring-beans-5.3.12.jar:5.3.12]
... 98 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330) ~[spring-beans-5.3.12.jar:5.3.12]
... 106 common frames omitted
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:421) ~[spring-orm-5.3.12.jar:5.3.12]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-5.3.12.jar:5.3.12]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.12.jar:5.3.12]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.12.jar:5.3.12]
... 113 common frames omitted
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.List, at table: member, for columns: [org.hibernate.mapping.Column(roles)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:499) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:466) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.mapping.Property.isValid(Property.java:227) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:624) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.mapping.RootClass.validate(RootClass.java:267) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:354) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:298) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:468) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1259) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.3.12.jar:5.3.12]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.3.12.jar:5.3.12]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-5.3.12.jar:5.3.12]
... 117 common frames omitted
- 오류
- UnsatisfiedDependencyException
- BeanCreationException
- PersistenceException
- MappingException
- 원인
- 처음에는 멤버 엔티티의 테이블 명을 대문자로 적은게 문제가 된 줄 알았다.
- 그런데 알고 보니까 멤버 엔티티의 List<String> roles 에 대해 어노테이션을 붙이지 않아서 에러가 여러 개 발생한 것이다.
- @ElementCollection()을 붙여줘야한다.
- ElementCollection: 컬렉션 객체임을 알려주는 애너테이션이다.
- RDB에는 컬렉션과 같은 형태의 데이터를 컬럼에 저장할 수 없으므로 별도의 테이블을 생성해서 컬렉션을 관리해야한다. 이 때, @ElementCollection를 이용해서 JPA에게 roles가 컬렉션 객체임을 알려줘야한다.
@ElementCollection(fetch = FetchType.EAGER)
private List<String> roles;
Ex) 회원 가입, 로그인
- 회원 가입 - localhost:8080/auth/signup
- content-type을 꼭 json 타입으로 바꿔야 403 에러가 안 난다.
<hide/>
{
"username" : "grace",
"password" : "grace123!#$",
"roles" : ["ROLE_READ"]
}
Note) 실행 결과
<hide/>
{
"id": 1,
"username": "grace",
"password": "$2a$10$2ea6Ee0soZ0MJZmKSXicSe3Uw4izKkodyBUp6RVC8PTwTpLPTu9R.",
"roles": [
"ROLE_READ"
],
"enabled": false,
"authorities": [
{
"authority": "ROLE_READ"
}
],
"accountNonExpired": false,
"credentialsNonExpired": false,
"accountNonLocked": false
}
- 로그인 - localhost:8080/auth/signin
<hide/>
{
"username" : "grace",
"password" : "grace123!#$"
}
Note) 실행 결과 - 생성된 jwt 토큰이 나온다.
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJncmFjZSIsInJvbGVzIjpbIlJPTEVfUkVBRCJdLCJpYXQiOjE2NjM2OTIzNzQsImV4cCI6MTY2MzY5NTk3NH0.vuNsowYVFhfIOmfkvJBqw_ZD_10U8szND5Y1kskwv1Rk_I9ix6tnLElIPcetecPcm4ykFgucwDW_9CHHXz9xfA
Ex) 회원의 토큰을 이용해서 회사 목록 조회하기
- 다음과 같이 헤더에 authorization을 추가하고 "Bearer " + 아까 생성된 토큰 값을 입력한다.
- 토큰을 제외하고 send 하면 403 - forbidden 에러가 발생한다.
Note) 실행 결과 - 회사 목록이 반환된다.
Ex) 다음과 같이 회사를 조회한다.
Note) 실행 결과
- 토큰을 넣었지만 위와 같이 403 에러가 발생한다.
- WRITE 권한이 없는 유저이기 때문이다.
- 회원 가입 하고 로그인하면서 생성되는 토큰을 클라이언트가 가지고 있다가 API로 호출할 때 헤더에 Authorization - 토큰을 넣는다.
5.5 회사 삭제 기능 구현
Ex) DELETE
- dividendRepo
<hide/>
@Transactional
void deleteAllByCompanyId(Long id);
- 컴퍼니 서비스
- 회사를 delete 하면 트라이에 저장된 회사명도 지워야한다.
<hide/>
@Service
@AllArgsConstructor
public class CompanyService {
private final Trie trie;
private final Scraper yahooFinanceScraper;
private final DividendRepository dividendRepository;
private final CompanyRepository companyRepository;
// 스크랩한 데이터 저장
// 외부에서 호출할 수 있는 메서드
public Company save(String ticker){
boolean exists = this.companyRepository.existsByTicker(ticker);
if(exists){
throw new RuntimeException("Already exists ticker -> " + ticker);
}
return this.storeCompanyAndDividend(ticker); // 존재하지 않으면
}
public Page<CompanyEntity> getAllCompany(Pageable pageable){
return this.companyRepository.findAll(pageable);
}
// 정상으로 저장되면 회사의 Company 인스턴스 반환
private Company storeCompanyAndDividend(String ticker){
// ticker 기준으로 회사를 스크래핑
Company company = this.yahooFinanceScraper.scrapCompanyByTicker(ticker); //회사정보 반환
if(ObjectUtils.isEmpty(company)){ // 회사 정보가 존재하지 않으면
throw new RuntimeException("Failed to scrap ticker -> " + ticker);
}
// 해당 회사가 존재할 경우, 회사의 배당금 정보를 스크래핑
ScrapedResult scrapedResult = this.yahooFinanceScraper.scrap(company);
// 스크래핑 결과
CompanyEntity companyEntity = this.companyRepository.save(new CompanyEntity(company));
List<DividendEntity> dividendEntityList= scrapedResult.getDividends().stream()
.map( e -> new DividendEntity(companyEntity.getId(), e))
.collect(Collectors.toList()); // 결괏값을 리스트로 반환
this.dividendRepository.saveAll(dividendEntityList);
return company;
}
public List<String> getCompanyNamesByKeyword(String keyword){
Pageable limit = PageRequest.of(0, 10);
Page<CompanyEntity> companyEntities = this.companyRepository.findByNameStartingWithIgnoreCase(keyword, limit);
return companyEntities.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
}
// 저장
public void addAutoCompleteKeyword(String keyword){
this.trie.put(keyword, null);
}
// 검색
public List<String> autoComplete(String keyword){
return (List<String>) this.trie.prefixMap(keyword)
.keySet()
.stream()
// .limit(5) // 개수 제한
.collect(Collectors.toList());
}
public void deleteAutocompleteKeyword(String keyword){
this.trie.remove(keyword);
}
// 삭제
public String deleteCompany(String ticker) {
var company = this.companyRepository.findByTicker(ticker)
.orElseThrow(() -> new RuntimeException("존재하지 않는 회사입니다."));
this.dividendRepository.deleteAllByCompanyId(company.getId());
this.companyRepository.delete(company);
this.deleteAutocompleteKeyword(company.getName());
return company.getName();
}
}
- 컨트롤러
- 대이터를 지우면 캐시에서도 지워야한다. clearFinanceCache()
- delete는 아무나 호출할 수 없도록 한다.
<hide/>
@RestController
@RequestMapping("/company")
@AllArgsConstructor
public class CompanyController {
private final CompanyService companyService;
private final RedisCacheManager redisCacheManager;
@GetMapping("/autocomplete")
public ResponseEntity<?> autocomplete(@RequestParam String keyword){
var result = this.companyService.getCompanyNamesByKeyword(keyword);
return ResponseEntity.ok(result);
}
@GetMapping
@PreAuthorize("hasRole('READ')")
public ResponseEntity<?> searchCompany(final Pageable pageable){
Page<CompanyEntity> companies = this.companyService.getAllCompany(pageable);
return ResponseEntity.ok(companies);
}
// 회사 및 배당금 정보 저장
@PostMapping
@PreAuthorize("hasRole('WRITE')") // 쓰기 권한이 있는 USER만 API 호출 가능하
public ResponseEntity<?> addCompany(@RequestBody Company request) {
String ticker = request.getTicker().trim();
if(ObjectUtils.isEmpty(ticker)){
throw new RuntimeException("ticker is empty");
}
Company company = this.companyService.save(ticker);
this.companyService.addAutoCompleteKeyword(company.getName()); //회사 저장할 때마다 트라이에 저장
return ResponseEntity.ok(company); // 회사의 정보를 반환
}
// 회사 삭제
@DeleteMapping("/{ticker}")
@PreAuthorize("hasRole('WRITE')")
public ResponseEntity<?> deleteCompany(@PathVariable String ticker){
String companyName = this.companyService.deleteCompany(ticker);
this.clearFinanceCache(companyName);
return ResponseEntity.ok(companyName);
}
public void clearFinanceCache(String companyName){
this.redisCacheManager.getCache(CacheKey.KEY_FINANCE).evict(companyName);
}
}
============================ 오류 =============================
<hide/>
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-09-21 11:21:41.987 ERROR 8212 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of constructor in com.dayone.web.CompanyController required a bean of type 'org.springframework.data.redis.cache.RedisCacheManager' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.data.redis.cache.RedisCacheManager' in your configuration.
- 오류: 컴퍼니 컨트롤러에 RedisCacheManager 빈이 없다고 오류가 나온다.
- 원인: 캐시 매니저를 RedisCacheManager가 아닌 CacheManager 라고 선언해야한다.
private final RedisCacheManager redisCacheManager;
============================ 오류 =============================
- 오류: 400 bad request company/POST, company/GET 실행이 안된다.
- 원인: 다음과 같이 headers에 key, value를 설정해야한다.
Note) 실행 결과
Ex) DELETE - http://localhost:8080/company/MMM
- 헤더에 권한 정보도 넣어줘야한다.
Note) 실행 결과
- 지운 회사의 회사명이 응답으로 나온다.
- 서비스, 자동완성, 캐시 안에 있는 데이터까지 모두 지워야 DELETE가 된다.
'Spring Projcect > 배당금 프로젝트' 카테고리의 다른 글
Chapter 07. README (0) | 2022.09.21 |
---|---|
Chapter 06. 완성도 높이기 (0) | 2022.09.20 |
Chapter 04. 서비스 구현 (0) | 2022.09.14 |
Chapter 03. 서비스 설계 (0) | 2022.09.13 |
Chapter 02. 스크래핑(Scraping) (0) | 2022.09.12 |