오류) 순환 참조 오류 & admin.main.do을 입력해도 자동으로 로그인 페이지(member.login)로 이동한다.
<hide/>
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| securityConfiguration defined in file [C:\Users\Ran\Desktop\R\zerobase\remote_mall\build\classes\java\main\com\example\mall\configuration\SecurityConfiguration.class]
↑ ↓
| memberServiceImpl defined in file [C:\Users\Ran\Desktop\R\zerobase\remote_mall\build\classes\java\main\com\example\mall\service\MemberServiceImpl.class]
└─────┘
- 오류: 임플 클래스와 시큐리티 컨피스 클래스 간의 순환 참조 문제
- 원인: impl 클래스가 security 클래스의 bean을 주입 받고 security클래스가 impl 클래스의 bean을 주입 받는 상황?
- 해결
AppConfig 클래스에 @bean을 하나 더 만든다. getBCrypt()bean이 겹치지 않도록 한다. => 클래스 삭제
- Impl
- 멤버 변수 passdwordEncoder 주석 처리하고 로그인 구현 부분을 모두 지운다. (SecurityConfig가 설정하도록 한다.)
- loadByUsername()를 오버라이드한다. loadByUsername는 SecurityConfig가 사용자를 인증하는 부분이다.
- loadByUsername()는 MemberService가 구현하는UserDetailsService에 선언된 메서드이다.
- 그런데 이 메서드의 매개변수 String name이 null() 이라는 오류가 계속 발생했다.
- 그래서 매개변수 이름을 username 으로 바꿨다.
- (여기서의 username은 Member의 userName과는 상관없다.)
- member/login.html 파일의 변수 username, password는 로그인 post 했을 때 컨트롤러에서 넘어오는 데이터이다.따라서, 변수명을 "userId"가 아닌 "username"으로 변경한다.
- 멤버 컨트롤러의 loginSubmit() 매개변수와는 관련 없이 동작한다.
<순환 참조의 원인이 되는 코드>
- SecurityConfig => MemberService(Impl) 참조
- MemberService(Impl)를 참고하고 있다.
<hide/>
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService)
.passwordEncoder(getPasswordEncoder());
super.configure(auth);
}
- ServiceImpl => SecurityConfig 참조
- 아래와 같이 "passwordEncoder"를 사용해서 순환 오류가 발생했다.
- 따라서, passwordEncoder.encode()를 지우고 대신 BCrypt.hashpw()를 이용한다.
// private final PasswordEncoder passwordEncoder;
String encPassword = BCrypt.hashpw(parameter.getPassword(), BCrypt.gensalt());
//passwordEncoder.encode(parameter.getPassword());
참고) yml - 이것만 바꾼다고 해서 순환 참조가 없어지지는 않는다. 순환 참조의 원인이 되는 근본적인 고리를 끊어내야한다.
spring:
main:
allow-circular-references: true
1. admin login
- 관리자 기능
- 관리자 로그인
- 가입한 계정에 대해 데이터베이스에서 관리자로 설정을 바꿔준다.
- 그 다음에 그 계정으로 로그인하면 관리자 페이지에 접속이 가능하다.
- 권한이 없을 경우(404 에러)에는 denied되도록 구현할 예정이다.
- 회원: 상세 정보, 이용 중 / 정지 / 탈퇴
- 상품: 등록 / 수정 / 삭제
- 컨트롤러
<hide/>
@Controller
public class AdminMainController extends BaseController {
@GetMapping("/admin/main.do")
public String main() { // 관리자가 아닌 경우의 처리는?
return "admin/main";
}
}
- 관리자 메인 페이지
- 미리 등록해둔 layout을 이용한다.
<hide/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>관리자 메인 페이지</title>
</head>
<body>
<h1>Online mall - admin</h1>
<div th:replace="/fragments/layout.html :: fragment-admin-body-menu" ></div>
</body>
</html>
Note) 실행 결과
- admin 페이지를 들어가려면 멤버 로그인 창이 가장 먼저 나온다.
- adminYn = true인 계정으로 로그인하고 나면 아래와 같은 메인 화면이 나온다.
- admin아 아닌 경우에 404 에러가 나는데 이 부분을 깔끔하게 처리하는 부분이 필요한다.
2. product
- product - cart 연관 관계: 다대일
- 카트 하나는 멤버 한 명 - 일대일
- 장바구니 하나에 여러 가지 상품이 들어갈 수 있다.
Ex) 테스트 하기 위해 Impl 클래스에 상품 추가 코드를 넣는다.
- Impl - 이메일 인증 코드의 아래에 추가해서 테스트
- 회원 가입한 회원이 이메일 인증을 하면 자동으로 쿠폰, 장바구니, 상품이 생성된다.
- 이 때, 임의로 만든 상품 a를 넣는다.
- member만 save() 했는데 카트, 장바구니, 상품까지 자동으로 save() 된다. - 영속성 전이
<hide/>
// Cart 추가
Cart cart = new Cart();
// Product 추가 테스트
List<Product> products = new ArrayList<>();
Product product = Product.builder().productName("상품A").price(5000).cart(cart).build();
products.add(product);
cart.setProductList(products);
// cartRepository.save(cart); 필요없음
// productRepository.save(product);
// Coupon 추가
LocalDateTime now = LocalDateTime.now();
Coupon coupon = Coupon.builder()
.couponType(CouponType.COUPON_C)
.couponRegDt(now)
.couponExpirationDt(now.plusMonths(3))
// .member(member) => TransientPropertyValueException 발생
.build();
// member에만 set()하고 cart, coupon은 멤버를 따로 저장하지 않는다.
member.setCart(cart);
member.setCoupon(coupon);
member.setUserStatus(MemberCode.MEMBER_STATUS_ING);
member.setEmailAuthYn(true);
member.setEmailAuthDt(LocalDateTime.now());
// couponRepository.save(coupon); 영속성 전이 때문에 필요없어지는 부분
// cartRepository.save(cart);
memberRepository.save(member); // 연관관계의 주인만 저장해도 된다. CASCADE 설정해서 cart, coupon에도 저장한다.
- member
<hide/>
@OneToOne(cascade = CascadeType.ALL) // 추가하면 member 테이블이 달라질 때, cart도 달라진다.
@JoinColumn(name = "CART_ID") // 카트의 변수명과 달라도되나???
private Cart cart;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "COUPON_ID") // 카트의 변수명과 달라도되나???
private Coupon coupon;
- cart
- 일대다 매핑
- cascase = CascadeType.ALL로 설정한다.
- cart가 바뀔 때 product까지 자동으로 업데이트 된다.
<hide/>
@OneToMany(mappedBy = "cart", cascade = CascadeType.ALL) // cascase를 넣어줘야 cart 업로드 될 때 product까지 자동으로 올라간다.
private List<Product> productList = new ArrayList<>();
- product
- 다대일 매핑
<hide/>
@ManyToOne
@JoinColumn(name = "CART_ID")
private Cart cart;
Note) 실행 결과
- cascade()
- save없이 저절로 다 들어간다.
3. category
- 카테고리는 쿠폰과 다르게 앤티티 아니라 enumT으로 고정한다.
@Enumerated(EnumType.STRING)
private Category category; // 딱 한개
<hide/>
public enum Category {
CATEGORY_OUTER,
CATEGORY_TOP,
CATEGORY_PANTS,
CATEGORY_SKIRT,
CATEGORY_SHOES,
CATEGORY_BAG,
CATEGORY_HEADWEAR,
CATEGORY_UNDERWEAR,
CATEGORY_ACCESSORY
;
}
점검 사항
- 로그인 에러날 때, 예외 처리를 페이지로 가게끔 할 수 있나? 컨트롤러가 아니더라도? 할 수 있는지 확인한다.
- loadByUsername() 메서드를 내가 직접 사용하는 게 아니라 시큐리티가 사용자 인증할 때 사용하는 부분이다.
- 그러면 컨트롤러에서 호출할 일이 없는데 이를 컨트롤러에서 호출하는 로직을 구현해도 될지 모르겠다.
- 일대일, 다대일 처럼 "ONE"으로 끝나는 매핑은 fetchType(LAZY)로 바꿔야한다.
- 정상 실행은 됐지만 CASCADE를 @OneToMany에 붙여도 되는가?
- 앞으로 상품 등록 / 상품 주문 / 배송 구현해야한다.
'Spring Projcect > [갠플] Online-mall' 카테고리의 다른 글
[8일차] Order 엔티티 매핑 (0) | 2022.10.03 |
---|---|
[7일차] 관리자 API - 상품 조회 (0) | 2022.09.29 |
[5일차] 연관 관계 매핑 - 장바구니, 쿠폰 (0) | 2022.09.27 |
[4일차] 오류 해결, 로그인 히스토리 (0) | 2022.09.24 |
[3주차] 회원 가입, 로그인 (0) | 2022.09.23 |