목차

회원 도메인 개발

-구현 기능

-회원 등록

-회원 목록 조회

 

순서

-회원 엔티티 코드 다시 보기

-회원 리포지토리 개발

-회원 서비스 개발

-회원 기능 테스트

 

회원도메인개발

1. 회원엔티티 코드 다시보기

 

2. MemberRepository

사진

a.레포지터리 패키지 생성

b.MemberRepository 클래스 생성

c. @Repository 어노테이션 세팅

*스프링에서제공하는 Repository 어노테이션을 설정해주면, 

component 스캔에의해서 스프링빈으로 자동으로 관리가된다.

 

*스프링빈

-자바 객체

-스프링 컨테이너(Spring Container)에 의해서 만들어진 자바 객체를스프링 빈이라고 부른다.

-스프링 빈과 자바 일반 객체와의 차이점은 없다굳이 다른점이라고 한다면, 컨테이너에 의해 만들어졌다.

 

*스프링 컨테이너가 나오게 된 배경

A a = new A( );

A a1 = new A( );

A a2 = new A( );

각각의 객체를 생성해주도록 코드를 작성하여 메모리를 3번 낭비했었는데,

메모리를 효율적으로 관리해주고자 메모리를 1번만 낭비를 하게 하기 위해 스프링 컨테이너가 탄생했다.

스프링 컨테이너에 스프링빈을 넣어 객체를 하나만 생성하여 공통 객체역할로 이 객체를 가지고 3개의 객체를 @Autowired를 활용해 생성한다.

 

예)

@Autowired

a a;

 

@Autowired

a1 a1; 

 

@Autowired

a2 a2; 

 

 

-@PersistenceContext 설정 및 private EntityManager em;

스프링이 엔티티 매니저를 만들어서 em에 인젝션 주입을 해준다.

 

d. 조회하는로직을 작성한다.(save, find)

save

-기본 엔티티매니저 사용은 persist 영속성 컨텍스트에 멤버엔티티객체를 넣고

후에 트랜잭션이 커밋 되는 시점에 디비에 인서트쿼리가 날라가면서 반영된다.

 

find메서드 사용

-단건조회

return em.find(Member.class : 첫번째 타입, id :primary key를 넣어준다.)

 

 

e. 회원전체 로직을 작성한다.(find all)

-createQuery다음에""를타입할것. 첫번째 jpql를 사용 뒤에는 반환타입

-하기코드를합친다.

( return result;에서 단축키 ctrl + alt + n < inline all < refactor )

List<Member> result = em.createQuery("select m from Member m", Member.class) .getResultList(); return result;

 

확인

*jpqlsql 차이점

-기능적으로 동일하고 결국 sql로 번역한다.

-sqlfrom의 대상이 테이블대상으로 쿼리를 한다.

-jpqlfrom의 대상이 엔티티객체를 대상으로 쿼리를 한다. (사진에 있는 Member를 조회한다.)

 

f. parameter binding에 의해서 특정 회원만 찾는 코드

-setParameterintPosition으로설정

아키텍처 설명

 

1.구현범위

회원기능

-회원가입, 회원목록

상품기능

-상품등록, 상품목록

주문기능

-상품주문, 주문내역

 

2.핵심 비즈니스 메서드를 선구현

 

예제 단순화를 위해 하기 기능은 구현x

-로그인과 권한 관리x

-파라미터 검증과 예외 처리 단순화

-상품은 도서만 사용

-카테고리는 사용x

-배송,정보는 사용x

 

3.애플리케이션 아키텍처

계층형 구조 사용 controller, web: 웹 계층

*controllerrepository로 접속할 수 있도록 구축할 예정

service: 비즈니스 로직, 트랜잭션 처리

repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용

domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용

 

패키지 구조

-jpabook.jpashop

-domain

-exception

-repository

-service

-web

 

개발 순서: 서비스, 리포지토리 계층을 개발하고, 테스트 케이스를 작성해서 검증,

 

마지막에 웹 계층 적용

*웹을 제외한 핵심비즈니스 계층을 먼저 개발한다

주문 검색 기능 개발

-JPA에서 동적 쿼리를 해결방법

 

검색기능

1. 검색 리포지터리 생성

a. OrderSearch 생성 및 정보생성

 

*OrderRepository의 하기 부분을 완성시키기 위한 검색 리포지터리 생성

public List<Order> findAll(OrderSearch orderSearch) { }

 

-파라미터의 조건이 있으면 where문으로 검색이 가능하다.

 

b. OrderRepository에서 OrderSearch 구문 작성

설명

em.createQuery~

-jpql로 작성한다.

-테이블이 아닌 객체로 표현한다. 실제적으로 보면 일반적인 sql join문으로 실행된다.

 

*JPQL이란 <출처:위키백과>

-Java Persistence Query Language

 Java Persistence API 스펙의 일부로 정의 된 플랫폼 독립적인 객체 지향 쿼리 언어

-JPQL은 관계형 데이터베이스에 저장된 엔티티에 대해 조회하는 데 사용

 

값이 다 있다는 가정하에 구문 작성_1

사진 

 

동적쿼리로 변경_2

상태도 다가져오고 네임도 가져오고 createQuery에 있는 내용만 선택을 해야되라고 한다면 동적 쿼리가 된다.

ordersearch에 네임 파라미터 가 없으면 상태값도 선택 되어 있지 않으면 주문이든 주문취소든 다가져와 라고 하려한다면 다음과 같이 작성되어야 한다.

 

사진

 

 

위의 구문을 JPA에서 동적 쿼리로 바꾸어주려면 어떻게 해야 하는가?

JPA에서 동적 쿼리를 어떻게 해결해야 하는가?

 

방법 1 jpql 문자로 무식하게 해결을 한다.

 

 

설명

단점

jpql을 문자로 생성한다는 것은 번거롭고 실수나 버그가 충분히 발생한다.

 

*mybatis를 사용하는 이유가 동적쿼리를 생성하는데 굉장히 편하다는 이점이 있다.

 

JPA Criteria 로 작성 (비권장)_2 -> 실제 포트폴리오에선 이것으로 사용함.

-jpa가 제공하는 jpql를 자바로 작성할 수 있도록 표준인 것이 있다.

 기본편에도 간단하게 설명만 하고 지나갔다.

 

장점

-build하고 나면 결과적으로 jpql이 만들어진다.

-jpql이 자바코드로 작성할 수 있게 jpa criteria가 도와준다.

-동적 쿼리 작성할 때 메리트가 있다.

 

단점

-이것만 봤을 때는 어떤 jpql이 만들어질지 감이 안잡힌다.

 

 

방법 3 QUERYdsl로 작성 (실무에서 권장)

테스트 목표

-상품 주문이 성공해야 한다.

-상품을 주문할 때 재고 수량을 초과하면 안 된다.

-주문 취소가 성공해야 한다.

 

1. OrderServiceTest클래스를 만든다.

-shitft + ctrl + T를 이용하여 JUnit4createTest를 한다.

 

@Autowired

ENTITY매니저로 테스트데이터를 바로 넣기 위해 사용

em.persist(member);

 

 

2. 상품주문 테스트

사진

 

설명

-주석 확인할것

 

3. 상품주문재고수량초과

사진

 

설명

상품주문재고수량초과 상황설정

-주문일어 날 때에 재고수량 초과 하도록 설정

-fail 구문 작성

 

상품수량을 초과되게 작성하면 정상작동후 fail로 내려 온다.

-상품주문재고수량초과 구문이 제대로 작동됨을 확인할 수 있다.

 

상품 수량을 숫자에 맞게 작성하면 에러가 발생한다.

주문수량이 재고수량초과가 되지 않으면

orderService.order(member.getId(), item.getId(), orderCount); 이 시점에서 처리가 끝나므로 fail로 내려가지 않아

fail구문이 작동되지 않으므로 에러가 발생한다.

 

4. 주문취소

-(ctrl + shift + t를 누르면 실제 주문취소구문이 있는 클래스를 왔다갔다 할 수 있다.)

 

사진

 

설명

GIVEN

a.주문취소 상황설정

-Long orderId = orderService.order(member.getId(), item.getId(), orderCount());

:orderId를 생성한다. (주문한 상황까지 상황이 주어져야 한다.)

 

b.주문이 취소 되었을 때

 

c.취소된 주문 Id 확인, 주문 취소 Status, 아이템재고수량결과 구문

 

주문 서비스 개발

1.OrderService 클래스 생성

 

2.주문서비스에서 주문 작성

-주문서비스를 받을 때 이루어지는 정보들 작성

 

-엔티티조회

-배송정보 생성

-주문상품 생성

-주문생성

 

-주문저장

*주문저장에 대한 설명

Cascade

-이전에 order클래스에서 설정해준 Casacade.All 설정 때문에 강제로 Persist를 해준다.

-다른 곳에서도 delivery와 같은 정보를 다른 엔티티에서 참조하거나할때에 cascade를 사용해서는 안된다.

-한 사이클 내에서만 private owner개념으로 사용한다면 casacade를 사용해도 된다.

 

사진

 

정리

엔티티조회

-findOne으로 member,item 엔티티조회를 한다. 이것 때문에 의존관계를 넣었다.

 

배송정보생성

-static 생성메서드를 통해 주문상품 생성

-static 생성메서드를 통해 주문 생성

 

주문저장

-주문 저장시 cascade옵션으로 인해 delivery, orderitem과 자동으로 db persist가 된다.

정확히는, 트랜잭션이 커밋 되는 시점에 flush 가일어나면 insertdb에 들어가게 된다.

 

참고

생성로직을 다르게 작성하면(생성필드를 추가작성한다던가) 다른 스타일로 작성할 때에는 유지보수하기가 어려워진다.

그래서 다른 로직이나, 스타일로 작성하지 못하도록 OrderItem클래스에서 Constructor를 만들때에 protected를 만들어준다. jpaprotected까지 기본생성할 수 있도록 스펙상 허용한다.

 

Protected OrderItem() {

}

 

이럴 경우 OrderService클래스에서 다른 스타일로 쓸 경우 작성된 코드에 빨간밑줄이 생긴다.

 

참고2

생성메서드에서 주문생성에 대해 수정을 하도록 묵시적으로 요구할 때에 작성하는 코드

Order클래스에서 하기와 같이 작성

 

@NoArgsConstructor(access = AccessLevel.PROTECTED)

public class Order{

 

OrderService클래스에서 직접 생성할 경우에 new Order() 로 작성할 경우 빨간밑줄이 생긴다.

이와 같이 코드를 제약을 걸어주는 것이 좋은 설계와 유지보수로 끌어갈 수 있다.

 

3 .주문서비스취소 구문 작성

-취소구문작성

-취소구문에서 cancel에서 흐름은 다음으로 넘어간다.

 

1차 흐름-Order클래스

this.setStatus(OrderStatus.CANCEL);

for (OrderItem orderItem : orderItems) {

orderItem.cancel();

}

 

2차흐름-OrderItem클래스

public void cancel() {

getItem().upStock(count);

}

 

3.2.sqljpa일 때에 차이점

-jpa 장점을 설명

sql은 일일이 수작업을 해주어야 한다.

 

 

참고

jpa orm 사용시 자주 사용하는 패턴

-엔티티의 핵심 비즈니스 로직이 있고 서비스 계층은 단순히 호출(save로 위임하는 형태로 흘러간다.

 엔티티가 비즈니스로직을 가지고 객체지향의 특성을 활용하는 것을 도메인 모델 패턴이라고 한다.

 

sql 사용시 자주 사용하는 패턴

-엔티티에는 비즈니스 로직이 거의 없다거나 서비스 계층에서 대부분의 비즈니스 로직을 처리하는것을

 트랜잭션 스크립트 패턴이라고 한다.

 

무엇이 유지보수 하기 좋은지를 고민 하여 두 개의 패턴을 사용할 것!

 

사진

 

주문 리포지토리 개발

 

1. Repository 패키지에 OrderRepository 클래스를 생성한다.

 

사진

주문도메인개발

 

주문도메인이 제일 중요!

-비즈니스로직들이 얽혀서 돌아가는 것을 jpa나 엔티티를 가지고 어떻게 풀어내는지 알 수 있다.

-트랜잭션 스크립트 패턴과 도메인모델패턴을 코드를 통해 이해 할 수 있다.

 

1.주문 도메인 개발

구현 기능

-상품 주문

-주문 내역 조회

-주문 취소

 

순서

-주문 엔티티, 주문상품 엔티티 개발 (핵심비즈니스로직)

-주문 리포지토리 개발

-주문 서비스 개발

-주문 검색 기능 개발

-주문 기능 테스트

 

주문,주문상품 엔티티 개발

1. 주문클래스에서 생성메서드 생성

 

설명

-Order가 연관관계를 걸면서 세팅이 되고, 상태랑 주문시간 정보까지 정리가 되어서 세팅이 된다.

 

중요!

앞으로 무언가 생성하는 시점을 변경해야 하면 다른데서가 아닌 이곳에서만 바꾸면 되기 때문에 이렇게 작성하는 것이 중요하다.

 

실습사진

 

2.주문클래스에서 비즈니스로직 주문취소 작성

설명

a.완료된 배송은 취소 불가

 

b.주문하고 난 뒤에 취소

-주문한 상품이 2개이면 취소시 2개 각각 취소를 시켜주어야 한다.

 OrderItem에 취소구문작성해주어야 함.

 

c. OrderItem클래스에서 취소구문작성

-주문취소로 인해 orderItem에도 비즈니스 로직이 생겼다.

-취소하게 됨으로써 주문하기전에 기존 수량으로 다시 올라간다.

 

d. 조회로직(주문상품 전체 가격조회)

-주문총가격은 주문가격 및 수량으로 계산이 되어야 한다.

 

조회로직 간소화하는 방법

1. for지점에서 alt + enter -> sum,

2. inttotalprice에서 ctrl + alt +n 사용 stream로 변경)

3. 최종정리

 

실습사진

 

e.조회로직의 OrderItemgetTotalPricealt + enter이용하여 OrderItem getTotalPrice메서드 생성

-주문총가격은 주문가격 및 수량으로 계산되어야함을 염두에 두고 작성한다.

 

사진

 

f. 주문이 생성될때에 생성되는 OrderItem에 대한 생성메서드를 OrderItem클래스에서 작성

-Order클래스에서 작성된 생성메서드 createOrder에서 받아오는 orderItem 정보를 위해 생성메서드를 작성함

-주문이 생성되기 전에 기존에 남아있는 수량에서 OrderItem에서 주문한 수량만큼 수량이

 내려가고 order클래스에서 createOrder(신규주문생성)으로 정보가 넘어가게 된다.

*단순히 비즈니스적인 면에서 코드를 작성을 할 때에 위에처럼 그렇게 되어야 논리상 말이 맞.

 실제로는 OrderItem이 넘어 올 때 파라미터나 DTO를 이용하여 복잡하게 구성될 것 같다.

 

사진

기능 설명정리

생성메서드

밖에서 새로운 Order를 받아와서 set하는 방식이 아닌

주문이 들어오는 시작점부터 createOrder를 호출해서

Order클래스 안에서 Order에 대한 비즈니스로직을 완료한다.

주문생성과 관련된 문제가 있을 때 이곳을 확인하면 된다.

 

주문취소

취소불가

-이미 배송완료된 상품 취소는 불가하다는 비즈니스로직에 대한 체크로직이 엔티티 안에 있다.

 

취소

-취소 될시에 해당 아이템취소 수량만큼 수량이 다시 늘어나게 된다.

 

조회로직

전체주문가격조회

-주문가격과 주문수량을 곱하기 해서 최종 주문가격이 조회가 된다.

 

 

상품서비스개발

 

1. servcie Directory 내에 ItemService 클래스 생성

 

 

-ItemServiceItemRepository에 위임만 하는 서비스이다.

-다른 방법으로 구현을 한다면 control에서 ItemRepository에 바로 접근하도록 구현해도 괜찮을 것 같다.

 

구현 기능

상품 등록

상품 목록 조회

상품 수정

 

순서

상품 엔티티 개발(비즈니스 로직 추가)

상품 리포지토리 개발

상품 서비스 개발

상품 기능 테스트

상품 엔티티 개발(비즈니스 로직 추가)

 

 

1.Item에서 재고가 늘어나고 줄어들고의 비즈니스 로직이 필요

stockQuantity에 비즈니스 로직 작성하는 이유 : 재고stock

-데이터를 가지고 있는 stockQuantity에 비즈니스 로직을 만드는 것이 응집력이 있고, 객체

 지향적으로 생각했을 때 맞다.

-아이템엔티티를 가지고 있어 관리하기 좋다.

 

 

2.Exception 디렉터리생성 및 내에 NoMoreException class 생성

 

 

 

3. Repository 디렉터리에 ItemRepository 클래스 생성

 

*EntityManager

-엔티티 매니저의 기능은 엔티티를 저장,수정,삭제,조회 및 기타 엔티티와 관련된 일들을 처리한다.

 

설명

아이템은 jpa에 저장하기 전까지 처음엔 데이터 저장할 때 id가 없다.

새로운 객체라는 의미 jpa가 제공하는 persist(신규로등록시) 사용하거나

em.merge(update개념, item값이 있을 때)을 사용한다.

 

문제상황

 

1.RealMember 클래스이름을 다시 기억하기 쉽게 Member이름으로 변경(테스트용 Member클래스파일은 바로삭제)

연동되는 클래스파일이름을 RealMember로 안해주고 그냥 Member를 사용한상태로 테이블을 생성했었었다.

그리고, 다시 기존 Member 클래스파일을 지우고 RealMember 클래스파일 이름을 MemberRENAME으로 바꿔주었다.

 

2. 의문점

이미 한번 과거 Member 안에 작성된 코드를 이용해서 생성된 테이블 을 한번 만들어줬다가

이름만 바꿔서 코드내용 바꿔주어서 다시 생성하면 변경된대로 적용되는지 궁금하다.

 

3. 디비 h2 맞고, 연동도 되어 있다.

H2 데이터베이스 콘솔접속해서 테이블들을 확인해보니 RealMember는 그대로 있고 Member도 그대로 있다.

테이블에서는 영향을 미치지않아 내가 직접 drop해주어야 하는 건지 모르겠다.

 

문제해결시도과정

1. 이름이 변경된 Member클래스에서 Member를 import 설정

Build completed with 2 errors and 0 warnings in 11 s 105 ms

빌드하는 과정에서 에러가 두가지 있었는데 Memberimport를 해주었다.

바로 밑에 코드 에도 (Member)로 설정

 

 

2.초기화옵션 ddl-auto:create, 데이터베이스에서 기존 Member 테이블 삭제시도

 

사진

 

ddl-auto:create옵션인 상태에서 h2 데이터베이스 접속해서 기존 MEMBER 테이블 삭제하려고 하니

다음과 같은 에러가 나온다.

------------------------------------------------------------------------

Cannot drop "MEMBER" because "FKPKTXWHJ3X9M4GTH5FF6BKQGEB" depends on it; SQL statement:

DROP TABLE member [90107-200] 90107/90107 (도움말)

org.h2.jdbc.JdbcSQLSyntaxErrorException: Cannot drop "MEMBER" because "FKPKTXWHJ3X9M4GTH5FF6BKQGEB" depends on it; SQL statement:

DROP TABLE member [90107-200]

at org.h2.message.DbException.getJdbcSQLException(DbException.java:576)

at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)

at org.h2.message.DbException.get(DbException.java:205)

at org.h2.command.ddl.DropTable.prepareDrop(DropTable.java:98)

at org.h2.command.ddl.DropTable.update(DropTable.java:124)

at org.h2.command.CommandContainer.update(CommandContainer.java:198)

at org.h2.command.Command.executeUpdate(Command.java:251)

at org.h2.server.TcpServerThread.process(TcpServerThread.java:406)

at org.h2.server.TcpServerThread.run(TcpServerThread.java:183)

at java.lang.Thread.run(Unknown Source)

 

at org.h2.message.DbException.getJdbcSQLException(DbException.java:576)

at org.h2.engine.SessionRemote.done(SessionRemote.java:611)

at org.h2.command.CommandRemote.executeUpdate(CommandRemote.java:237)

at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:228)

at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:201)

at org.h2.server.web.WebApp.getResult(WebApp.java:1459)

at org.h2.server.web.WebApp.query(WebApp.java:1116)

at org.h2.server.web.WebApp$1.next(WebApp.java:1078)

at org.h2.server.web.WebApp$1.next(WebApp.java:1065)

at org.h2.server.web.WebThread.process(WebThread.java:178)

at org.h2.server.web.WebThread.run(WebThread.java:94)

at java.lang.Thread.run(Unknown Source)

------------------------------------------------------------

 

-여기에서 내가 확인하고 이해한 에러 핵심은 다음과 같다.

-일단 MEMBER 테이블을 삭제할 수 없다. 그리고 Unknown Source 알려지지 않은 소스 Cannot drop "MEMBER"....

.

.

at java.lang.Thread.run(Unknown Source)

 

-기존에 MEMBER클래스파일을 삭제해서 테이블에서도 찾아내지 못해 삭제하지 못하는 것이라는 가설을 세웠다.

 

2. Real_Member 테이블 삭제 시도

-DROP TABLE Real_Member;

-지워진다..

-이것은 Real_Member 이름을 가진 클래스파일을 인식해서인지 삭제가 되었다.

-다시 h2 데이터베이스 재접속 상황은 같다.

 

3. 기존 MEMBER 클래스 파일을 다시 만들어서 삭제한다?

-이미 RealMember클래스파일 이름을 MEMBER파일로 바꾸었는데 다시 만들어서 테이블삭제는 말이 안되었다.

이미 변경한 MEMBER클래스파일은 어떻게 한단 말이지..

 

4. 테이블삭제시도전에 코드를 run하는 과정에서 drop if exists 문구와 error executing ddl를 확인한 것이 기억났다.

 

출처 https://galid1.tistory.com/610

구글링 해보니, 초기화 옵션을 (ddl-auto: update)update로 해주면 에러가 해결된다는 내용이 있었다.

물론 나와는 상황이 조금 달랐지만, 혹시 테이블도 update 되지 않을까 하는 생각으로 변경해주고 다시 테이블 설정을 했다.

 

 

결과는 클래스파일 RealMember에서 RENAME으로 변경한 Member클래스파일에 작성된 컬럼들이 생성됨을 확인했다. 즉 새롭게 변경해준 Member클래스파일 내용으로 update가 되었다.

 

5.기존에 있던 ID, USERNAME은 그대로 존재한다. 기존에 있는 컬럼에 합쳐진 것 같다.

 

맴버테이블을 연관관계없이 생성한 상태일 때에 삭제가 안될 수도 있다고 하여,

기존 멤버테이블 컬럼삭제된 상태에서, 테이블을 삭제 시도 해보았다.

 

하기와 같은 명령어를 이용해 ID, USERNAME을 삭제했다.

ALTER TABLE MEMBER

DROP COLUMN USERNAME;

 

삭제가 되어 현재 변경된 Member클래스 내용과 동일한 내용으로 업데이트하여 해결완료!

 

7.이왕 삭제하는 김에 테이블전부 삭제하고 다시 생성하기로 했다. (ddl-auto : create)

 

*맴버테이블을 연관관계없이 생성한 상태일 때에 삭제가 안될 수도 있다고 하여,

만약 연관관계가 있는 상태에서 삭제가 되지 않는다면 하기 명령어로 해볼 것

 

DROP TABLE : 테이블의 모든 데이터 및 구조를 삭제

-DROP TABLE 테이블명 [CASACADE CONSTRAINT];

 

기타공부내용

기타해결방법(기존 member 클래스파일을 realmember로 변경할 경우에 사용할 것)

-삭제된 member는 이제 더 이상 jpa가 관리하는 persist에 의해 영속성을 가지지 못하고

그냥 orm과는 관계 없고, 자바와는 관계없는 분리된 db테이블이 됨

더 이상 사용되지 않는 테이블이 되었다.

 

ddl-auto : create (ddl-auto옵션은 create)

멤버랑 리얼멤버 테이블을 drop시키고 다시 테이블을 새로 생성해도 무방하다.

의존성을 가진다면 테이블 스페이스를 전체를 다 날려버리면,

스프링이 재시작 될 때에 처음부터 다시 전체 테이블을 만들어줄 것이다.

바뀐 멤버엔티티에 영속성이 걸리게 되고, 연관관계매핑도 작성된대로 설정이 된다.

 

이해를 위한 ddl-auto : create옵션 추가설명

-예를들어, jpashop이라는 폴더를 만들고 그안에 member라는 폴더를 만들고 파일도 있다.

근데 이름을 realmember로 바꾸려하면 create 옵션은

기존 member는 그대로 냅두고 realmember폴더를 새로 만드는 것과도 같다.

+ Recent posts