빈티지 카메라 웹 사이트 만들기
빈티지 카메라를 살 수 있는 웹 사이트이며 프로젝트 만드는 과정을 정리하고 기록해보자
- 기능
- 회원
- 로그인 / 회원가입
- 회원 조회
- 상품
- 상품 등록
- 상품 수정
- 상품 조회
- 상품 삭제
- 주문
- 상품 주문
- 장바구니
- 주문 취소
- 주문 조회
- 기타
- 상품 재고 관리를 할 수 있다.
- 회원
- 패키지 구조
도메인 : 핵심 비즈니스 업무 영역 (컨트롤러나 폼은 핵심 업무 도메인이 아님)
- domain
- login
- item
- member
- order
- orderItem
- delivery
- address
- web
- login
- item
- member
- order
- orderItem
- delivery
- address
- exception
- repository
- service
"도메인"은 웹 기술에 의존하지 않고 "웹"은 다른 웹 기술로 바꿀 수 있다.
web 은 domain 을 의존(참조)해서 다른 웹기술로 변경되거나 삭제를 하더라도 domain은 영향을 받지 않는다.
그렇기에 웹과 도메인은 서로 다른 영역이며 domain은 web을 알 필요가 없다.
이렇게 의존관계가 단방향이 되어야 유지보수에 좋은 설계!
예를들어 domain의 itemRepository에 web 영역의 ItemSaveForm 이 사용되면 의존하게 돼버려서 쓰면 안되고
web 영역의 ItemSaveForm 에 domain영역의 Item 을 불러와야한다. (new Item)
도메인 모델링
엔티티 매핑
- 많이 헷갈리는 다대다 관계 정리
- Order 주문과 Item 상품은 다대다 관계이다
- 회원은 여러 주문을 할 수 있다
- 그리고 주문에서 여러개의 상품을 담을 수 있다.
- 주문(Order)과 상품(Item)은 다대다 관계이기 때문에 중간에 OrderItem 엔티티를 추가해준다
- OrderItem 엔티티를 추가해서 주문 관계와는 일대다, 상품 관계와는 다대일이 된다.
- 왜냐하면 다대다 관계는 거의 사용하지 않기 때문!
- Order 주문과 Item 상품은 다대다 관계이다
- 왜 item엔티티는 orderItem 을 참조안하는걸까?
- 연관관계는 꼭 필요한 경우에만 설정하는 것이 좋다.
- orderItem 입장에서 item 알아야하지만 반대로 item 은 orderItem 을 찾아갈 일이 많이 없기때문!
- 왜 order 엔티티만 member엔티티를 참조하는걸까?
- 보통 일대다 관계에서 다(N) 쪽이 외래키(FK) 를 가진다.
- 외래키(FK) 를 갖고있는 쪽이 연관관계 주인이다.
- order 엔티티가 연관관계 주인이라서 member 엔티티를 참조하는 것
- 일대일 관계에서는 주로 양쪽 엔티티에서 서로를 참조한다
- Order 엔티티와 Delivery 엔티티처럼
테이블 매핑
외래 키가 있는 곳이 연관관계의 주인
프로젝트 생성
- Project: Gradle Project
- Language: Java
- Spring Boot: 3.2.3 (뒤에 아무것도 안 붙인걸로 선택한다.)
Project Metadata
- Group: hello
- Artifact: vintage-camera-shop
- Name: vintage-camera-shop
- Package name: hello.vintage-camera-shop
- Packaging: Jar (주의)
- ava: 17
Dependencies
- Spring Web
- Lombok
- Thymeleaf (JSP를 안 쓰고 타임리프를 사용)
프로젝트 생성 후 정상 실행이 되는지 확인
- 메인메서드 run 해보기
- 콘솔에 8080 확인하고 http://localhost:8080/ 들어가서 Whitelabel Error Page 확인하면 성공
기본 세팅
- 롬북세팅
settings > plugin > lombok 검색 후 실행 (재시작)
settings > Annotation Processors > Enable annotation processiong 체크하기!
- 인텔리제이로 변경하기
Settings > Gradle > IntelliJ 로 변경하기
gradle로 하면 gradle을 통해 실행이 돼서 속도가 느려지기 때문
- 데브툴 설정하기
html 수정할때마다 서버 재실행 안 하고 build > Recompile 만 클릭하면 수정이된다.
build.gradle 에 spring-boot-devtools 의존성 추가하기
runtimeOnly('org.springframework.boot:spring-boot-devtools')
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly('org.springframework.boot:spring-boot-devtools')
}
settings 에 가서 Compiler > Build project automatically 체크하기
H2 DB 사용
설치 : https://www.h2database.com/
주의! 스프링 부트 3.x 이상은 2.1.214 버전 이상 사용해야 한다.
h2 를 설치가 끝나면
1. cmd창 열어서 h2 가 설치된 경로로 이동
2. h2.bat 을 입력하면 h2 창이 열린다.
데이터베이스 파일 생성 순서
- jdbc:h2:~/vintage-camera-shop (처음에 한번만!)
- url 에 숫자부분을 localhost 로 수정해준다
- 사용자 폴더에 vintage-camera-shop.mv.db 파일 생성 확인
- cmd창에서 확인 하는법
- cd %HOMEPATH%
- dir
- vintage-camera-shop.mv.db가 생성되는 것을 확인
- 이후부터는 jdbc:h2:tcp://localhost/~/vintage-camera-shop 접속해도 된다.
** H2 버전확인하기 ( 스프링 부트 3.x 이상이라1.4.200 버전이면 안됨!)
SELECT H2VERSION() FROM DUAL;
삭제할때 혹시나 안되면 작업관리자에서 작업 끝내기 후에 삭제하면된다.
DB와 연결이 되는지 동작확인 테스트
main/resources/application.yml
properties 또는 yml 둘 중 하나를 써도 되는데 복잡하면 yml을 쓰는게 더 좋다고 한다.
기존에 있던 application.properties 을 삭제하고 application.yml 을 생성해서 붙여넣으면 된다.
** 띄어쓰기 오타 주의!!!
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/vintage-camera-shop
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# show_sql: true
format_sql: true
logging.level:
org.hibernate.SQL: debug
# org.hibernate.orm.jdbc.bind: trace #스프링 부트 3.x, hibernate6
create : 애플리케이션 실행 시점에 테이블을 모두 지우고 다시 생성
[Member]
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
}
[MemberRepository]
회원 저장, 조회 메서드 생성하기
@Repository
public class MemberRepository {
@PersistenceContext
EntityManager em;
public Long save(Member member) {
em.persist(member);
return member.getId();
}
public Member find(Long id) {
return em.find(Member.class, id);
}
}
- @PersistenceContext : JPA에서 사용되는 어노테이션이며 엔티티 매니저를 주입받기 위해 사용된다.
- EntityManager : 엔티티 매니저는 영속성 컨텍스트를 관리하고 DB와의 상호작용을 해준다.
- em.persist(member) : Member 엔티티를 영속성 컨텍스트에 넣어 관리할 수 있고 조회, 수정 작업을 할 수 있다.
테스트 클래스 [MemberRepositoryTest]
참고) MemberRepository 에 shift + ctrl + T 단축키하면 테스트 클래스 생성 된다 (JUnit5 로 생성!)
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Test
@Transactional
@Rollback(false)
public void testMember() throws Exception {
//given
Member member = new Member();
member.setUsername("memberA");
//when
Long savedId = memberRepository.save(member);
Member findMember = memberRepository.find(savedId);
//then
Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
Assertions.assertThat(findMember).isEqualTo(member); //JPA 엔티티 동일성 보장
}
}
* JUnit5 라면 @ RunWith(SpringRunner.class) 대신 @ExtendWith(SpringExtension.class)을 사용
- @Transactional : 엔티티 매니저를 통한 모든 데이터 변경은 트랜잭션 안에서 이루어져야한다. 그래서 어노테이션 꼭 붙여야함!
- Assertions 생략하기
import org.assertj.core.api.Assertions; 로 임포트하기
Assertions 에 커서를 두고 alt + enter 해서 Add on-demand~~ 클릭하면 앞으로 Assertions 생략이 된다.
생략된 Assertions 코드가 간결해졌다
// then
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
assertThat(findMember).isEqualTo(member);
[실행결과]
쿼리 파라미터가 ?? 로 보이니까 SQL을 로그에 보이게 하고싶다면??
SQL 쿼리를 로그에 찍히게 하기
1. application.yml 에 org.hibernate.orm.jdbc.bind: trace 추가하면 된다. (스프링 부트 3.x 이상일 경우)
logging.level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace #스프링 부트 3.x, hibernate6
* 스프링 부트 2.x 이상은 "org.hibernate.type: trace" 추가
[실행결과]
로그가 찍히는걸 볼 수 있다!
좀 더 자세히 보고 싶다면?
2. build.gradle 에 외부 라이브러리 추가하기 (스프링 부트 3.x 이상일 경우)
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'
[실행결과]
마지막으로 타임리프 적용되는지 테스트 해보기
[HomeController]
@Controller
@Slf4j
public class HomeController {
@GetMapping("hello")
public String hello(Model model) {
model.addAttribute("data", "hello!!");
return "home";
}
}
[home.html]
<html xmlns:th="http://www.thymeleaf.org">
<p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p>
'Spring MVC 웹페이지 만들기' 카테고리의 다른 글
Spring MVC | (5) PRG / Redirect / GET, RedirectAttributes - 저장완료 메세지 추가 (0) | 2024.04.02 |
---|---|
Spring MVC | (4) 상품 수정, 리다이렉트 (0) | 2024.04.02 |
Spring MVC | (3) 상품 등록, @ModelAttribute 와 @RequestParam 차이점 (0) | 2024.04.01 |
Spring MVC | (2) 상품 상세 , 타임리프 (0) | 2024.03.31 |
Spring MVC | (1) 상품 목록, 뷰 템플릿 , 타임리프 (0) | 2024.03.27 |