기본 환경 세팅하기 

VirtualBox 

: 가상 머신 소프트웨어로 VirtualBox가 SAP ABAP 서버를 실행할 수 있는 가상환경을 만들어주는 역할

 

SAP ABAP

: SAP 의 프로그래밍 언어인 ABAP 을 실행하여 연습할 수 있는 SAP 환경을 구축할 수 있다. 

 

Open SUSE ISO 파일

: Linux 기반 운영체제이다 (SAP ABAP 서버는 Linux 환경에서 동작하도록 설계되어있기 때문)

 

WinSCP

: Window 컴퓨터에서 가상머신 안에 있는 SAP 서버( 리눅스 환경 )로 파일을 전송할 수 있도록 도와주는 도구

 

 

설치 링크 

 

1. 오라클 VirtualBox 가상머신 설치

https://www.virtualbox.org/wiki/Downloads

 

 

2. SAP ABAP 설치

https://developers.sap.com/trials-downloads.html

 

SAP NetWeaver AS ABAP Developer Edition 7.52 SP04 > 1부터 11까지 모두 다운받고 맨 밑에 라이센스도 설치

* 라이센스는 2년마다 최신걸로 재설치하여 갱신해야한다.

반디 집으로 압축해제 추천

 

 

 

3. 오픈 수세 설치

https://download.opensuse.org/distribution/leap/15.3/iso/

 

* 버전은 15.3, 15.2 설치권장 ( 15.4 이상은 에러발생할 수 있음 )

3개 다운로드  

  • openSUSE-Leap-15.3-3-DVD-x86_64-Build38.1-Media.iso.sha256.asc
  • openSUSE-Leap-15.3-3-DVD-x86_64-Build38.1-Media.iso
  • openSUSE-Leap-15.3-3-DVD-x86_64-Build38.1-Media.iso.sha256

 

 

4. WinSCP 설치

https://winscp.net/eng/download.php

 

 

 

 

공유폴더 만들기

 

install.sh 파일이 있는 폴더 (SAP ABAP AS Part 1) 의 파일들과 라이센스 파일 압축풀어서 sap폴더 새로 만들어 넣기

SAP ABAP AS Part 1 파일 모두 복사

 

sap폴더에 복붙

 

sap폴더 새로 만들어 넣고 라이센스도 압축해제 해서 넣기

 

 

 

라이센스 파일(License\SYBASE_ASE_TestDrive)에 있는 SYBASE_ASE_TestDrive.lic 파일 복사해서 server\TAR\x86_64 폴더 안에 붙여넣는다.

(2년마다 최신껄로 재설치해서 갱신하기!)

 

 

vm 가상머신에서 실행하기

 

새로 만들기 클릭

 

 

이미지-기타 클릭

이름은 아무거나 적기

ISO 이미지 > '기타' 선택

 

 

파일크기 큰거 확인

오픈수세 15.3 버전과 파일크기(큰거)  확인하고 선택하기

 

 

 

 

하드웨어

녹색바 최대치까지

기본메모리는 초록색 창 끝까지 선택하고 CPU는 4까지 (초록색 창 끝까지)

 

 

 

하드디스크

디스크 공간 100기가 꼭 설정!!

☆ 주의사항

디스크 공간은 100GB로 꼭 설정한다. 아니면 설치 중에 멈춰버린다

 

 

 

최종

완료 클릭

 

 

 

 

시작 

 

 

Installation 선택 후 엔터 (방향키)

 

쭉쭉 Next 하고 

 

 

yes 

 

 

next

 

 

 

 

Desktop with GNOME (두번째) 선택

(화면 설정하는 부분)

 

 

 

Expert Partitioner 선택

current setting 선택 

 

 

 

sda2 클릭 후 Edit 선택

 

 

 

Format > Ex4 버전 선택 (4GB 이상 파일 지원가능)

Next 

 

주의사항

Ex4 버전을 선택 안 하면 큰 용량을 지원 못하기 때문에 SAP서버 설치가 불가능해서

이 부분을 안 지키면 재설치해야한다

 

 

Accept

Next

 

 

Asia > Seoul 선택

Next

 

 

 

로그인 (가장 중요한 부분 절대 틀리면 안됨)

 

아이디 vhcalnplci 

패쓰워드 Down1oad (번호 1번!)

 

 

 

Tip! 

Caps Lock, Num Lock 되어 있을지도 모르니 비밀번호는 메모장에 미리 적어보기

 

 

 

 

주의 (이 부분도 틀리면 다시 설치해야함)

  • Firwall will be disabled
  • SSH Service will be enabled

통신을 원할하게 하기 위해 firewall 방화벽을 끄고 SSH 파일서버 사용하기를 켜야한다.

아니면 SAP서버 잘 설치해도 로그인이 안되거나 네트워크가 안된다.

 

* 처음에 WinSCP-6.3.5-Setup 를 설치한 것이 SSH와 통신하기 위함

 

 

 

Install

OK

하면 자동으로 부팅이 된다.

 

 


자동으로 부팅이 안된다면?

 

여기서 절대 Installation 클릭하면 안되고 Boot from Hard Disk엔터를 쳐서 부팅을 해야한다.

(자동으로 부팅으로 넘어가기도 함)

 

 

 

 

부팅 후 화면

 

오른쪽 맨 상단 Activities 클릭 후 Terminal 검색해서 터미널 들어가기 




 

 

SAP 서버 세팅하기 


리눅스 ( 터미널 명령어 ) 설명

-i 옵션 >> 로그인 후 폴더 위치를 root사용자 폴더 최상단으로 옮겨준다.
-s 옵션 >> 현재 위치에 그대로 위치하겠다.
cd (change directory) >> 폴더 경로를 이동시켜준다.
cd 폴더명 >> 해당 폴더명으로 위치이동 (ex. cd Intel >> 하위에 있는 Intel 폴더로 이동)
cd 전체경로 >> /를 앞에 붙여준다. (ex. cd /Intel/Logs) 
cd .. >> 상위폴더로 가는 것
Is (list) >> 현재 폴더 안에 있는 리스트를 보여달라 (파일 폴더 모두)
Is -l >> 현재 폴더 리스트 + 상세 정보를 함께 보여달라 (수정날짜,유형,크기) 

 

* / 를 붙이면 최상단 폴더부터 전체경로 , / 없으면 하위폴더

 

 

sudo -i 
로그인 하기 

 

* 우선 터미널 창 키면(리눅스 서버 들어가면) sudo -i 쳐서 비밀번호 Down1oad 엔터치고 로그인 해야함

비밀번호는 쳐도 안 보인다

 

 

 

ip addr
ip 주소 확인

127.0.0.1

10.0.2.15 

확인하기



sudo nano /etc/hosts
nano 파일경로 (etc 폴더아래 hosts 파일 실행)

 

nano >> 편집기를 실행해서 파일을 편집할 수 있게 실행
ex. 메모장 열어 편집

 

 

 

 

 

편집하기

 

127.0.0.1 위쪽에 추가하기 (한 글자도 틀리면 안된다)

10.0.2.15                      vhcalnplci.dummy.nodomain           vhcalnplci 

   > IP 주소 + 도메인 이름 + 호스트 이름

 

Ctrl + X (exit)

Y

Enter

 

 

 

 

잘 편집됐는지 확인해보기

cat /etc/hosts
cat 파일경로

cat (concatenate) >> 파일 내용을 출력해서 보여달라
ex. 메모장을 열어 내용을 보여줌

 

 

 

 

 

sudo nano /etc/hostname
nano 파일경로
nano >> 편집기를 실행해서 파일을 편집할 수 있게 실행. 즉 hostname 서버이름을 설정한다

 

서버이름을 vhcalnplci 치고

나오기 Ctrl + X > Y > Enter 

 

 

  • 호스트이름을 vhcalnplci 로 설정한 이유

/etc/hosts 파일 에서 수정한 IP주소의 호스트 이름이 vhcalnplci 라고 되어있는데

IP주소와 호스트 이름을 매핑해서 IP주소 대신 호스트 이름으로도 접속가능하다. (접속 편리함)

 

 

Cat /etc/hostname 편집한거 확인해보기 
cat 파일경로

 

마지막으로 sudo reboot 하기 
여기까지 작업한거 적용하기 위해 리부트처리 한다

 

* sudo 안 쳐도 정상적으로 돌아갔음

 

 

주의사항 hosts,  hostname 정확하게 적어야한다.

 


* 에러 참고

만약에 vbox:~ 로 나온다면 다시 껐다 해보기

 

 

다시 끄고 들어가서 해보니 vbox가 아니라 vhcalnplci:~ # 이어야 함...!


 

 

 

 

zypper refresh

오픈수세에 사용되는 패키지 관리 명령어 = 오픈수세 새로고침

 

 

만약 Ask package to quit? 이 나오면 yes , Try again? 나오면 no 하고 다시 zypper refresh 친다.
이게 계속 반복되면 터미널 창을 닫았다가 다시 들어가기 

 

 

 

zypper update
오픈수세 전체 업데이트 (윈도우 보안 업데이트 같은거라고 보면된다.) 

 

설치할거냐?

 y 

 

 

 

zypper in uuidd 
uuidd 사용자 인증

 

설치할거냐?

 y 

 

 

service –status-all | grep uuidd
현재 uuidd 서비스가 실행하고 있는지 확인하는 명령어 

 

명령어를 치면 안 뜨는거를 볼 수 있다 (정상)

 

service –status-all >> 현재 실행되는 프로세스를 보여달라 (=작업관리자)
grep 문자열 >> 문자열 검색 명령어. 즉 uuidd 서비스만 검색하여 보여달라는 말

 

 

 

zypper install tcsh 
tcsh 설치

 

* 보통 tcsh 는 기본으로 설치 되어있음

 

 

rpm -qa | grep libaio
libaio 문자열 찾아서 패키지가 설치 됐는지 확인해달라는 명령어

 

설치가 되어있다.

 

 

zypper in libaio1 tcsh

마지막 설치 체크하는 용도

 

이 명령어는 안 해도 상관없지만 마지막으로 설치가 다 됐는지 확인

 

 

 

 

SAP 공유폴더 설정하기 

로컬에 저장된 SAP 압축파일을 가상머신에 바로 접근하기 위함

나가지 않고 버츄얼 박스 설정 > 공유폴더 > + 파일 아이콘 클릭

처음에 공유폴더로 만든 'sap 폴더'를 경로로 지정한다. (tip! 폴더이름 길지 않게 sap로 하는게 좋음)

자동 마운트 꼭 체크 (가상머신이 시작될때 공유폴더가 자동으로 접)

 

공유폴더 설정과 자동마운트를 통해 SAP 설치 과정이 간소화 할 수 있다.

 

sap폴더

 

 

확인

 

 

 

다시 터미널 가서 

Cd /media
최상단의 미디어 폴더로 이동

 

 

ls
/media 폴더 안에 있는 파일,폴더 모두 목록 출력

공유폴더가 잘 잡혔으면 공유폴더 이름( sf_sap )이 나온다

 

 

 

cd sf_sap
sf_sap 폴더로 이동

 

 

ls
sf_sap 폴더 안에 있는 파일,폴더 모두 목록 출력

 

 

ls -l
현재 폴더 안에 있는 목록과 상세 정보 출력 (파일권한, 수정날짜, 유형, 크기.. 등)

 

drwxrwx의 d는 폴더(directory)
rwxrwx은 파일 이라는 뜻 

r >> read 읽기 , w >> write 쓰기 , x >> excute 실행

 

 

만약 x 권한이 없다면??

Chmod +x install.sh >> install.sh에 실행권한을 준다.

Chmod 명령어 >> 권한 주는 명령어 (파일 속성 들어가면 알 수 있다.)

 

 

 

sudo systemctl status uuidd
uuidd 서비스 상태 확인

 

inactive > 시스템이 꺼져있다 

 

ctrl + c 로 빠져나와서 

 

 

 

sudo systemctl start uuidd
uuidd 서비스 시작

 

systemctl start uuidd 시작하고 다시 systemctl status uuidd 하면 active 로 바뀐걸 확인할 수 있다.

다시 ctrl + c 로 빠져나오기 

 

 

 

 

 

 

./install.sh

본격적으로 SAP 서버 설치 시작 

약관

q 눌러서 빠져나오기 

 

 

약관 동의하기

yes

 

 

 

Down1oad 패스워드 쓰고(안 보이니까 잘 치기!) 엔터 

 

그럼 설치 실행이 된다~ 

(대략 10~15분 소요)

 


 

설치 진행 중 에러

 

 

오류 분석

SAP ABAP 1 이 손상된 파일이라서??

 

압축을 풀었을때 SAP ABAP 1 에는 client 파일이 없다... 잘못된 파일이었음

 

정상적인 SAP ABAP 1 을 다운로드 하면

압축해제했을때 11까지 자동으로 풀리고 라이센스는 최신걸로 꼭 하기!

 

설정>공유파일부터 다시 진행해서 ./install.sh 하니까 설치 되는중~~~~

 


 

 

 

성공

 

여기까지 SAP서버 설치 완료!

 

 

 

설정으로 들어가기 전에 

service uuidd start
uuidd 데몬 서비스 올린다.

Service uuidd start

 

uuidd 서비스를 시작하지 않으면 SAP 로그인할 때 uuidd 비스가 실행 중이지 않다고 에러 발생시킨다.

 

 

 

 

네트워크 세팅

설정 들어가기

 

 

 

네트워크 - 포트 포워딩

 

 

 

추가 버튼 3번 누르기

 

 

 

이름 각각 수정 후 확인 버튼눌러서 네트워크 세팅하기

 

  • SSH TCP 127.0.0.1 22 10.0.2.15 22
  • RFC TCP 127.0.0.1 3300 10.0.2.15 3300
  • SAP GUI TCP 127.0.0.1 3200 10.0.2.15 3200

 

 

 

SAP GUI 설치

 

처음에 공유폴더 만든 'sap 폴더'로 가서 C:\~~\sap설치\sap\client\SAPGUI4Windows 압축 풀고 

 

 

 

 

SapGuiSetup.exe 실행한다.

 

파일위치: sap\client\SAPGUI4Windows\50144807_6\BD_NW_7.0_Presentation_7.50_Comp._2_\PRES1\GUI\WINDOWS\Win32

 

 

 

 

SapGuiSetup.exe 실행

Next

 

 

첫번째꺼 체크 한 후 Next

 

 

Next

 

 

바탕화면에 아이콘 생성됨

 

 

 

SAP GUI 세팅

마우스 우클릭 > 신규 엔트리 추가 

 

 

다음

 

 

 

 

 

어플리케이션 서버, 인스턴스 번호, 시스템 ID 적기 

  • 어플리케이션  서버 : 127.0.0.1
  • 인스턴스 번호 : 00
  • 시스템 ID : NPL

다음

다음

종료

 

 

 

 

 

NPL 더블클릭 했을때 로그인 창이 뜬다.

 

 

 

User : DEVELOPER

Password : Down1oad

 

 

초록색 체크 선택

 

 

라이센스 세팅

 

검색어에 slicense 검색하면 아래 화면이 나온다

 

Active Hardware Key 복사

 

 

 

 

라이센스 갱신하는 사이트

minisap 사이트 구글에 치거나 아래 링크 들어가기

https://go.support.sap.com/minisap/#/minisap

 

* slicense 갱신은 3개월마다한다.

 

 

NPL – SAP NetWeaver 7x(Sybase ASE) 선택 후 아래 스크롤 내려서 

 

 

 

Hardware Key에 복사한거 붙이기 

 

동의 후 Generate 하면 NPL 텍스트 파일이 생성된다.

 

 

 

이제 NPL텍스트 파일을 업로드를 해준다.

 

Install New License 클릭해서 NPL 텍스트 파일 올리기 

 

npladm

NPL.txt 파일 열기

 

 

 

권한 허용

 

 

라이센스 설치 성공

 

 


라이센스 갱신

3개월마다 S라이센스 갱신을 해야하는데 minisap 사이트에서 NPL.txt 다운받고 올리는 위 과정을 반복하면 된다.

대신 SAP시스템 넘버가 중복되면 안돼서 두번째 줄 우클릭 후 Delete하기!!

 

 


 

 

Installation Number 가 자동으로 DEMOSYSTEM 으로 변경됐으면 SE80 개발 프로그램 쓸 수 있는 상태가 된 것

 

 

 

 

 

SE80 들어가서 잘 되는지 확인해보기 

 

SAP 명령어 설명

/ose80 검색하면 새창으로 검색
/nse80 검색하면 기존 창 끄고 se80 페이지 창 뜬다

 

 

새창 클릭 또는 메인화면으로 돌아가서 se80 검색

Repository Browser > Local Objects > DEVELOPER

 

 

Programs > 마우스 우클릭 Create

 

 

우리가 만든 프로그램은 이름 맨앞에 Z, Y를 넣어야한다. (SAP 내장 된 프로그램과 구분하기 위함 )

초록색 체크 클릭

 

 

 

Type > 실행가능한 프로그램으로 하고 Save 클릭

(그대로 하면 됨)

 

 

패키지 선택하는 창

 

$TMP 은 모듈이고

임시 패키지에 사용자 이름으로 업로드

저장 디스크 버튼 클릭

 

 

프로그램 SE80 편집 테스트 

 

안경 버튼 누르면 ZTEST에 작성가능

write 'hello world'

 

 

 

1. Activate 

프로그램 테스트

 

 

추가로 선택할게 있다면 모두 선택해서 활성화 시키기 

맨아래 초록색 체크 클릭

 

 

2. Direct Processing

프로그램 사용 가능

 

실행버튼 클릭 

 

 

 

 

 

 

나중에 서버 끄고 다시 시작할때

 

버츄얼 박스 실행 후 터미널에 

sudo -i 로그인
su npladm
사용자 계정 ( npladm 계정 ) 으로 로그인

 

이 전에는 root 계정 로컬 시스템 사용자 계정으로 파일이 저장됐다면 

su npladm 명령어를 통해 npladm 계정으로 변환하여 SAP 작업이 수행된다고 보면 된다.

 

 

startsap all
서버 올라옴

 

서버가 다 올라오면 다시 SAP 로그인 가능하다.

 

 

 

SAP로그인

DEVELOPER

Down1oad

 

 

 

 

* sap 끈 후에 버츄얼 박스 끄는게 좋음

 

 

 


SAP 표현층 구조를 읽어보는걸 추천!~

 

https://sangbeomkim.tistory.com/258

 

[SAP ERP]SAP ERP의 구조

오늘 날 인터넷 및 웹 기술, 그리고 데이터베이스의 발전으로 정보처리를 위한 기술 구조를 다중계층구조(Multi-Tier)로 구성되는 것이 보편화 되었지만, SAP ERP는 클라이언트/서버 기술의 초기버전

sangbeomkim.tistory.com

 

  • 표현층 : 사용자들이 보이는 화면 (SAP GUI)

네트워크 세팅 

1. hosts, hostname 고쳐서 아이피, 호스트 이름을 매핑하는 작업

2. 네트워크 탭에서 포트포워딩 > 포트간의 통로를 연결시켜주는 작업

3. SAP GUI 에서 127.0.0.1 넣고 세팅해주는거 

 

  • 응용층 : 애플리케이션 서버(리눅스 서버 오픈수세 , SAP 서버 install.sh)

install.sh 

1. npladm 이라는 사용자계정이 만들어졌고 

2. 이 사용자 계정에 sap 서버가 설치가 됐다

3. sybase DB도 같이 설치

 

  • 데이터베이스층 : 데이터베이스 서버 (sql문으로 불러 올수있음 > install.sh 로 사이베이스 DB설치)

 

 

 

반응형
LIST

 

빈티지 카메라 웹 사이트 만들기

빈티지 카메라를 살 수 있는 웹 사이트이며 프로젝트 만드는 과정을 정리하고 기록해보자 

 

 

  • 기능
    • 회원 
      • 로그인 / 회원가입
      • 회원 조회
    • 상품
      • 상품 등록
      • 상품 수정
      • 상품 조회
      • 상품 삭제
    • 주문
      • 상품 주문
      • 장바구니
      • 주문 취소
      • 주문 조회
    • 기타
      • 상품 재고 관리를 할 수 있다. 

 

 

  • 패키지 구조

도메인 : 핵심 비즈니스 업무 영역 (컨트롤러나 폼은 핵심 업무 도메인이 아님)

 

  • 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 엔티티를 추가해서 주문 관계와는 일대다, 상품 관계와는 다대일이 된다. 
    • 왜냐하면 다대다 관계는 거의 사용하지 않기 때문!
  • 왜 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>

 

 

 

 

반응형
LIST

 

h2 연동 에러들

 

에러문구 

Caused by: java.lang.RuntimeException: Driver com.mysql.cj.jdbc.Driver claims to not accept jdbcUrl,

 

 

해결

application.yml 또는 .properties 에 분명 오타 또는 띄어쓰기 잘못된거 있으니까 잘 보면 된다..

 

 

정말 삽질을 오래했는데 알고봤더니 jdbc 에서 j가 빠져있었다.. 

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/vintage-camera-shop

 

 

 

 

에러문구 : h2 DB가 제대로 연결이 안됐다?

java.lang.ClassNotFoundException: org.h2.Driver

 

 

해결

 build.gradle 에 runtimeOnly 'com.h2database:h2'  추가하면 된다!

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    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')
    
     runtimeOnly 'com.h2database:h2'
}
반응형
LIST

 

 

상품등록 후 새로고침하면 중복등록이 되는 문제가 발생

 

 

[실제 상품을 등록한 뷰 화면]

 

위처럼 상품 등록한 상태에서 새로고침을 누른다면 상품ID는 계속 올라가고 상품이 계속 등록되는 이슈가 생긴다.

 

 

 

[상품등록 POST - BasicItemController ]

@PostMapping("/add")
public String addItemV4(Item item){
    itemRepository.save(item);
    return "basic/item";
}

 

  • 전체 흐름
    • 실제 상품등록
    • 상품 상세 뷰로 뷰템플릿(basic/item)을 호출 - 끝
    • 상품 등록 (POST / add) URL 이 유지되어있는 상태
    • 새로고침하면 계속 POST / add 행위가 지속된다.
    • 새로고침 시 마지막 행위가 다시 요청되기 때문에 상품이 중복저장이 되는 이슈가 발생한다. 
  •  

 

 

 

 

 

문제 해결 방법은 리다이렉트! 

 

 

POST ▶ Redirect ▶ GET

실제 상품을 등록하고 뷰 템플릿으로 호출하는게 아니라 상품 상세화면으로 리다이렉트를 하면된다.

그렇게 하면 상품 등록 후 상품 상세 화면으로 다시 이동해서 (리다이렉트) 마지막에 호출한 GET /items/{id} 이 된다.

 

  • 전체 흐름 
    • 실제 상품등록 POST/ add
    • 상품 상세 화면으로 리다이렉트 Redirect items/{itemId} 호출 http://localhost:8080/basic/items/3
    • 웹 브라우저가 상품 상세를 새로 요청 GET /items/{id} 
    • 새로고침을 해도 GET /items/{id} 상품 상세 화면으로 이동
    • 문제 해결 

 

 

 

[상품등록 POST redirect - BasicItemController ]

@PostMapping("/add")
public String addItemV5(Item item) {
    itemRepository.save(item);
    return "redirect:/basic/items/" + item.getId();
}

 

※ 주의

+item.getId() 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험

 

 

 

 

RedirectAttributes

RedirectAttributes 을 사용하여 URL 인코딩 문제 해결과 상품이 실제로 등록이 됐을때 "저장되었습니다." 메세지가 나오도록 해보자 

@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes){
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/basic/items/{itemId}";
}

 

RedirectAttributes 는 리다이렉트할 때 모델에 데이터를 전달할 수 있고 파라미터에 itemIdstatus 를 붙여주는 역할이다. 

  • basic/items/1?status=true

 

 

 

이제 저장완료 메세지를 만들어보자

 

리다이렉트  redirect:/basic/items/{itemId}  ▶ 최종 basic/item 뷰 템플릿을 호출한다. 

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {

    private final ItemRepository itemRepository;

    //상품 상세
    @GetMapping("/{itemId}")
    public String item(@PathVariable Long itemId, Model model){
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "basic/item";
    }
    
    ....

 

 

 

 

뷰템플릿

resources/templates/basic/item.html 

상품 상세

<div class="container">
    <div class="py-5 text-center">
        <h2>상품 상세</h2>
    </div>

    <!-- -->
    <h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
    
    ...
  • th:if 해당 조건이 참일 때
  • ${param.status} : 파라미터 값을 조회할 수 있는 기
  • 참이면 '저장완료' 가 나온다.

 

 

 

상품 실제 등록시 '저장 완료' 메세지를 확인할 수 있다. 

반응형
LIST

 

상품수정은 상품등록처럼 같은 url (/{itemId}/edit)이다 

GET - 상품 수정 폼 

POST - 상품 수정 처리

 

 

 

컨트롤러 - 상품 수정 [BasicItemController] 에 추가 

 

  • 상품 수정 페이지를 열때는 GET (실제 수정 X)
//상품 수정
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model){

	Item item = itemRepository.findById(itemId);
	model.addAttribute("item", item);
	
	return "basic/editForm";
}

 

 

 

 

뷰 템플릿 [edit.html]

/resources/templates/basic/editForm.html

 

  • 타임리프 설정
<html xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.min.css}"
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
            href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
        max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 수정 폼</h2>
    </div>
    <form action="item.html" th:action method="post">
        <div>
            <label for="id">상품 ID</label>
            <input type="text" id="id" name="id" class="form-control" value="1"
                   readonly>
        </div>
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" value="상품A" >
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control"
                   value="10000" th:value="${item.price}">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" value="10">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">저장</button>
            </div>
            <!--수정폼에서 취소 눌렀을때 상품상세 페이지로-->
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='item.html'"
                        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
                        type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 

 

<form action="item.html" th:action method="post">

상품수정은 상품등록처럼 유사하게 GET,POST URL이 같기 때문에 th:action 에 생략해도 괜찮다

 

 

 

상품 수정화면

그러나 '상품 수정' 버튼을 눌러서 상품 수정 폼에 들어가면 상품명이 안 바뀌어있다  ( itemA ▶ 상품A )

 

 

폼 input에 각 상품명, 가격, 수량에 th:value 값을 넣으면 된다.

  • th:value="${item.id}"
  • th:value="${item.itemName}"
  • th:value="${item.quantity}"

 

변경 되는 상품수정 화면 ( itemA itemA )

 

 

 

 

취소 버튼 눌렀을때 다시 수정 폼으로 돌아가기 

th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})
<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='item.html'"
        th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
        type="button">취소
</button>

 

 

 

 

 

컨트롤러 - 상품 수정 [BasicItemController] 에 추가 

  • 상품을 실제로 수정할 때는 POST
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId,@ModelAttribute Item item){

    itemRepository.update(itemId, item);
    //수정저장 후 redirect
    return "redirect:/basic/items/{itemId}";
}

 

페이지 이동 : 상품을 수정하고 저장하면 상품 상세 페이지로 이동하도록 한다.

 

 

 

 

리다이렉트 redirect 

상품 수정 마지막에 뷰 템플릿을 호출하는게 아니라 상품 상세 화면으로 이동하는 리다이렉트를 호출한다. 

  • redirect:/basic/items/{itemId}basic/items/1

 

 

 

상품 수정했을때 개발자 모드를 보면 

  • Location : http://localhost:8080/basic/items/1 로 리다이렉트를 한 뒤 
  • itemId가 1인 항목을 불러온다. 즉, 상품 상세 컨트롤러를 다시 호출한다.

 

 

 

 

반응형
LIST

 

 

컨트롤러 - 상품 등록 [BasicItemController] 에 추가 

 

  • 상품 등록 페이지를 열때는 GET (실제 등록 X)
//상품 등록 
@GetMapping("/add")
public String addForm(){
    return "basic/addForm";
}

 

  • 상품을 실제로 등록할 때는 POST
@PostMapping("/add")
public String save(){
    return "basic/addForm";
}

 

 

같은 URL인데 HTTP 메서드로 기능을 구별해준다. 

 

 

 

 

뷰 템플릿 [addForm.html]

/resources/templates/basic/addForm.html

 

  • 타임리프 설정
<html xmlns:th="http://www.thymeleaf.org">
<link th:href="@{/css/bootstrap.min.css}"
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
            href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
        max-width: 560px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2>상품 등록 폼</h2>
    </div>
    <h4 class="mb-3">상품 입력</h4>
    <form action="item.html" th:action method="post">
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">
        </div>
        <div>
            <label for="price">가격</label>
            <input type="text" id="price" name="price" class="form-control"
                   placeholder="가격을 입력하세요">
        </div>
        <div>
            <label for="quantity">수량</label>
            <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">
        </div>
        <hr class="my-4">
        <div class="row">
            <div class="col">
                <button class="w-100 btn btn-primary btn-lg" type="submit">상품 등
                    록</button>
            </div>
            <div class="col">
                <button class="w-100 btn btn-secondary btn-lg"
                        onclick="location.href='items.html'"
                        th:onclick="|location.href='@{/basic/items}'|"
                        type="button">취소</button>
            </div>
        </div>
    </form>
</div> <!-- /container -->
</body>
</html>

 

 

 

 

타임리프 속성

th:action

 

상품 등록 버튼을 눌렀을때 동적으로 URL이 변경되도록 하는 액션속성

 

  • 사실상 GET, POST 같은 URL (basic/items/add) 이기 때문에 안 써도 된다.
    • th:action="/basic/items/add"  ▶ <form action="item.html" th:action method="post">
  • 등록 페이지에 갔을때 '페이지 소스보기' 를 하면 action="" 비어있는 것을 확인할 수 있다. 

 

 

 

 

 

취소 버튼 눌렀을때 url 수정하기 

취소했을때는 목록으로 돌아가기 

th:onclick="|location.href='@{/basic/items}'|"
<button class="w-100 btn btn-secondary btn-lg"
        onclick="location.href='items.html'"
        th:onclick="|location.href='@{/basic/items}'|"
        type="button">취소
</button>

 

 

 


 

이제 실제로 상품을 등록해보자 

 

 

상품 등록 POST - @ModelAttribute

 

우선 @ModelAttribute 와 @RequestParam 차이점을 알아보자

 

 

@RequestParam

@RequestParam - 상품 등록 처리

 

클라이언트의 HTTP POST 요청파라미터를 처리하기 위해 @RequestParam 을 사용한다 

 

[addItemV1 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
                        @RequestParam int price,
                        @RequestParam Integer quantity,
                        Model model) {
                        
	Item item = new Item();
	item.setItemName(itemName);
	item.setPrice(price);
	item.setQuantity(quantity);

	itemRepository.save(item);

	model.addAttribute("item", item);
    
	return "";
}

 

 

@RequestParam String itemName 은 addForm.html 의 name="" 값이다. 

  • <input type="text" id="itemName" name="itemName" ~ >

 

 

 

실제 상품을 등록한 뷰 화면

 

 

 

@ModelAttribute 

@ModelAttribute - 상품 등록 처리

 

[addItemV2 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item ) {
	
	itemRepository.save(item);
	//model.addAttribute("item",item);
    
	return "basic/item";
}
  • @ModelAttribute - 요청 파라미터 처리를 해준다. 
    • Item 객체를 생성하고 요청 파라미터 값을 setXxxx 으로 Item 객체의 필드에 값을 설정해준다.
    •  Spring이 자동으로 처리해줌 개발자가 직접 setXxxx 메서드를 호출할 필요가 없어진다. 
    • Item item = new Item();
      item.setItemName(itemName);
      item.setPrice(price);
      item.setQuantity(quantity);
  • @ModelAttribute 은 자동으로 model에 뷰를 넣어준다.
    • @ModelAttribute("item") 이름이 ▶ model.addAttribute("item", item) 이다. 
    • model.addAttribute("item", item) 가 주석처리를 해도 잘 동작한다. 

 

 

 

ModelAttribute 이름 생략

 

[addItemV3 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
	
	itemRepository.save(item);
	//model.addAttribute("item",item);
    
	return "basic/item";
}
  • @ModelAttribute("item") Item item 이름을 생략하면 클래스의 첫글자만 소문자로 변경해서 등록한다. 
  • 클래스명 Item item

 

 

 

ModelAttribute 전체 생략

 

[addItemV4 - BasicItemController에 추가]

@PostMapping("/add")
public String addItemV4(Item item) {
	
	itemRepository.save(item);
    
	return "basic/item";
}
  •  addItemV1 처럼 단순타입인 String이라면 @RequestParam 이 자동적용되고 
  • 객체 타입인 경우 @ ModelAttribute가 자동적용이 된다. 
  • 이것 또한 클래스명 Item  item 소문자로 변경해서 모델에 담긴다. 

 

 

 

반응형
LIST

+ Recent posts