프로젝트 Backend

peter park·2019년 8월 21일
0

토이프로젝트

목록 보기
3/5

환경설정

profile 설정

spring:
    profiles:
        active: develop
---
spring:
    profiles: develop
---
spring:
    profiles: product    

커맨드 라인 아규먼트의 우선순위(4등)보다 JAR 안에 있는 application.properties의 우선순위(15등) 훨씬 높아서 나중에 커맨드라인으로 환경변수들을 관리할 수 있다.

로그 설정

logging:
    path: develop-logs
    level:
      com.test.editx: debug
  • loggin.path = develop-logs
    • 로그가 저장되는 경로.
    • develop-logs라는 파일 이름으로 로그 정보 저장

WebController.java

private Logger logger = LoggerFactory.getLogger(WebController.class);
logger.debug(String.valueOf(logginedUser));

원하는 클래스(WebController)에서 추가

데이터베이스 쿼리 보기

spring:
	jpa:
        hibernate:
            ddl-auto: update
        properties:
            hibernate:
                format_sql: true
        show-sql: true

ddl-auto : 어떤 방식으로 데이터베이스를 작동시킬 것인가

  • update : 데이터를 삭제하지 않고 계속 쌓아나감

JAVA_HOME : 자바 어플리케이션에 JDK가 어디있는지 알려줌
PATH : 자바 어플리케이션에게 실행가능한 정보들이 어디 있는지 알려줌
Class_Path : 자바 어플리케이션에게 Library가 어디있는지 알려줌

Intellij

스프링 부트

H2 데이터베이스 추가

https://mvnrepository.com/artifact/com.h2database/h2/1.4.199

<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.199</version>
    <scope>test</scope>
</dependency>

아래의 주소로 h2 데이터베이스 접속

localhost:8080/h2-console

Spring DATA JPA

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

User라는 클래스를 만든 후 다음과 같이 정의한다.

다시 h2-conosle로 가게되면 User라는 테이블이 생성됨을 알 수 있다.
하지만 h2는 메모리 데이터베이스이므로 서버를 재시작할 때마다 해당 데이터가 초기화 된다. 이는 스프링 부트가 jpa 설정할 때 데이터베이스 테이블을 drop하고 테이블을 다시 create하는 형식으로 돌아간다.

URL 설계

/users/joinGetMapping을 하는대신 Post Mapping으로 /users라는 요청만 받으면 유저의 정보를 데이터베이스에 입력한다는 뜻으로 알 수 있다.

/users라는 url이 반복이 되므로 class 이름 바로 위에 아래와 같은 어노테이션을 추가하면 다른 요청들을 공백으로 놓을 수 있다

@RequestMapping("/users")

만약 아래와 같은 에러가 나는 경우 해결방안에 대해 찾아본다

No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.editx.demo01.domain.User$HibernateProxy$HnIIiRr6["hibernateLazyInitializer"])

간단하게 해석해 보자면 생성한 모델에 무엇인가 Jackson에 의해 serialize 되지 못하게 만드는 무엇인가가 있는 것 같다. 이 문제를 해결하기 위해 스프링은 SerializationFeature.FAIL_ON_EMPTY_BEANS을 disable하는 것을 추천하는 것 같다.

일단 키워드를 찾아본다면 Serialize이다. 여기를 참조하면 JSON에서 아래와 같이 변화하는 과정이라고 볼 수 있다.

  • Serialize : JSON Object -> String
  • Deserialize : String -> JSON Object
    데이터를 저장할 때 byte 형식으로 저장이 되는데 JSON Object 형식은 데이터를 저장할 수 없다. 그래서 Serialize 하여 byte String 형식으로 데이터를 받아 저장할 수 있게 된다.

조금 더 자세히 보자
Post 요청을 받으면 내부적으로 스프링 MVC는 HTTP 요청을 오브젝트로 혹은 반대로 바꾸는 HttpMessageConverter라는 컴포넌트를 사용하게 된다.

만약에 이것을 바꾸고 싶으면 스프링 부트에서 간단하게 할 수 있다. 만약 POST 메소드가 조금 더 유연해지고 Hotel이라는 엔티티에 있지 않은 프로퍼티를 무시해야한다면, Jackson의 Object Mapper의 설정에 의해 가능하다. 스프링부트에서는 새로운 HttpMessageConverter 빈을 생성하고, 이것은 결국 기본 message converter를 override하게 된다. 오류 메세지에서 처럼 SerializationFeature.FAIL_ON_EMPTY_BEANS 를 disable 해주기 위해 converter를 기본 설정을 바꿔주면 된다.

아래 코드를 @configuration하면 정상적으로 출력이 되는 것을 볼 수 있다.

public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {  
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    MappingJackson2HttpMessageConverter converter =
                new MappingJackson2HttpMessageConverter(mapper);
    return converter;
} 

하지만 아래 그림과 같이 hibernateLazyInitializer라는 쓸데없이 친절한 프로퍼티가 따라 오게 된다.

즉, 오류메세지에서 말하는 것이 hibernateLazyInitializer라는 쓸데없이 착한 친구가 어찌하다 보니 자식 객체처럼 들어오게 되었는데 (User 객체에는 hibernateLazyInitializer 프로퍼티가 없으니) 이 녀석 때문에 lazy loading이 불가능하여 serialize하지 못하는 이유가 된다고 생각되었다.

그리고 조금 더 조사해본 결과 User 객체를 생성할 때 @JsonIgnoreProperty 어노테이션으로 convert 과정에서 우리가 필요하지 않은 hibernateLazyInitializer를 무시하여 우리가 원하는 User 객체만 lazy loading 할 수 있도록 만듦으로서 문제를 해결할 수 있었다.

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})

User 클래스 위에 위와 같은 어노테이션만 추가한다면 작동작하는 것을 알 수 있다. (앞에서 작성한 mappingJackson2HttpMessageConverter() 메소드는 지워도 잘 작동한다.)

Hibernate lazy loading
하이버네이트가 부모 객체를 부르면 자식 객체가 자동적으로 딸려오는데 lazy loading은 자식을 빼놓고 부모만을 부르게 하는 기능

JsonIgnoreProperties
@JsonIgnoreProperties 는 JSON serialization, deserialization하는 특정한 프로퍼티들을 무시할 수 있다. 이 어노테이션은 class 레벨이며 JSON 객체안의 여러 프로퍼티들 중 필요없는 것들을 무시 즉, 표시하지 않게 만든다.

github

.gitignore

CentOS7

adduser

adduser로 리눅스 계정을 추가하여 모든 서버 내용을 관리.

adduser username
passwd username

유저의 권한을 sudo 권한으로 바꾸는 방법

# cd /etc/sudoers

root권한을 복사하여 root 대신 새로운 유저의 이름으로 바꿔줌

계정별 UTF-8 인코딩 설정

https://crowsaint.tistory.com/140

vi ~/.bash_profile
export LANG="ko_KO.utf8"
source ~/.bash_profile

Locales(i18n)
Locales는 프로그램과 shell session을 위한 구체적인 언어와 국가에 대한 세팅이다. 해당 국가에 대한 날짜, 시간, 숫자, 화폐단위 등에 대한 LInux와 Unix기반 시스템의 locales를 사용할 수 있다. 시스템의 locales를 세팅하기 위해서는 shell 변수들을 사용해야한다. 예를들어 LANG 변수는 ko_KO(한국어)를 세팅하기 위해 사용된다.

CentOS7부터는 i18n을 사용하지 않는다. 대신 /etc/locale.conf에서 설정이 가능하다. 참조

어떻게 현재 locale 설정을 보는가

locale
locale name

locale 커맨드는 현재 locale 혹은 스크린의 모든 locale에 대한 정보를 표시한다.

locale 커맨드의 예

locale

Linux와 Unix계열 시스템의 모든 locales 보기

locale -a

다음과 같은 코드로 Debian이나 Ubuntu 기반의 시스템의 locale정보를 볼 수 있다.

less /usr/share/i18n/SUPPORTED

Centos는

ls /usr/share/i18n/locales

Locale 카테고리 섹션

POSIX에 의해 다음과 같은 카테고리 색션이 정의된다.

LC_CTYPE
LC_COLLATE
LC_MESSAGES
LC_MONETARY
LC_NUMERIC
LC_TIME

예를들어

locale -c -k LC_NUMERIC

  • -c
    • 카테고리 이름
  • -k
    • 키워드 이름

메뉴얼 페이지를 보고 싶다면 다음과 같은 커맨드를 사용하자

man 5 locale

"Fedora LInux v22"와 "CentOS/RHEL/Scientific LInux 7.x"와 그 이상의 유저를 위한 글로벌 locale 보기와 세팅

유저의 현재 loclae을 보기위해 다음과 같은 커맨드를 사용한다.

cat /etc/locale.conf

결과는 다음과 같다

또한 다음과 같은 코드도 사용할 수 있다.

localectl status

결과는 다음과 같다.


모든 가능한 locale을 보고싶으면

localectl list-locales

모든 유저에 대해 기본 글로벌 시스템 locale을 지정하고 싶으면, 다음 커맨드를 root 권한에서 사용하자

sudo localectl set-locale LANG=localeValueHere
sudo localectl set-locale LANG=en_IN.UTF-8

특정 유저에 대해 글로벌 locale을 적용하고 싶다면?
간단하게 HOME/.bash_profile bash shell profile 파일을 설정하면 된다.

vi ~/.bash_profile

다음과 같이 편집하면 된다.

LANG="ko_KO.utf8"
export LANG

혹은

export LANG="ko_KO.utf8"

혹은 새로운 .i18n 파일을 HOME 디렉토리에 추가해준다.

LC_ALL
시스템 상에 있는 기본 언어 설정을 바꿔줌

JDK 1.8 설치

sudo yum install -y java-1.8.0-openjdk-devel

JAVA_HOME 설정

리눅스 내에 jdk 소프트웨어가 설치 된 위치

cd /usr/lib/jvm/jre-1.8.0-openjdk

jdk로 가는 심볼릭 링크를 메인 디렉토리에 설정

ln -s /usr/lib/jvm/jre-1.8.0-openjdk java

JAVA_HOME 및 PATH 수정

vi ~/.bash_profile
JAVA_HOME=/home/username/java
PATH=$PATH:$JAVA_HOME/bin

https://www.cyberciti.biz/faq/linux-unix-set-java_home-path-variable/

~./bash_profile은 컴퓨터가 시작할 때 한번만 실행되는 스크립트이다. 일반 유저가 접속했을 때 실행시키기 위한 커맨드이다. 보통은 PATH.JAVA_HOME, shell 커멘드의 별칭을 생성하고 새롭게 생성된 파일들의 기본 허가를 설정 등의 환경변수를 설정하기 위한 스크립트이다.

  • JAVA_HOME
    export JAVA_HOME=<path-to-java>의 형식을 사용하여 설정한다.
export JAVA_HOME=/home/username/java/
  • PATH
export PATH=$PATH:$JAVA_HOME/bin

설정을 완료하시면 source ~/.bash_profile을 실행하여 재부팅 없이 설정된 내용을 적용시킬 수 있다. ./bash_profile에 대해 궁금하다면 이 글을 참고하자.

/etc/profile이 컴퓨터가 시작할 떄 전체 설정이라면 ~/.bash_profile은 개별 유저의 환경설정파일이라고 생각하면 편하다

환경설정을 끝냈다면 다음의 명령어로 수정된 내용을 확인하자

echo $JAVA_HOME
echo $PATH

JAVA_HOME에 들어가야되는 것을 뭘까?

이후 위의 설정을 변경

source ~/.bash_profile

java 빌드

./mvwn clean package

java 실행

java -jar 빌드한 파일

하지만 이 경우 foreground로 실행되어 터미널에서 나오게되면 이 프로세스는 종료가 된다. 그래서 background로 프로세스를 돌리기 위해서는 위 명령어에 &를 추가하면 터미널을 나와도 잘 돌아간다.

java -jar 빌드한 파일 &

방화벽 추가

sudo yum install -y firewalld
sudo startctl start firewalld
sudo startctl enable firewalld
sudo firewall-cmd --permanent --add-port=8080/tcp

AWS에서 서비스되는 EC2 인스턴스는 firewalld가 적용되지 않는 것 같다.

실행중인 프로세스의 포트번호 보기

netstat -ntlp

프로세스 죽이기

kill -9 PID

데이터 관리

List로 데이터를 관리했을 경우, 이 데이터는 메모리에 저장되기 때문에 서버를 종료시키면 날라가게 된다. 그래서 하드디스크 형식인 비휘발성 메모리에 데이터를 저장해야 함. 하드디스크에 계속 넣는게 불편하므로 데이터베이스를 사용한다. 메모리는 하드디스크보다 빠르므로 잠깐 저장했다가 하드디스크 혹은 데이터베이스에 저장하는 방법도 좋음.

git pull

깃헙 원격 리포지토리에서 코드를 가져올 때는 git clone를 했지만 기존의 코드 위에 원격서버의 코드를 받아오려면 git pull 명령어로 충분하다

재빌드

./mvnw clean package
  • clean
    • target 디렉토리 날림
  • package
    • 컴파일, 테스트, 패키징 작업을 다함

기존의 자바 프로세스 종료

ps -ef | grep java

https://linuxhint.com/ps_command_linux-2/

kill -9 PID

다시 돌리기

로컬의 코드를 소스트리를 통해서 최신 커밋에서 원하는 커밋지점에 마우스 오른쪽 클릭하여 master를 이 커밋으로 초기화 버튼을 누른 뒤 hard 모드로 설정하면 커밋지점을 뒤로 돌릴 수 있다. 이런 경우 원격의 코드는 최신 코드 이지만 로컬의 코드는 몇단계 뒤의 코드가 된다.

Intellij에서 프로젝트를 refresh하면 이전의 코드로 돌아가게 된다.

이런 경우 push를 해도 github과 소스트리는 기본적으로 push를 못하게 막아 놓았다. 하지만 터미널에서 git push --force라는 명령어를 사용하면 강제로 원격의 최신코드에 로컬의 코드를 덮어 씌울 수 있다.

github에서 확인을 해보면 커밋의 개수가 줄어든 것을 볼 수 있다. 초기화 하기 이전으로 가고 싶으면 최신의 커밋지점으로 마우스 오른쪽 클릭하여 master를 이 커밋으로 초기화를 한번 더 누르면 이전 코드로 돌아가게 된다.

github에서 다시 원래의 커밋 개수로 돌아가면서 복구가 가능하다.

다른 브랜치를 clone 후 마스터 브랜치로 pull하는 방법

git pull origin master

ssh 접속 설정

sudo vi /etc/hosts
111.111.111.111 editx

이러면 아이피 대신 host이름으로 접속가능하다.

sudo ssh -i .pem위치 ec2-user@editx

하지만 지금은 가변아이피이므로 참조만하자

jar파일 압축없이 테스트, 빌드, 실행을 한번에

./mvnw spring-boot:run &

VIM

참고글
https://nolboo.kim/blog/2016/11/15/vim-for-beginner/
http://www.vimgolf.com/challenges/5cf62aa56c09760009d6b2f3
https://blog.outsider.ne.kr/540

테스트용 데이터 입력

data.sql

위 파일을 resource 폴더에 넣어두면 스프링 부트 실행시 쿼리문이 자동 실행

INSERT INTO USER (USER_ID, PASSWORD, NAME, EMAIL) VALUES('111','111','111','111@gmail.com');

하지만 이렇게 입력하면 에러가 뜨면서 동작하지 않는다.

NULL not allowed for column "ID"; SQL statement:

해석하자면 ID 컬럼에는 NULL이 허용되지 않는다. ID는 @Id 어노테이션이 적용되어 Primary Key가 되기 때문이다.

GenerationType

  • Auto
    • 기본 generation 타입.
  • Identity
    • auto-increment가 되는 데이터베이스에 의존
    • 매 insert 마다 새로운 value를 데이터에게 생성하게 한다.
    • auto-increment는 매우 옵티마이즈가 잘 되있어 데이터베이스에 좋다

User 클래스에 @GeneratedValue(strategy = GenerationType.IDENTITY)를 추가하면 ID에 특정한 값을 insert 하지 않아도 자동적으로 증가하므로 오류가 발생하지 않는다.

비밀번호 비교

최대한 데이터베이스에서 계속 값을 꺼내 올 것이 아니라 객체에서 해결하려고 노력

이처럼 입력된 비밀번호와 기존에 객체에서 가지고 있던 비밀번호를 비교함으로서 일일이 데이터베이스를 왔다갔다 할 필요가 없어짐

sql문 보여주기

spring.jpa.show-sql=true

출력되는 쿼리문 형식을 예쁘게 하려면

spring.jpa.properties.hibernate.format_sql=true

War 파일로 배포

html, css, 이미지 등과 같이 정적 데이터들은 jar 형식으로 배포했을 때 보여지지 않으므로 웹형식인 war파일로 배포하는 것이 좋다. 이 때 스프링부트에 내장되어 있는 톰캣을 사용하지 않고 외부에서 다운받은 톰캣에 war파일을 올려서 배포해야한다. 하지만 REST API를 만드는 것이기 때문에 계속 jar 파일로 배포할 수 있도록 한다

Docker

이미지 만들기

자바가 설치되어 있는 이미지를 받아서 설치할 수 있음
아니면 Dockerfile에서 자바를 다운받고

docker hub에서 원하는 이미지를 검색할 수도 있고 다음과 같은 커맨트로 터미널에서 검색이 가능하다

docker search java8

Centos에 Java JDK 수동 설치

https://www.digitalocean.com/community/tutorials/how-to-install-java-on-centos-and-fedora

먼저 자바 jdk를 설치하자

sudo yum install java-1.8.0-openjdk-devel

만약 위의 명령어 대신 sudo yum install java-1.8.0-openjdk를 썼다면 그냥 JRE만 설치한 것이다. JDK는 compiler까지 포함하기 때문에 JDK가 없다면 java 파일은 빌드 되지 않는다.

자바가 잘 설치되었는지 확인해보자

java -version 

이렇게 설정하고 빌드를 하려고 하면 JAVA_HOME의 경로를 찾을 수 없다고 나온다. 아래 명령어를 실행하면 버전별로 java가 있는 원래 경로를 보여주게 된다.

sudo alternatives --config java

alternatives는 alternatives system을 구성하는 심볼릭 링크에 대한 정보를 생성, 삭제, 관리한다.
alternative는 파일시스템의 구체적인 파일 이름을 말한다.
generic name은 /usr/bin/editor처럼 비슷한 역할을 하는 수많은 파일 등 중의 하나의 이름.
symbolic link는 바로가기 정도로 생각하면 편하다.
--config 옵션은 옵션뒤에 오는 이름의 마스터 링크의 링크 그룹의 모든 선택권을 보여준다. 예를들어 --config java라고 하면 java의 버전은 다양한데 버전 별로 모든 원래 파일의 경로를 보여주는 식이다.

vi ~/.bash_profile

위의 명령어를 실행하고 앞에서 찾은 자바 경로에서 bin/java를 제외한 경로를 JAVA_HOME= 에 붙혀주면 된다.

JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre/

설정을 바꾸었으니 설정이 사용할 수 있게하자

source ~/.bash_profile

locale 설정

locale에 언어추가
docker에서 아무리 설정을 해도 안먹힌다. docker에서는 기본적으로 언어 설정이 영어로 되어있다.

locale -a

위의 명령어를 실행하면 나오는 결과가 영어로 되어있다.

# vi /etc/yum.conf

GNU
GNU's Not Unix의 약자로 약자 안에 GNU라는 약자가 들어있어 Recurive acronym(반복 약자)이라고 불린다. 즉 GNU를 찾아보니 GNU's Not Unix라는 결과가 나오고 그 안에 또 GNU가 있으므로 GNU의 약자를 또 찾아보게 되는 반복 약자라고 할 수있다.

이는 yum.conf 파일안에 설정에서 override_install_langs=en_US.utf8 매 설치마다 영어로 기본적으로 설정이 되게 되어있다. 그러므로 이를 주석처리 한 후(#)

yum reinstall glibc-common

다음 명령어를 실행하고 locale -a | grep ko을 실행한다면 locale에서 한국에 관한 설정을 할 수 있음을 알게 된다.

Dockerfile 만들기

CMD vs ENTRYPOINT vs RUN

pop8682/editx-server:0.1

FROM centos:latest
MAINTAINER Peter Park <bgpark82@gmail.com>

RUN yum update
RUN yum install -y java-1.8.0-openjdk-devel git
RUN rm -rf editx-test
RUN git clone https://github.com/pop8682/editx-test.git

ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64/jre/
ENV LANG ko_KR.UTF-8
ENV LC_ALL ko_KR.UTF-8

RUN sed -i '/^override_install_langs=/d' /etc/yum.conf
RUN yum reinstall -y glibc-common

WORKDIR editx-test
COPY deploy.sh .
RUN ./mvnw clean package

WORKDIR /editx-test/target

ENTRYPOINT ["java","-jar","editx-0.0.1-SNAPSHOT.jar"]

EXPOSE 8080

CMD /bin/bash

springboot with docker
JAVA_HOME path in Dockerfile

  • RUN : image를 빌드할 댸 실행되어야 할 커맨드
  • ENTRYPOINT : container를 실행할 때 반드시 실행되어야 할 커맨드
  • CMD : container를 실행할 때 부가적으로 실행되어야 할 커맨드. 아규먼트를 넣어 바꿔 사용할 수 있다.

업데이트 RHEL7.3
locale 설정

Since glibc-common-2.17-157.el7.x86_64, reinstalls no-longer seem to regenerate 
/usr/lib/locale/locale-archive and locale -a only lists C POSIX en_US en_US.iso88591 en_US.iso885915 en_US.utf8
sed -i '/^override_install_langs=/d' /etc/yum.conf

한마디로 말하면 /etc/yum.conf 파일 안에 있는 overrid_install_langs= 라는 부분을 찾아서 삭제(d)해라 라는 뜻이다. 즉 우리가 필요없는 override_install_langs 옵션이 있는 부분을 삭제하라는 뜻이다.

sed
검색, 대체, 삽입, 제거 등의 스트림 에디터. 주로 단어를 대체하거나 바꿀 때. 파일을 열지 않고 수정 가능하다.

SED is a powerful text stream editor. Can do insertion, deletion, search and replace(substitution).

  • s : substitution
  • / : delimiters
  • d : delete
  • ^ : 정확히 일치되는 단어
docker run -dit -p 8080:8080 editx 

JPA 관계 설정

두개의 객체가 있을 때 각각의 아이디를 연결시킬 것이 아니라 하나의 객체를 다른 하나의 객체에 넣어줌으로서 관계를 맺어줄 수 있다. 아이디로 관계를 맺으면 매번 데이터베이스를 왔다갔다해야하는 낭비를 해버린다

유저 객체는 최상위 객체. 왠만하면 다른 객체와 관계를 맺지 않으면 좋다. content에서 user를 바라보도록 관계를 맺을 것임. getID으로 어떤 요소를 가져오는 것 보다 객체를 가져오도록 코드를 짜는 것이 좋다.

Content.java

    @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_content_writer"))
    private User user;

여러개의 Content(Many)가 하나의 User(One)에 의해 생성됨
추후에 content.setUser 이런식으로 넣어줘도 괜찮

REST API에서는 session을 사용할 수가 없다. REST API는 기본적으로 stateless이기 때문
state vs stateless

  • Stateful : session 정보를 저장할 수 있는 서버
  • Stateless : 서버는 어느 state도 저장 하지 않음. 각각의 요청은 유저를 구분하기 위해 나름의 유저 정보를 식별하는 방법을 만들어야 함. 예를들어 매 요청의 header에 토큰

Redis usecase

User.java

public boolean isSameWriter(User loginUser){
        return this.userId.equals(loginUser);
    }

    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Content content = (Content) o;
        return Objects.equals(userId, content.userId);
    }

loginUser와 user객체는 많은 것을 비교해야한다. 그래서 그냥 equals 메소드를 쓴다면 같지 않으므로 false를 반환한다. 그래서 클래스에서 equals 메소드를 생성하여 객체 내에서 어느 인자를 비교할 것인지 정해줘야한다.

LocalDateTime을 JPA의 Date 형식으로 입력하기

@Converter(autoApply = true)
public class DateTimeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
    @Override
    public Timestamp convertToDatabaseColumn(LocalDateTime localDateTime) {
        return localDateTime != null ? Timestamp.valueOf(localDateTime) : null;
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Timestamp timestamp) {
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }
}

String은 데이터 컬럼에서 255자, 그래서 내용이 긴 녀석들은 @Lob 어노테이션을 추가하여 많은 내용을 넣을 수 있다.

@Lob
private String content

매번 컨텐츠의 댓글을 불러올 때 answerRepository.findByContentId를 통해 데이터를 가져온다면 자원의 낭비가 생길 것이다. 대신 Content 객체와 Answer객체 모두 매핑을 시켜줌으로서 각각이 서로의 객체들을 가지게 한다면 편할 것이다.
Content.java

@OneToMany(mappedBy = "content")
    @OrderBy("id ASC")
    private List<Answer> answer;
  • mapptedBy : 상대(answer)에서 지정한 컬럼의 이름
  • OderBy : 답변의 id를 기준으로 오름차순

Answer.java

 @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name="fk_answer_question"))
    private Content content;

Optional

공식문서
어떤 값이 존재하지 않을 수 있을 경우 사용. null 예외를 감소시키고자 사용. value기반의 클래스

  • 값이 존재시
    isPresent() : return true
    get() : return 객체 (존재하지 않을 시 NoSuchElementException 출력)
    orElse() : return default value (값이 존재하지 않을 때)
    ifPresent() : 코드 실행 (값이 존재할 때)
    Option에서 객체 반환받는 3가지 방법

Optional과 JPA
Optional은 Serializable 인터페이스를 implement받지 않는다. 이런 이유로 Optional을 엔티티 속성으로 받으면 안된다. 왜냐하면 엔티티 사용에 제약을 가할 것이기 때문이다.

HttpSession에 저장된 객체들은 모두 Serializable하다. 왜나하면 웹노드에 클러스터 되기 때문이다.

StackoverflowError

JPA 양방향

@OneToMany, @ManyToOne 어노테이션을 사용한다고 관계를
mappedBy로 양방향을 설정한다. mappedBy는 관계의 주인을 알려준다. 이렇게 설정하는 순간 Reply 엔티티에 content_id라는 키가 생성된다.

@OneToMany(mappedBy = "content")
private List<Reply> replies

@ManyToOne()
private Content content

매핑시켜준 애와 다른 파라미터를 사용해서 계속 바보짓, 생성자에 들어가는 애가 DTO안에 있는 애와 다른데 계속 넣고 있음. Content값을 데이터베이스에 못넣고 있는데 계속 넣고 있음

Reply
content id = null

받아온 데이터가 무한루프 도는 문제

lety_20391의 답변을 보자

Movie.java
    @OneToMany(mappedBy = "movie", targetEntity = Clip.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Clip> clips = new HashSet<Clip>();

//the below getter will look for data of related Clip
    public Set<Clip> getClips(){ return this.clips}


 Clip.java

    @ManyToOne
        @JoinColumn(name="movie_id")
        private Movie movie;

//the below getter will look for related Movie again
   public Movie getMovie() { return this.movie }

위의 예시를 보고 설명을 해보자

구조는 간단히 하나의 Movie에는 여러개의 Clip을 가지고 있다. 마치 하나의 게시판에 여러개의 댓글이 있는 것이다.

Movie 객체를 받을 때, 시스템을 Clips 리스트를 로딩할 것이다. 하지만 Clip 클래스는 Movie라는 프로퍼티가 있고 getMovie라는 getter 메소드가 있다. 그래서 Clip이 로딩될 때 Movie 프로퍼티를 getMovie해서 객체를 또 반환하게 된다. 이렇게 무한루프를 돌게 만든다

예를들면. Movie01이 있을 때, 이 릴레이션은 Clip01과 Clip02를 가지고 있다. 시스템이 Movie01데이터를 불러온다면 getter 메소드를 통해 Clip01과 Clip02를 불러올 것이다.

하지만 Clip 클래스에는 Movie 프로퍼티가 있고 getMovie라는 getter메소드도 가지고 있다. 그래서 시스템이 Clip01이라는 데이터를 받아오면 그것과 연결된 Movie01라는 객체를 getMovie로 또 받아오게 된다.

Movie01 -> getClip -> Clip01 -> getMovie -> Movie01 -> getClip -> Clip01 -> getMovie -> Movie01 -> ...

결론은 Clip의 getMovie를 지우면 문제가 해결된다.

톰캣의 로그 파일 보기

cd ~/tomcat/logs
tail -500f catalina.out

Docker ARG vs ENV

ARG는 docker 이미지가 빌드(RUN)되고 있을 때만 사용된다. 이미지가 사용되고 난 이후와 컨테이너가 시작했을 때(ENTRYPOINT, CMD)는 사용되지 않는다. 또한 ARG를 ENV에 세팅할 수도 있다.
ENV는 컨테이너에서만 쓰인다.

github 커밋 템플릿

feat: 새로운 기능을 추가할 경우
fix: 버그를 고친 경우
docs: 문서 수정한 경우
style: 코드 포맷 변경, 세미 콜론 누락, 코드 수정이 없는 경우
refactor: 프로덕션 코드 리팩터링
test: 테스트 추가, 테스트 리팩터링 (프로덕션 코드 변경 없음)
chore: 빌드 테스크 업데이트, 패키지 매니저 설정할 경우 (프로덕션 코드 변경 없음)

Dockerfile build & run 한꺼번에 하기

docker build -t foo . && docker run -it foo

주의) Dockerfile을 사용할 때, RUN에 해당하다는 git clone 명령은 같은 Dockerfile로 빌드할 때 캐시메모리에 남아있는 명령어를 사용하므로 예전 clone된 파일을 사용하여 계속 에러 발생할 수 있음

Tomcat shutdown

tomcat/bin/shutdown.sh 

ENTITY 클래스의 jsonIgnore

@CrossOrigins

@CrossOrigin을 사용해도 cross-origin 에러가 난다면 @CrossOrigin 어노테이션에 아래 옵션을 추가해보자

allowCredentials = "false"

추정하기로 ALLOW-ORIGIN-ALL 헤더에 OPTION 정보가 날아가지 않아서 그런것 같다. credential을 꺼서 날라가게 해주는 것이 좋을 듯

Axios에서 POST 방식으로 REST API 받아올 때 특수문자 인코딩

실행환경이랑 운영환경 분리해야지....

바보다..

Optional로 예외처리

Optional<Account> notOpt = accountRepository.findByUsername("test");
Account notAccount = notOpt.orElseThrow(()->new Exception("Username not found"));

orElseThrow의 인자는

환경설정

  1. 개발/운영 환경 분리
  2. update/validate

자동 날짜 생성

Entity 생성

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class AbstractEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreatedDate
    private LocalDateTime createDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

@EntityListeners의 AuditingEntityListener에 의해 데이터에 변경이 있으면 자동으로 시스템의 시간이 추가될 수 있도록 만듦

JpaAuditing 설정

@SpringBootApplication
@EnableJpaAuditing
public class EditxApplication {
    public static void main(String[] args) {
        SpringApplication.run(EditxApplication.class, args);
    }
}

방금 생성한 abstractEntity를 DTO 객체에 상속 시켜줌으로서 부모 클래스로 자식이 중복적으로 가져야할 코드

@Id 
@GeneratedValue 
private Long id

등을 상속 시켜줌으로서 코드 관리를 용이하게 만든다.

Swagger

		<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.2.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.2.2</version>
            <scope>compile</scope>
        </dependency>
@EnableSwagger2
public class EditxApplication {
    public static void main(String[] args) {
        SpringApplication.run(EditxApplication.class, args);
        
         @Bean
    public Docket newsApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("greetings")
                .apiInfo(apiInfo())
                .select()
                .paths(PathSelectors.ant("/content/**"))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
				.title("Editx API")
                .description("Editx API")
                .termsOfServiceUrl("http://www-03.ibm.com/software/sla/sladb.nsf/sla/bm?Open")
                .contact("Niklas Heidloff")
                .license("Apache License Version 2.0")
                .licenseUrl("https://github.com/IBM-Bluemix/news-aggregator/blob/master/LICENSE")
                .version("2.0")
                .build();
    }
        
    }
}

.paths(PathSelectors.ant("/content/**")) : 어떤 패턴의 url을 문서화 할 것인지 결정

다음과 같은 주소로 접근가능

http://localhost:8080/swagger-ui.html

배포 자동화

shell script

bash script cheatsheet
홈페이지 형식으로 bash script 연산자를 정리해놓은 사이트이다. es6, vim, react 등 여러가지 요약본도 찾을 수 있으니 참조하길 바란다.

deploy.sh

#!/bin/bash			

#!/bin/bash

  • 어떤 shell을 사용할 것인가

-rw-rw-r-- 1 editx editx 32 Aug 29 02:52 deploy.sh

  • 처음 생성하면 실행권한이 없음
  • chmod 755 deploy.shell 커맨드를 통해 실행권한을 줄수 있도록 한다.

-rwxr-xr-x 1 editx editx 32 Aug 29 02:52 deploy.sh

if[-z soemthing]

-z

  • string 피연산자의 사이즈가 0인지 확인, 만약에 0이면 true를 반환

nohup
빌드파일의 로그를 nohupout에서 남김

환경변수를 @어노테이션 안에 넣으려면

application.yml

editx:
	server:
    	url: http://localhost:8080
@Cors

@valid

자바스크립트에서 검증은 브라우저에 남기 때문에 다른 사람이 보고 검증방법에서 돌아가 접속할 수 있을 가능성이 있음

자동 배포 스크립트

deploy.sh

#!/bin/bash

echo "welcome to editx-backend build process!"

echo "> editx-front 디렉토리로 이동"
cd /editx-test

echo "> 프로젝트 git pull 시작"
git pull

echo "> 프로젝트 build 시작"
./mvnw clean package

echo "> 현재 진행중인 어플리케이션 PID"
CURRENT_PID=$(pgrep -f java)
echo "$CURRENT_PID"

if [ -z $CURRENT_PID ];
then
        echo "> 현재 실행중인 어플리케이션이 없습니다"
else
        echo "> 현재 어플리케이션이 구동 중입니다"
        echo "> 실행중인 어플리케이션을 종료합니다"
        echo "> 현재 실행중인 PID : $CURRENT_PID"
        kill -9 $CURRENT_PID
        sleep 5
fi

echo "> 프로젝트 deploy 시작"
JAR_DIR=/editx-test/target
JAR_NAME=$(ls $JAR_DIR | grep editx | head -n 1)
echo "> jar 파일 디렉토리 : $JAR_DIR"
echo "> jar 파일 : $JAR_NAME"

java -jar $JAR_DIR/$JAR_NAME --spring.profiles.active=product

create react app + docker + nginx

https://medium.com/yld-blog/deploy-your-create-react-app-with-docker-and-ngnix-653e94ffb537

Spring REST Validation

도커 이미지 어떻게 만들어졌는지 보기

docker history image

Dockercompose

도커간 통신은 불가

호스트의 네트워크를 공통으로 사용ㅎ나ㅡㄴ Contianer간에 내부통신은 불가
컨테이너에서 도커 호스트와의 통신만 가능
컨테ㅣ너들이 도ㅋ호스트이ㅡ nic를 같이 사용하기 떄문

기존 생각했던 아키테처는 고려하지 못한 것이
1. 컨테이너끼리 소통이 가능한가
2. container에서 나가는 요청은 NAT가 되는가

그냥 막연하게 그렇겠지 라고 생각하고 넘어갔다
기존의 실수는 여러가지 네트워크 중
host만 사용했는데 host는 컨테이너 상호간 커뮤니테이션 못한다.
bride 네트워크만 컨테이너 상호간 통신을 가능하게 한다. 하루 날렸다

호스트 아이피

hostname -I

브리지 확인

brctl name

HTTP vs REST

https://stackoverflow.com/questions/2190836/what-is-the-difference-between-http-and-rest

REST는 HTTP가 사용해야할 방식이다. 정확히 말하면 REST는 HTTP를 어떻게 사용해야하는지 정해놓은 룰이다.

우리는 HTTP 메소드 중에 GET과 POST만 쓴다. 하지만 GET, POST 뿐만아니라 PUT, DELETE, PATCH 등 여러가지의 메소드들이 있다. REST는 HTTP 프로토콜 메소드를 모두 사용하는 방식이다.

예를들어 DELETE 메소드는 URI을 통해 문서(파일이나 state)를 삭제하는 방법이다.

HTTP : html 문서들을 서버로 가져올 수 있음
1. Request HTTP
header : 브라우저가 서버로 보내는 메세지
2. Response HTTP
3. HTTP Method
서버에 요청을 보내는 방법

RESTful

  • 위의 HTTP 메소드를 이용하여 서버와 통신하는 것을 RESTful 이라고 합니다.
  • Representational State Transfer
  • GET, POST, PUT, DELETE를 이용하여 서버와 통신
    * POST
       POST /dogs HTTP/1.1
    			{ "name": "blue", "age": 5 }
    			HTTP/1.1 201 Created
       ```
    	*	POST 는 클라이언트가 리소스의 위치를 지정하지 않았을때 리소스를 생성하기 위해 사용하는 연산이다
    *	PUT
    	*	반면 리소스의 위치가 명확히 지정된 다음의 요청을 고려해 보자.
    			*	/dogs 의 프로퍼티가 name 과 age 뿐이라면, 이건 몇 번을 수행하더라도, 같은 결과를 보장한다. 다시 말해 idempotent 하다.

[Architectural Styles and

the Design of Network-based Software Architectures](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)

REST is a hybrid style derived from several of the network-based architectural styles described in Chapter 3 and combined with additional constraints that define a uniform connector interface.

Client-Stateless-Server (CSS)

statelss 서버는 서버에 세션정보를 저장할 수 없다.
그래서 모든 정보를 요청에 저장해서 보내야한다.

The client-stateless-server style derives from client-server with the additional constraint that no session state is allowed on the server component.Each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is kept entirely on the client.

  • 서버에 세션이 저장이 안됨
  • 요청에는 요청을 이해할만한 모든 정보가 다 있어야 함
  • 서버에 저장된 context를 사용할 수 없다.
  • session state는 모두 클라이언트에 저장된다.

These constraints improve the properties of visibility, reliability, and scalability. Visibility is improved because a monitoring system does not have to look beyond a single request datum in order to determine the full nature of the request. Reliability is improved because it eases the task of recovering from partial failures [133]. Scalability is improved because not having to store state between requests allows the server component to quickly free resources and further simplifies implementation.

  • 가독성
    • 시스템 모니터링 가능
  • 신뢰성
    • 부분적인 실패에 복구를 쉽게 해줌
  • 확장성
    • 서버에 세션을 저장하지 않으므로 쉽게 새로운 리소스를 얻을 수 있음

The disadvantage of client-stateless-server is that it may decrease network performance by increasing the repetitive data (per-interaction overhead) sent in a series of requests, since that data cannot be left on the server in a shared context.

  • 단점
    • 중복된 데이터를 보냄으로 네트워크 퍼포먼스가 안좋아 질 수 있음

5.1.4 Cache
In order to improve network efficiency, we add cache constraints to form the client-cache-stateless-server style of Section 3.4.4 (Figure 5-4). Cache constraints require that the data within a response to a request be implicitly or explicitly labeled as cacheable or non-cacheable. If a response is cacheable, then a client cache is given the right to reuse that response data for later, equivalent requests.

  • 네트워크 효율을 높히기 우해 캐시 사용
  • 응답이 캐시가 가능하다면 클라이언트 캐시는 나중에 같은 요청에 대해 응답 데이터를 사용할 권한을 부여받는다

5.2 REST Architectural Elements

The Representational State Transfer (REST) style is an abstraction of the architectural elements within a distributed hypermedia system

docker

MAINTAINER

MAINTAINER 작성자 이름

로컬 image를 docker registry로 보내기

# container -> iamge
docker commit 기존컨테이너 새이미지

# image -> iamge
docker tag 기존이미지 새이미지

docker login 도커허브아이디
docker push image

WAS 내부망으로 숨기기

도커 bridge 생성

dcoker network create (--driver bridge) --subnet 10.10.10.0/24 editx-network
docker run -d -p 8080:8080 --name es --network editx-network pop8682/editx-server

git

git init
git add .
git commit -m 'initial commit'
git remote add origin https://github.com/pop8682/React-webpack-from-the-scratch.git
git push origin master
profile
Let's make something happen 👨🏻‍💻🌿

0개의 댓글