728x90

1주차 학습내용

깃허브 바로가기

1주차 스터디에 대해 간략하게 정리하는 내용이다.

JPA란?

Java Persistence API 의 줄임말로써, 자바 진영의 표준 ORM 기술이다.
JPA는 다른 JDBCTemplate 나 Mybatis 등등 객체와 관계형 데이터베이스의 차이를 메우기 위해 SQL을 작성을 해서 개발을 진행해야했었는데, 반복적인 CRUD 뿐만아니라 객체 모델링과 관계형 데이터베이스의 차이를 해결해주었다.
JPA의 핵심은 바로 SQL이 아닌 객체 중심으로 개발을 진행하기 때문에 생산성과 유지보수에서 용이하다.
이렇게 되면 테스트를 작성하기에도 편리하다.

SQL을 직접 다룰 때 발생하는 문제점

지금까지 실무에서 Mybatis나 iBatis를 사용하였다. 그래서 소스코드를 보거나 내가 구현해야 할 때 상당 시간을 데이터베이스를 매핑시킨 xml에 너무 의존하여 개발했다.

  • 왜? - 오타에 의한 , 띄어쓰기에 의한 하나라도 틀리게 되면 무수한 에러를 발생시킨다.

기존의 DTO파일에 뭔가 한 칼럼을 추가해야된다고 하면 추가할 클래스들과 sql을 수정해서 작성해야 하는데,
이 때 또 다시 아까처럼 오타가 발생하면 안되기에 꼼꼼하게 개발해야 한다.

또 연관관계에 있어서 Member라는 객체가 Team을 갖고 있는다고 가정을 한다면, Team에 대한 SQL문도 직접 매핑을 시킨다음 묶어주어야 했다.

JPA와 문제 해결

  • 저장기능persist()
  • 조회기능 find()
  • 수정 : 메소드는 별도로 없지만 객체를 변경하게 되면 적절한 update sql쿼리가 실행
  • 연관된 객체 조회기능 : 연관된 객체를 사용하는 시점에 적절한 select sql 실행

상속

객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능을 지원하기는 하지만 객체의 상속과 다르다.
JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.

연관관계

객체는 참조를 사용하여 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을
조회한다.

→ 객체를 테이블에 맞춰 모델링 한다 이렇게 객체지향 모델링을 해도 무척 번거롭고 어려움
→ 이런 패러다임 불일치는 객체지향 모델링을 포기하게 만들정도로 극복하기에 어려움

반면 JPA는 이런 문제를 손쉽게 해결해준다.

객체 그래프 탐색

참조를 사용하여 연관된 객체를 탐색하는 것이다.
SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐
반면 JPA는 자유롭게 마음껏 탐색이 가능함. 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행하기 때문에 자유로운 것.

  • 지연로딩 : 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미루는 것
  • JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지 간단한 설정으로 정의할 수 있다.

비교

DB의 경우 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는것은 쉽지 않다.
반면에 JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

정리

객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 다르기 때문에 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다. 그래서 정교한 객체 모델링을 할수록 불일치 문제는 더 커진다. 이러다보니 데이터 중심의 모델로 변하는 과정이 되버린다.
그리고 이것을 해결해주는 것이 바로 JPA다. 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

📕 ORM(Object Relational Mapping)

ORM(Object Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻.
이 프레임워크는 객체와 테이블을 매핑하여 패러다임 불일치 문제를 개발자 대신 해결해준다.

ORM이란 것은 애플리케이션과 데이터베이스 사이에 가상의 데이터베이스(JPA에서 영속성 컨텍스트라고 불리는)라는 인디렉션을 만드는 것이다.

즉, OOP(Object-Oriented Programming)를 하기위한 프로그래밍 기법이다.

원래는 데이터베이스에 종속적이었던 코드를 역으로 데이터베이스를 추상화하여 코드레벨에 맞춰주며

그렇게 함으로써 데이터에 종속적인 프로그래밍이 아닌 OOP를 할 수 있게 된다.

내가 실무를 보며 느끼기에 ORM이란 것은 결국 사전적인 정의대로 OOP를 위한 기술이다.

SQL에 종속적이다, 똑같은 코드가 반복된다 등의 잡다한 이유가 있지만

이러한 문제들의 본질은 OOP를 하지 못하기 때문에 발생하는 것이다.

애플리케이션이 데이터베이스와 연동되면 모든 코드가 자연스럽게 데이터 기반으로 돌아가기 때문에

자연스럽게 OOP 5대 원칙, SOLID도 대부분 위반하게 된다.

약어 개념
SRP 단일 책임 원칙 (Single responsibility principle)
한 클래스는 하나의 책임만 가져야 한다.
OCP 개방-폐쇄 원칙 (Open/closed principle)
“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
LSP 리스코프 치환 원칙 (Liskov substitution principle)
“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라.
ISP 인터페이스 분리 원칙 (Interface segregation principle)
“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”
DIP 의존관계 역전 원칙 (Dependency inversion principle)
프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

간단한 예제를 보자면

Member 객체를 DB에 저장할것이라고 했을 때,

Member 클래스를 Java Beans 규약에 맞추어 작성하고, SQL을 작성한다.

class Member{
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}
INSERT INTO MEMBER
    (NAME, AGE)
VALUES
    (#name, #age);

이제 접근자를 이용해 nameage를 추출하여 SQL에 넣어줘야한다.

class InsertService {
    public void insert(String name, int age);
}

...
service.insert(member.getName, member.getAge);
...

이 데이터를 뽑아내고 입력하는 과정 자체가 데이터에 종속적인 과정이며, 결국 이 과정으로 인해 모든 문제가 발생한다.

만약 이 상황에서 address라는 필드가 하나 더 필요해진다면?

Memberaddress 필드를 추가하고, SQLService 클래스도 변경되어야만 한다.

실무에서는 절대 써먹을수조차 없는 매우 간단한 예제임에도 불구하고 즉시 OCP가 위배된다.

이처럼 코드와 SQL 사이에 매우 강한 결합도(=내용 결합도)가 생기기 때문에 SOLID를 지키기가 정말 어려워진다.

나는 개인적으로 불가능하다고 생각하기도 한다.

그렇다면 ORM을 도입하면 어떻게 변할까?


@Entity
class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}

SQL은 따로 작성하지 않는다.

추상화된 가상의 데이터베이스가 알아서 처리해주기 때문이다.

이 상태에서 address 필드를 추가해야 한다면 그냥 추가하면 끝이다. 더 손댈 게 없다. 즉, OCP를 위반하지 않게된다.

이렇게 간단한 예제만으로도 SOLID를 지키기가 압도적으로 수월해지며, 결과적으로 OOP의 장점을 모두 누릴 수 있게된다.

SQL에 종속적인 문제, 재사용성 증가, 유지보수성 증가, 리팩토링 용이성 등등. 결국 모두 OOP를 하게 됨으로써 얻는 이득이다.

자바진영에 다양한 ORM프레임워크가 존재하는데 그중 Hibernate 프레임워크가 가장 많이 사용된다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04
[JPA] JPA Auditing  (0) 2022.08.03
728x90

요즘 계속 뭔가 하나씩 연결이 되는 상황이 많다. 그래서 기분이 좀 좋다. 몰랐던 것을 조사하면 이전의 학습했던 것들이 쭉 이어진다. 오늘도 마찬가지이다.

라우터란?

라우터는 패킷의 위치를 추출하여, 그 위치에 대한 최적의 경로를 지정하며, 이 경로를 따라 데이터 패킷을 다음 장치로 전향시키는 장치이다. 즉, 라우터는 이름 그대로 네트워크와 네트워크 간의 경로를 설정하고 가장 빠른 길로 트래픽을 이끌어주는 네트워크 장비이다. 그냥 공유기라 생각하면 된다.

이 라우터에는 단자를 꼽는게 크게 두가지로 볼 수 있는데
그것이 바로 WAN과 LAN이다.

WAN

WAN은 Wide Area Network의 줄임말로 한 집마다 인터넷 회선이 있는데 그것을 WAN으로 생각하면 되겠다. 광범위한 지역 단위로 구성하는 네트워크이다.
이 회선을 가지고 집이나 사무실의 네트워크를 구성한다.
public IP이다.

LAN

LAN은 Local Area Network로 사용자가 포함된 지역 네트워크이다. 랜랜 거리는 것이 바로 이 LAN이다.
사무실에는 예를 들면 내선전화가 있고 집에서는 공유기를 통해 Wi-Fi나 이더넷으로 연결한 기기들이 다 LAN으로 구성이 되어있다. 그래서 구성할 때 드는 비용과 전기세를 빼면 유지보수비가 들지 않는다. private IP이다.

라우터를 쓸 때에는 이 자체에도 IP주소가 붙게된다.

인터넷을 실행시키는 순서는 다음과 같다.
컴퓨터에서 웹 브라우저를 킨다. 그리고 도메인명을 입력해서 그 주소에 해당하는 자료를 요청하면 화면이 나오게 되는데
이것을 우리가 배운것으로 설명하자면...

private IP를 라우터가 받는다. 여기서 NAT가 등장하는데 이 뒤에서 설명하도록 하겠다. 공유기가 하는 일은 여기서 두 가지 이다.

  • 어떤 IP가 도메인명을 요청하는지 내부에서 자체적으로 확인 후 기억
  • NAT가 요청한 데이터를 변경 ex) 192.168.0.xx 를WAN(public IP)으로 주소를 변경해서 요청(request)한다.

이렇게 되면 그 도메인은 WAN주소에 응답(response)하고 데이터를 전달해준다. 그러면 WAN에서 다시 공유기로 받은 데이터를 가져온다.
여기서 공유기가 아까 했던 두번째를 다시 바꿔주는데
이제 WAN을 LAN으로 변경해준다. 그렇게 한 뒤에
첫번째로 했던 일인 private IP를 기억해내서 해당 IP에 WAN으로 부터 넘어온 데이터를 넘겨준다.
이런식으로 인터넷으로 소통을 하는것이다.

NAT

NAT는 Network Address Translation이다. IP 패킷에 있는 출발지 및 목적지의 IP주소와 TCP/UDP 포트 숫자 등을 바꿔 재기록하며 네트워크 트래픽을 주고 받게하는 기술이다.

NAT가 있어서 내가 가지고 있는 하나의 공인 IP주소(private IP)를 사용하여 여러 대의 호스트가 인터넷에 접속할 수 있다.
그래서 LAN으로 묶여있는 기기들은 전부 같은 공인 IP주소를 사용한다.

포트 포워딩

0~ 65535 포트까지 있음
22 - SSH
80 - http
1023까지 well-known port

127.0.0.1:8080 ==> 저 ip주소에 8080포트로 서버를 연결해달라는 의미

근데 여기서 문제는 192 사설포트가 여러개일 경우에 해당하는 서버로 매칭시켜주는 것이 포트가 필요한 이유이다.
공인ip:포트 접속요청이 오면 내부에서 192.168.0.x:포트 로 보내는것

여기서 라우터의 nat설정을 바꿔야한다.(라우터가 없을때는 그냥 localhost = 128.0.0.1)

외부포트 -> 공인ip:요청포트
내부포트 -> 설정한 사설ip:요청포트

동적 IP - 계속 값이 변화하기 때문에 지속적인 연결을 하려는데에 제한이 있다.
대신 보안에는 좋을 듯?
고정 IP - 지속적인 연결에는 좋을것, 하지만 보안에는 상대적으로 취약할 수 있다.

네트워크의 기초 개념들을 학습중인데 다시 보니까 이해가 더 잘되는 것같다. 이렇게 공부하고 그냥 아니까 지나가자 가 아니라 다시 한번 와서 개념들을 복습하는게 훨씬 좋은 것이라고 생각한다.🤣🤣

mybatis나 ibatis의 db 매핑정보에서 url주소를 192.168인 내부IP, 그리고 127.0.0.1(localhost) 종류를 많이 사용했고 실제 배포할 때에는 외부 IP를 사용하여 데이터베이스 정보를 잘 매핑시켜야 배포도 원활하게 진행이 되는 것이다. 다음에 서버를 구매하여 배포를 할 때에는 이점을 명시하고 잘 배포해보아야 겠다.

728x90
728x90

XSS : Cross Site Scripting

사이트를 교차해서 스크립트를 발생시킴.
게시판을 포함한 웹에서 자바스크립트같은 스크립트 언어를 삽입해 개발자가 의도하지 않은 기능을 작동시키는 것
클라이언트 측을 대상으로 한 공격이다.

게시판에서 글을 쓰는곳에 작성자가 HTML 코드를 삽입하여 글을 읽는사람의 브라우저에서 실행되게 하는 원리이다.

이게 단순하게 alert()만 띄운다고 되는것이 아니라 그 스크립트 안에 엄청 긴 코드를 주입해서 무한정 창을 띄우게 하거나 악성코드를 직접적으로 받게 할수는 없지만, URL을 클릭하도록 유도하여 악성 프로그램을 다운받는 사이트로 Redirect시킨다.

위험성

  1. 쿠키 정보 및 세션 획득
    공격자는 XSS에 취약한 페이지 및 게시판에 XSS공격을 수행함으로써 해당 페이지를 이용하는 사용자의 쿠키 정보나 세션 ID를 획득할 수 있다.
    이렇게 되면 XSS공격을 통해 페이지를 사용하는 사용자의 세션ID를 얻어서 공격자가 불법적으로 사용자인 척 하면서 활보할 수 있다.
  2. 시스템 관리자 권한 획득
    XSS취약점이 있는 웹서버에 다양한 악성 데이터를 포함시킨 후, 사용자의 브라우저가 악성 데이터를 실행하게 할 수 있다.
    만약 이렇게 해킹이 될 경우, 내부로 악성코드가 이동해 중요 정보가 탈취될 수 있다.
  3. 거짓페이지 노출
    <script> 뿐만이 아니라 <img>와 같은 그림을 표시하는 태그를 사용해 원래 페이지와 전혀 관련 없는 페이지를 표시할 수가 있다.

XSS 방지법

XSS 공격은 IPS, IDS, 방화벽 등으로도 방지할 수가 없다.
그래서 문자를 필터링 해주는 방법이 있다.

Script 문자 필터링

XSS 공격은 입력값에 대한 검증이 제대로 이루어지지 않아 발생하는 취약점이다. 그래서 입력값에 대해 서버는 필터링을 해야 한다. < > " ' 와 같은 문자열들을 정규표현식으로 필터링하여 태그를 제거해준다.

예시

게시판에서 글 제목을 클릭하면 다른 페이지로 넘어가서 글 상세보기를 해준다고 가정을 했을 때, 제목을 <a href="hi">test</a> 로 한다면 원래 보여주려던 '상세보기'는 구현할 수 없고 href에 있는 hi 주소로 http 통신을 보내기 때문에 생뚱맞는 페이지를 호출하여 redirect 해준다.

이 점을 항상 생각하고 유의하여 코드를 작성하여야 한다.

728x90
728x90

JPA 오류 💢

jpa에서 spring.jpa.hibernate.ddl-auto=create 속성으로 테이블을 만들어줄 경우에
MariaDB default로 어떤값을 주던간에 latin1속성으로 insert가 된다. 그래서 이방법 말고 안에서 테스트만 한다고 하면 h2 DB를 사용하는 것이 더 바람직하다.

아니라면 ddl-auto 속성을 주지않고 직접 MariaDB에서 어떤 Column들을 넣을지 결정하고 먼저 생성한 다음 연결하는게 좋을 것이다.
이것 때문에 애를 많이 먹었다.

[mysqld]
datadir=C:/Program Files/MariaDB 10.5/data
port=3306
innodb_buffer_pool_size=1948M
init_connect="SET collation_connection = utf8_general_ci"
init_connect="SET NAMES utf8"
character-set-server = utf8
collation-server = utf8_general_ci
[client]
port=3306
plugin-dir=C:/Program Files/MariaDB 10.5/lib/plugin
default-character-set=utf8
[mysqldump]
default-character-set = utf8
[mysql]
default-character-set = utf8

MariaDB data 경로에 있는 my.ini 파일인데 이렇게 바꿔주면 기본으로 utf8 설정을 할 수 있다.

데이터베이스 character set 확인
show create database 데이타베이스명;

데이터베이스의 character set 변경
alter database 데이타베이스명 default character set = utf8;

테이블 character set 확인
show create table 테이블명;

테이블의 character set 변경
alter table 테이블명 default character set = utf8;

각 컬럼별 character set 확인
show full columns from 테이블명;

각 컬럼별 character set 변경
alter table 테이블명 modify column 컬럼명 varchar(20) character set utf8 collate utf8_general_ci;

ddl-auto로 생성하였다면 이렇게 일일이 바꿔주어야 한다. 꼭 명심하고 체크한 뒤에 test를 진행하자.😨

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] JPA Auditing  (0) 2022.08.03
728x90

Mock 빈 오류에 관해서

그동안 혼자 공부한 것들을 토대로 프로젝트를 하나 해보려고 한다.
나름 공부한다고 공부를 했는데 HelloWorld 구현하는데 컨트롤러를 만들어두고 테스트 코드를 내의지대로 처음 작성해봤다.
MockMvc 클래스를 사용하여 Mock Bean을 주입받으려고 했더니 빈이 등록되지 않았다는 오류를 받았다.
일단 오류에는 두가지가 있었다.
@SpringBootTest 어노테이션을 사용할때 @WebMvcTest도 같이 사용해서 MockBean을 주입받는줄 알고 바로 넣었는데 두 Mock객체가 공유되어있는 객체가 아니고 서로 다른 객체이다. 그래서 두개의 빈이 충돌이 발생한다.

여기서 해결책은 두가지였다.

  • @SpringBootTest@AutoConfigurationMockMvc를 사용한 후 MockMvc의 @Autowired를 제거 하는 방법
  • @ExtendWith, @WebMvcTest@Autowired를 같이 사용하는 방법

이렇게 두가지가 있었다.

먼저 @SpringBootTest의 경우 일반적인 테스트로 슬라이싱을 사용하지 않기 때문에 전체 응용 프로그램 Context를 시작한다. 때문에 전체 응용 프로그램을 불러서 모든 bean을 주입하기 때문에 속도가 느리다고 한다.

@WebMvcTest의 경우는 뒤에 ()를 사용해 특정 레이어를 테스트하고 모의 객체를 사용하기 때문에 필요한 bean을 직접 세팅해줘야 하는 단점이 있지만, 가볍고 빠르게 테스트 할 수 있다.

위의 두가지중 아래의 방법에서는 같이 @AutoConfigurationMockMvc를 사용이 가능하다.

가짜 객체인 Mock을 사용할 때 bean 주입하는데에 있어서 이것을 생각하고 주입해주도록 하자.

728x90

'Spring' 카테고리의 다른 글

Spring Data JPA  (0) 2022.08.06
Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] Spring Security  (0) 2022.08.03
의존성 주입 어노테이션 정리  (0) 2022.08.03
728x90

그리디 알고리즘

그리디 알고리즘은 단순하지만 강력한 문제 해결 방법이다.
단어 그대로 번역하자면 탐욕법이다.
이 부분에서 탐욕적이라 함은 '현재 상황에서 지금 당장 좋은 것만 고르는 방법'이다.
이 알고리즘은 매 순간 가장 좋아 보이는 것만 선택하고, 그 선택이 나중에 어떤 영향을 미치는지에 대해서는 고려하지 않는다.

거스름돈을 예시로 살펴보자
당신은 음식점의 계산을 도와주는 점원이 되었다고 가정하자.
500원, 100원, 50원, 10원짜리 동전은 무한히 존재한다. 손님에게 거슬러줘야 하는 돈이 N원일 때, 거슬러 줘야 할 동전의 최소 개수를 구해라
(단, 거슬러줘야 할 돈 N은 항상 10의 배수이다.)

이럴때는 단순하게 가장 가격이 큰 동전 부터 돈을 거슬러 주는 것이다.
이렇게 되면 N원으로 설정된 가격을 금방 차감시켜 0원으로 만들기 적합하다.

coin.py

n = 1260 # 거슬러줘야할 가격
count = 0 # 코인의 개수

# 화폐 단위가 큰 것부터 차례로 거슬러준다.
coin_types = [500, 100, 50, 10]

for coin in coin_types:
    count += n // coin # 원래 가격을 코인값으로 나눈 몫만큼 동전으로 추가해줌
    n %= coin

print(count)

화폐의 종류가 K개 라고 할때, 위 소스의 시간복잡도는 BigO 표기법으로 O(K)이다. 시간복잡도에서는 총 화폐인 N을 찾을 수 없다.
그래서 이 알고리즘은 동전의 종류에만 영향을 받지 거슬러 줘야하는 금액에는 초점이 맞춰지지 않는다.

알고리즘 문제 유형을 바로 파악하기 어렵다면, 그리디 알고리즘을 의심해 볼 수 있다.

그리디 알고리즘의 예제를 많이 풀어봐야겠다.

728x90

'CS' 카테고리의 다른 글

불 논리 회고  (0) 2022.08.07
HTTP  (0) 2022.08.07
불 논리 정리  (0) 2022.08.07
재귀 알고리즘  (0) 2022.08.06
728x90

Enum

소스코드를 분석을 하다 보니 너무 많은 if-else 가 엮여져 있는 코드들을 많이 봐서 너무 어지러웠다. 이것을 어떻게 할 수 없을까에 대한 고찰을 가지고 있다가 처음엔 switch조건을 생각해서 메소드로 따로 빼내자고만 생각했다.
근데 Enum을 찾아본 결과 너무 좋아서 포스팅하게 되었다.

Enum이란

  • Enum은 Eumeration로 열거형이라고 불리며, 서로 연관된 상수들의 집합을 의미한다.

  • 자바에서 final static String, int와 같이 문자열이나 숫자들을 나타내는 기본자료형의 값을 Enum으로 대체해서 사용할 수 있다.

  • 인터페이스나 클래스로 상수를 정의하는 것을 보완하여 IDE의 지원을 적극적으로 받고 타입 안정성도 갖출 수 있게 된다.

예를 들면 이런 코드였다.

    String ss = "A구역";

    if(ss.equals("A구역")) {
        System.out.println(ss);
    }else if(ss.equals("B구역")) {
        System.out.println(ss);
    }else if(ss.equals("C구역")) {
        System.out.println(ss);
    }else if(ss.equals("D구역")) {
        System.out.println(ss);
    }

이렇게되면 계속 else if(ss.equals("구역")이 지금은 적어도 가독성이 이렇게 좋지 않은데 엄청 많은 구역이 있다고 한다면 그만큼의 줄 수를 채워서 단지 if 조건 하나만을 수행한다. 참으로 효율이 떨어지는 코드이다.

여기서 Enum을 사용했다.

TestEnum.java

enum TestEnum{
    A("a", "A구역");
    B("b", "B구역");
    C("c", "C구역");
    D("d", "D구역");

    private String alpa;
    private String name;

    TestEnum(String alpa, String name){
        this.alpa = alpa;
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

이렇게 A,B,C,D라는 키에 각각 Value 값을 넣어준다. 값을 할당하는 갯수는 자유이다. 대신 생성자의 매개변수도 같이 늘어나야 한다.

그러면서 메인함수 실행 클래스에서 어떻게 설정하는지 이제 보도록 하자.

Main.java

public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String key = sc.next();

        String str = TestEnum.valueOf(key).getName();
        String alpa = TestEnum.valueOf(key).getAlpa();    
    }
}

이렇게 설정을 해준 상태에서 위의 코드에서는 입력을 받아 key 변수에 값을 넣어준다. A,B,C,D 의 키값을 호출하게 되면 안에 있는 A구역이라면 alpa에는 a가, name에는 A구역이 들어가게 된다.
이렇게 해서 기존의 코드에서 상당히 많은 줄 수를 줄이면서 가독성도 높여 실용적으로 고칠 수 있는 방법을 터득하게 되었다.

앞으로 if-else구문이 나올때 반복되는게 많아진다면 Enum을 사용하는 것을 염두에 두고 코드를 짜 나갈 것이다.

그리고 Enum에 대해 추가적으로 더 공부가 필요해 보인다.

이상으로 Enum포스팅을 마친다.

728x90

'Java' 카테고리의 다른 글

MockMvc  (0) 2022.08.04
Mock, Mockito  (0) 2022.08.04
[Java] Optional  (0) 2022.08.03
Java Stream API  (0) 2022.08.03
728x90

스프링 시큐리티

스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증, 권한, 인가 등) 을 담당하는 스프링 하위 프레임워크이다.
인증인과를 담당하는 프레임워크이다.

🔒스프링 시큐리티 특징과 구조

  • 보안과 관련하여 체계적으로 많은 옵션을 제공하여 편리하게 사용할 수 있다

  • Filter 기반으로 동작하여 MVC와 분리하여 관리 동작

  • 어노테이션을 통한 간단설정

  • 세션과 쿠키 방식으로 인증

  • 인증관리자(Authentication Manager)와 접근 결정 관리자(Access Decision Manager)를 통해 사용자의 리소스 접근을 관리

어떤 유저가 로그인을 했을 시에 기본 회원과 admin 유저가 있다고 가정을 하면 회원인지 admin인지 인증 관리자가 먼저 판단을 한다. 그래서 누구인지 명확하게 알고 이 로그인한 유저에 대한 권한을 접근 결정 관리자가 판단을 하는데 여기서 특정 기능에 대한 권한이 없다면 그 동작은 실행시켜주지 않게끔 동작을 수행한다.

  • 인증 관리자는 UsenamePasswordAuthenticationFilter, 접근 결정 관리자는 FilterSecurityInterceptor가 수행한다.

🔒스프링 시큐리티 기본 구조
스프링 시큐리티 기본 구조

📢각 필터별 기능 설명

필터 기능
SecurityContextPersistenceFilter SecurityContextRepository에서 SecurityContext를 로드하고 저장하는 일을 담당함
LogoutFilter 로그아웃 URL로 지정된 가상URL에 대한 요청을 감시하고 매칭되는 요청이 있으면 사용자를 로그아웃 시킴
UsernamePasswordAuthenticationFilter 사용자 명과 비밀번호로 이뤄진 폼기반 인증에 사용하는 가상 URL요청을 감시하고 요청이 있으면 사용자의 인증을 진행함
DefaultLoginPageGeneratingFilter 폼기반 또는 OpenId 기반 인증에 사용하는 가상URL에 대한 요청을 감시하고 로그인 폼 기능을 수행하는데 필요한 HTML을 생성한다
BasicAuthenticationFilter HTTP 기본 인증 헤더를 감시하고 이를 처리함
RequestCacheAwareFilter 로그인 성공 이후 인증 요청에 의해 가로채어진 사용자의 원래 요청을 재구성하는데 사용됨 --> 현재 요청과 관련있는 캐시 요청이 있는지 확인하고 있다면 캐시요청을 처리해줌
AnonymousAuthenticationFilter 이 필터가 호출되는 시점까지 사용자가 아직 인증을 받지 못했다면 요청 관련 인증 토큰에서 사용자가 익명 사용자로 나타나게 됨
SessionManagementFilter 인증된 주체를 바탕으로 세션 트래킹을 처리해 단일 주체와 관련한 모든 세션들이 트래킹되도록 도움
ExceptionTranslationFilter 보호된 요청을 처리하는 동안 발생할 수 있는 기대한 예외의 기본 라우팅과 위임을 처리
FilterSecurityInterceptor 권한부여와 관련한 결정을 AccessDecisionManager에게 위임해 권한부여 결정 및 접근 제어 결정을 쉽게 만들어 줌
728x90

'Spring' 카테고리의 다른 글

Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
의존성 주입 어노테이션 정리  (0) 2022.08.03
Spring Test MockMvc 한글 깨짐 처리  (0) 2022.08.03
728x90

JPA Auditing이란?

보통 엔티티에는 해당 데이터의 생성시간과 수정시간을 포함한다.
언제 만들어졌고, 언제 수정되었는지 등등 나중에 유지보수에 중요한 정보이기도 하다. 그래서 매번 DB를 insert, update 할 때 날짜 데이터를 등록/수정하는 코드들이 들어간다.

아래의 내용은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 에서 읽은 내용이다.

//생성일 추가 코드 예제
public void savePosts(){
    ...
    posts.setCreateDate(new LocalDate());
    postsRepository.save(posts);
    ...
}

이런 단순한 코드가 모든 테이블, 서비스 메소드에 포함 된다고 생각하면 귀찮고 코드가 지저분해진다.
그래서 쓰는것이 JPA Auditing이다.

LocalDate 사용

Java8부터는 기존에 쓰던 Date, Calendar 클래스가 아닌 LocalDate와 LocalDateTime을 사용한다.
Date의 문제점을 고친 타입이기 때문에 Java8이상일 경우 무조건 써야 한다고 생각하면 되겠다.
Calendar 클래스는 월 값이 명시하는 static 변수이름에 해당하는 월 -1 이었다.
ex) Calendar.FEBRUARY 2월을 나타내는 이것은 값이 1이다.

BaseTimeEntity.java

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
    //모든 Entity의 상위 클래스가 되어 Entity들의 createDate, modifiedDate를 자동으로 관리하는 역할을 하는 클래스

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

BaseTimeEntity클래스는 모든 Entity의 상위 클래스가 되어 Entity들의 createDate, modifiedDate를 자동으로 관리하는 역할을 한다.

어노테이션 설명
@MappedSuperclass JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들도 칼럼으로 인식하도록 한다
@EntityListeners BaseTimeEntity클래스에 Auditing 기능을 포함시킴
@CreatedDate Entity가 생성되어 저장될때 시간이 자동 저장된다
@LastModifiedDate 조회한Entity가 값을 변경할 때 시간이 자동 저장된다

Posts.java

@Getter
@NoArgsConstructor
@Entity // 실제 DB테이블과 매칭되는 클래스
// 기본값으로 클래스의 camelCase 이름을 스네이크케이스 테이블과 매칭시켜준다.
public class Posts extends BaseTimeEntity {
    @Id // 해당 테이블의 PK임을 명시
    @GeneratedValue(strategy = GenerationType.IDENTITY) //Identity 옵션을 넣어야 auto_increment 설정 가능
    private Long id;

    @Column(length = 500, nullable = false) // 해당클래스는 변수만 선언해도 column이 되지만
    // 추가로 변경이 필요한 옵션이 있을때 사용한다. ex) varchar max_length 기본값은 255인데, 500으로 늘리고 싶을때
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    //여기처럼 타입을 TEXT로 변경하거나, null값을 못넣게 설정할 수 있다.
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author){
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content){
        this.title = title;
        this.content = content;
    }
}

마지막으로 JPA Auditing 어노테이션을 모두 활성화 하기 위해서 Application 클래스에 활성화 어노테이션을 붙여준다.

Application.java

@EnableJpaAuditing //JPA Auditing 활성화
@SpringBootApplication
public class Application {
    public static void main(String[] args){ SpringApplication.run(Application.class, args); }
}

이제 실제 코드는 완성 됐으니까 기능 동작을 위해 테스트를 수행한다.

JPA Auditing 테스트 코드

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {

@Test
    public void BaseTimeEntity_등록(){
        //given
        LocalDateTime now = LocalDateTime.of(2021, 5, 25, 0, 0, 0);
        postsRepository.save(Posts.builder()
                                  .title("title")
                                  .content("content")
                                  .author("author")
                                  .build());
        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);

        System.out.println(">>>>>>> createDate = " + posts.getCreatedDate() + ", modifiedDate = " + posts.getModifiedDate());

        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);
    }
}

이로 인해 앞으로 등록/수정일로 고민할 필요는 없어졌다. 넣고자 하는 엔티티에 BaseTimeEntity만 상속받으면 알아서 해결해준다.

2021-07-16 수정 ✔

하지만 이 형식대로 하면 date format이 동작하지 않는데
@DateTimeFormat, @JsonFormat 들도 Auditing에선 작동하지 않는다.

이럴때 찾은 방법은 아래와 같다.

@MappedSuperclass
@Getter
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    private String createdAt;

    private String modifiedAt;

    @PrePersist //해당 엔티티 저장하기 전
    void onPrePersist(){
        this.createdAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        this.modifiedAt = createdAt;
    }

    @PreUpdate //해당 엔티티 수정 하기 전
    void onPreUpdate(){
        this.modifiedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    }
}

Pre, Post 에 따라 이전 이후 구분 가능

이랬던 format 형식을


이런식으로 변경하여 DB에 저장할 수 있다.

이상으로 JPA Auditing 에 대한 포스팅을 마친다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04

+ Recent posts