반응형

App과 DB 사이에 있는 가상의 DB라고 생각하는 것이 직관적인 것 같다.

 

EntityManager Factory가 스레드마다 DB에 접근 시 EntityManager를 생성한다. 

EntityManager가 영속성 컨텍스트를 관리한다.

 

 

https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80

 

JPA 영속성 컨텍스트란?

영속성 컨텐스트란 엔티티를 영구 저장하는 환경이라는 뜻이다. 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.em.persist

velog.io

 

 


목차

  • 1차 캐시
  • 더티 체킹
  • 쓰기 지연
  • 동일성 보장

 

  • 영속성 컨텍스트의 4가지 상태

 

  • transaction

 


영속성 콘텍스트의 기능

 

 

1차 캐시

Map의 형태로 Key는 idValue는 Entity

 

  1. [1차 캐시]에 조회하고자 하는 id가 있는지 확인한다.

(persist 이후 1차 캐시에 있다면 조회 쿼리가 동작하지 않고 찾아온다.)

  2. 1차 캐시에 없다면 [DB]에 있는지 확인한다. (DB select 쿼리가 동작한다.)

1차 캐시를 먼저 확인하고, DB에 접근하는 방식이다. 캐시를 재사용하므로 성능이 향상된다.

 

 

 

Dirty Checking

상태 변화 감지

영속성 콘텍스트의 최초 조회한 상태(스냅샷)가 변경되면 트랜잭션 종료 시점에서 자동으로 해당 Entity를 update 한다.

persist로 Entity의 등록 상태가 아니라 조회를 한 상태여야 한다.

조회한 Entity를 변경하면 detach Entity가 생성 되는데 detach Entity를 merge -> update한다. (자세한 내용은 아래에)

 

 

 

트랜잭션을 이용한 쓰기 지연

영속성 컨텍스트에서 발생하는 sql쿼리를 모아놨다가 트랜잰션이 종료될 때 모든 sql쿼리를 반영한다.

 

@Transactional 안에 save()가 있다면, 즉시 반영이 아니라 해당 메서드가 종료될 때 반영 (@Transactional이 상위이므로)

 

 


영속성 콘텍스트 4가지 상태

 

 

 

 

New 상태

객체를 생성한 상태, 객체를 생성했다고 해서 Managed가 아니다. (persist필요)

영속성 콘텍스트에 Entity가 등록되지 않은 상태

 

Manage 상태

영속성 콘텍스트에 Entity가 등록되어 관리되고 있는 상태

 

Remove 상태

영속성 콘텍스트에 Entity가 등록되어 관리되고 있다가 삭제된 상태

 

Detach 상태

영속성 콘텍스트에서 관리되지 않도록 분리한다. (1차 캐시, Dirty Checking이 동작되지 않는다.)

EntityManager에서만 Detach 상태를 만들 수 있고, JpaRepository를 이용해서는 불가능하다.

(JPA에서는 굳이 직접 Detach 상태로 만드는 것이 의미가 없다고 생각했는지)

 

EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();

// 기존 엔티티를 가져와서 Detached 상태로 변경
MyEntity detachedEntity = entityManager.find(MyEntity.class, entityId);
entityManager.detach(detachedEntity);

// Detached 상태의 엔티티 수정
detachedEntity.setName("Updated Name");

// 수정된 내용이 반영된 새로운 엔티티를 영속성 컨텍스트에서 관리
MyEntity managedEntity = entityManager.merge(detachedEntity);
managedEntity.setAnotherProperty("Updated Property");

transaction.commit();

기존 Entity는 detached가 되고, update한 내용인 Entity는 managed 상태가 된다.

 

 


트랜잭션 - 지연 쓰기, 캐시

 

 

✔ @Transactional

트랜잭션이 종료되면 영속성 콘텍스트를 DB에 반영하고, 종료한다. (생명주기가 같다고 본다.)

반영할 sql을 영속성 콘텍스트에 모아두었다가 한 번에 처리해주는 역할

 

 

 

✔ flush()

영속성 컨텍스트를 DB와 동기화한다. (Managed와 DB를 맞춘다.)

(영속성 컨테이너에 Entity는 소멸되지 않는다.)

1. 트랜잭션 종료

2. flush() 호출

3. jpql 쿼리 동작

 

 

 

✔ AutoFlush 

jpql 쿼리 실행시 자동으로 flush() -> jpql 동작

jqpl을 사용하면 DB에 접근하게 되는데, 영속성 컨텍스트 내용을 반영하고, 다시 가져온다.

동작에 이상없도록 하기 위한 JPA의 설정이다.

 

 

 

✔ save()

save()는 하나의 트랙잭션이다(@Transactional Propagation required가 달려있다.)

트랜잭션이므로 호출 시점에서 영속성 콘텍스트 내용을 DB에 반영하고, 종료한다.

 

하지만 상위 @Transactional로 묶여있다면 save는 DB에 저장하겠다는 의미가 아니다.

상위 트랜잭션에 묶여 사용된다. (Propagation)

 

save에서 persist & merge의 이해

persist - 새로운 객체를 영속성 콘텍스트에서 등록 관리한다. 트랜잭션 종료 시 insert

merge - detach Entity를 manage상태로 만든다. 트랜잭션 종료 시 update

 

Detach Entity는 단순히 볼게 아니다.

처음 key 1을 갖고있는 Entity는 manage상태이고

같은 키를 갖는 새로 생성한 Entity는 detach 상태가 된다. (키가 같기 때문에)

키가 같은 Entity는 detach상태이고, merge가 동작하여 update가 된다.

기존의 Entity를 조회하여 set...()으로 값을 변경했을 때도 마찬가지로 detach Entity상태가 되고, merge 동작하게 된다.

 

void 영속성상태테스트(){
    // 해당 key 가 없으면 insert
    MyUser myUser = new MyUser();
    myUser.setId(1L);
    myUser.setName("Park");
    myUserRepository.save(myUser); // insert
    System.out.println(myUserRepository.findById(1L));

    // 해당 key 가 있으면 update
    MyUser myUser1 = new MyUser();
    myUser1.setId(1L);
    myUser1.setName("Park123");
    myUserRepository.save(myUser1); 
    System.out.println(myUserRepository.findById(1L));

    // 해당 key 가 있으면 update
    MyUser myUser2 = myUserRepository.findById(1L).orElseThrow(RuntimeException::new);
    myUser2.setName("Park456");
    myUserRepository.save(myUser2); 
    System.out.println(myUserRepository.findById(1L));
}

첫번째 save는 insert

두번째, 세번째 save 는 update가 동작한다. save()에서 select로 해당 키를 가져와서 merge -> update한다.

간단하게 새로운 id라면 insert, 기존 DB에 있는 id라면 update

 

 

 

✔ 예외처리

checked - 컴파일 타임 때 알 수 있으므로 무조건 예외 처리된다는 전제하에 RollBack 되지 않고 DB에 반영된다. (실제 예외가 발생해도 commit)

unchecked - 개발자가 놓칠 수 있으므로 예외 발생 시 RollBack

 

 

 

✔ Transaction Propagation

(default) - required 상위 트랜잭션이 있다면 유지해서 사용, 없다면 생성

support - 상위 트랜잭션이 있다면 사용, 없다면 없이 진행

notSupport - 해당 영역은 무조건 트랜잭션 없이 사용

requires_new 트랜잭션을 새로 생성해서 사용

 

nested 같은 트랜잭션을 사용하지만 종속된 트랜잭션은 상위에 영향을 주지않는다. 상위로부터는 영향받는다.

 

mandatory - 상위 트랜잭션이 있어야하고, 없으면 오류발생

never - 상위 트랜잭션이 없어야하고, 있으면 오류발생

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

Spring boot 기준 Filter는 Spring 외부에 있지 않다.

톰캣이 내장되어 Filter도 Di가 가능.

 

 

interceptor는 dispatcher servlet과 controller 사이에서 요청과 응답을 가공, 처리하고자 할 때 사용한다.

 

 

 

interceptor와 filter

filter는 모든 요청과 공통기능에 적용할 사항

interceptor는 선별한 요청과 응답에 대해 적용할 세부 사항

 

 

interceptor 와 AOP

interceptor는 URL을 통해 적용할 메서드를 선별

AOP는 포인트컷을 통해 적용할 메서드를 선별

 

 

 


 

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Login {
    
}

메서드에서 사용할 Login 어노테이션

 

 

 

 


 

 

 

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;

            if (hm.hasMethodAnnotation(Login.class)){
                log.info("true");
                return true;
            }
        }
        log.info("false");
        return false;
    }

LoginInterceptor

인터셉터의 preHandle에서 URL 매핑  메서드에 Login어노테이션이 있는지 확인한다.

 

return true이면 controller로 진입하여 로직을 처리한다.

return false이면 controller로 진입하지 못한다.

 

 

HandlerInterceptor의 메서드

preHandler() - 컨트롤러 로직 처리 전

postHandler() - 컨트롤러 로직 처리 후

afterCompletion() - 뷰가 렌더링된 이후에 호출된다.

예외가 발생하면 postHandler()는 동작하지 않지만, afterCompletion()은 동작한다.

 

Object handler

URL 로 접근하는 매핑된 메서드 

HandlerMethod로 형변환하여 매핑 메서드 정보를 읽어낼 수 있다.

 


 

 

 


@Configuration
public class WebMvc implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor());
    }
}

작성한 LoginInterceptor 인터셉터를 WebMvcConfigurer 에 등록한다.

WebMvcConfigurer는 어플리케이션 구동시 실행된다.

 

 

@Slf4j
@Configuration
public class WebMvc implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")// 적용할 url 패턴
                .excludePathPatterns("/ic/get2")  // 배제할 url 패턴
                .order(1);
    }
}

인터셉터를 적용할 URL 패턴을 적용하고, 제외할 수 있다.

인터셉터의 적용 순서도 적용할 수 있다.

 

URL pattern

/** [ /모든 경로 ] 

ex) user/board/1

 

/*   [ /경로 하나 ] 

ex) /ic , /user, /board

 

 

 


 

@Controller
@RequestMapping("/ic")
public class Icontroller {

    @GetMapping("/get")
    @ResponseBody
    @Login
    public String test(){
        return "hello interceptor";
    }
}

Login체크를 진행할 컨트롤러 메서드에 @Login을 붙여준다.

 


 

 

 

interceptor를 이용한 로직은 목적에 따라 달라질 수 있다.

 

 

 

 

 

 

 

이전에는 Filter가 스프링 컨테이너에 등록되지 않아, Filter에서 DI를 이용하는 것이 불가능 했다.

(Spring boot 기준) Filter 구현체를 Bean으로 등록하므로, 현재는 가능하다.

 

 

@Component
@RequiredArgsConstructor
public class FirstFilter implements Filter {

   private final FilterDiTestService filterDiTestService;

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      System.out.println("필터 동작");
      filterDiTestService.test();
      chain.doFilter(request,response);

   }
}

 

 


 

 

 

 

 

반응형
반응형

 

도커

도커는 컨테이너때문에 쓰는 것이다.

컨테이너는 어느 환경에서건 같은 환경을 구축할 수 있다.

 

기존 가상머신 방식이 비효율적인 이유

하나의 운영체제에 여러 가상머신을 설치해서 각 가상머신마다 다른 버전, 다른 구성으로 software 구축할 수 있다.

하지만 각 가상 머신 마다 OS가 필요하므로 메모리, 드라이브 공간을 낭비한다.

 

컨테이너 방식이 효율적인 이유

하나의 운영체제에 하나의 docker engine을 설치하여 여러 컨테이너를 도커가 관리한다.

docker engine에서 동작하는 컨테이너들은 가상 머신보다 빠르고 효율적이다.

 

이미지로 만들어서 어떤 환경에서든 이미지가 같으면 같은 컨테이너가 생성된다.

공유, 재구축, 배포가 쉬워진다.

 

 

설치

window10 pro 이상이면 docker desktop (window10에서 제공하는 가상 머신 사용)

linux는 도커 엔진 설치가 간단하다.

 

 


이미지와 컨테이너

 

이미지 -> 클래스

docker hub에 이미 만들어진 이미지 사용하거나 커스텀, 그 위에 레이어를 쌓는 방식

이미지는 빌드되면 닫힌다. ReadOnly

변경사항을 적용하려면 이미지를 다시 빌드해줘야 한다.

 

 

컨테이너 -> 인스턴스

컨테이너는 하나의 머신으로 생각, shell로 안에서 작업을 진행할 수 있다. 

하나의 이미지로 여러개의 컨테이너 인스턴스를 실행할 수 있다.

컨테이너끼리는 전혀 관계가 없다.

컨테이너마다 버전을 다르게 할 수 있다.

 


 

 

도커 이미지 만들기 [Dockerfile]

 

FROM   base 이미지 지정

ARG   이미지 빌드타임 KEY=VALUE로 사용할 수 있다.

ENV  PORT 80      환경 변수 설정, 사용시에는 $PORT, 런타임시에도 사용가능

 

WORKDIR   [컨테이너에서 작업폴더가 되는 곳    COPY, RUN 등은 이곳을 기준으로 동작]

COPY   [이미지에 들어갈 로컬 파일]  [WORKDIR이 기준] 

RUN   이미지를 빌드(생성)할 때 마다 실행할 명령어

 

 

컨테이너가 된 이후

 

EXPOSE $PORT         포트 80 노출, host와 포트 포워딩 필요

CMD 컨테이너가 실행될 때 동작할 명령어 [배열로 명령어 작성, shell이 있다면 string 가능]  

 

 

 

Dockfile 한줄 한줄이 layer다.

변경이 자주되는 layer를 아래에 두면, 윗 layer는 이전 레이어로 caching 된다.

변경 사항이 일어난 layer 밑은 전부 다시 빌드 작업을 진행한다.

 

역 슬래시를 이용해서 여러줄 작성이 가능.

 

 


데이터 volumes & bind mounts

 

데이터를 도커가 관리하고, 직접 접근은 할 필요가 없는 경우

 

✔ 익명 볼륨

컨테이너가 삭제되면 같이 제거된다.

하나의 컨테이너에서 데이터 사용

명령어: -v 볼륨경로 (볼륨명을 주지않는다. 컨테이너를 생성할 때 지정)

 

✔ 네임드 볼륨

컨테이너가 삭제되도 유지된다.

여러 컨테이너에서 데이터를 공유, 생성, 유지하고 싶을 때

명령어:-v 볼륨명:볼륨경로 (볼륨명을 준다. 컨테이너를 생성할 때 지정)

 

 

 

 

 

✔ bind mounts

host에서 직접 접근해서 사용할 필요가 있는 경우. 주로 편집을 위해 사용

도커와 host가 같이 사용하는 바인딩하여 관리하는 데이터

명령어: -v 왼쪽경로디렉토리(로컬):오른쪽 경로디렉토리(컨테이너)

 

 

bind mounts주의점

도커 컨테이너 내부 폴더보다, bind mount가 우선된다. (덮어씌운다)

 

컨테이너 내부가 bind mounts된 로컬 폴더로 뒤집어 씌어지면서 이미지 빌드할 때 설정한 종속성이 없어졌다.

덮어씌워지기 전에 이때 필요한 종속성 이미지 파일들을 익명 볼륨에 넣어 유지시켜줘야 한다.

 

도커파일에 설치명령은 먼저 동작하여 익명 볼륨에 관리시키고 (충돌이 발생하면 도커가 경로가 긴 볼륨을 먼저 보고 적용한다.)

로컬파일을 덮어씌우는 방식

 

 

컨테이너에서 유지되어야 하는 것

DB 데이터

서버 log 등...

 


네트워킹

 

✔ 컨테이너 app <ㅡ> host DB 연동

호스트 database와 컨테이너 app이 통신할 수 있다.

host.docker.internal

 

✔ 수동 컨테이너 app <ㅡ> 컨테이너 DB 연동

컨테이너 app에서 DB를 연동

database 컨테이너를 띄우고

docker container inspect로 [ip:port]를 확인한다.

 

✔ 더 개선된 컨테이너 app <ㅡ> 컨테이너 DB 연동

컨테이너들을 하나에서 네트워크로 사용하면 된다. 도커에서는 이러한 네트워크를 제공한다.

docker network create [네트워크명]

컨테이너를 생성할 때 --network [네트워크명]

컨테이너 app에서 DB를 연동하는 코드에 [ip]대신 [컨테이너DB명]:port 를 입력

 

도커 네트워크가 같다면 컨테이너 명으로 접근할 수 있다.

 

내부에서 사용되는 컨테이너 DB는 컨테이너 생성 명령어에서 -p 작업을 해주지 않아도 된다. 

외부에서 요청을 받게되는 컨테이너 app은 포트번호를 노출시켜줘야 한다.

 

 


docker compose

 

자체 네트워크로 여러 컨테이너를 동시에 띄우고 --rm default로 제거한다.

자체 네트워크를 사용하므로 네트워크를 지정해주지 않아도 된다.

 

이미지를 빌드하고, 컨테이너를 실행하는 과정을 한번에 과정으로 실행

 

docker-compose up

docker-compose down

 

docker-compose run [services 등록명]

일부만 컨테이너로 실행할 경우

 

docker-compose.yml

version: '3.4'

services:
  webmvc:
    image: eshop/webmvc
    environment:
      - CatalogUrl=http://catalog-api
      - OrderingUrl=http://ordering-api
      - BasketUrl=http://basket-api
    ports:
      - "5100:80"
    depends_on:
      - catalog-api
      - ordering-api
      - basket-api

  catalog-api:
    image: eshop/catalog-api
    environment:
      - ConnectionString=Server=sqldata;Initial Catalog=CatalogData;User Id=sa;Password=[PLACEHOLDER]
    expose:
      - "80"
    ports:
      - "5101:80"
    #extra hosts can be used for standalone SQL Server or services at the dev PC
    extra_hosts:
      - "CESARDLSURFBOOK:10.0.75.1"
    depends_on:
      - sqldata

  ordering-api:
    image: eshop/ordering-api
    environment:
      - ConnectionString=Server=sqldata;Database=Services.OrderingDb;User Id=sa;Password=[PLACEHOLDER]
    ports:
      - "5102:80"
    #extra hosts can be used for standalone SQL Server or services at the dev PC
    extra_hosts:
      - "CESARDLSURFBOOK:10.0.75.1"
    depends_on:
      - sqldata

  basket-api:
    image: eshop/basket-api
    environment:
      - ConnectionString=sqldata
    ports:
      - "5103:80"
    depends_on:
      - sqldata

  sqldata:
    environment:
      - SA_PASSWORD=[PLACEHOLDER]
      - ACCEPT_EULA=Y
    ports:
      - "5434:1433"

  basketdata:
    image: redis

 

Dockerfile을 만들어서 참조해서 사용해도 되고, docker-compose에 모든 내용을 작성해도 된다.

 


Docker util container

 

로컬에 환경설정을 하기 위해 사용되는 이미지와 컨테이너

 

도커 이미지를 기반으로 컨테이너화를 할 때,

이미지마다 해당되는 명령어를 사용하여, 추가적인 환경 설정과 툴을 설치해줄 수 있다.

 

바인드 마운트를 같이 진행하면 로컬에 환경 설정이 된다.

 

 

주의점

docker run ... [명령어] 를 실행하면

Dockerfile의 [CMD 명령어]를 덮어씌운다.

이를 방지하고자 [ENTRYPOINT 명령어]를 사용하면 ENTRYPOINT 명령어 뒤에 이어서 진행한다.

 

 

 


Docker 배포 시

 

도커를 이용해 배포할 때는 바인드 마운트 사용을 권장하지 않는다.

도커 컨테이너의 기본 아이디어는 컨테이너의 캡슐화이고,

바운드 마운트는 개발할 때의 편의성으로 사용한다.

 

AWS ECS를 이용하면 컨테이너가 동일 머신에서 동작할 것이 보장되지 않기 때문에,

도커 네트워크를 사용할 수 없다.

환경 변수 설정으로 ip를 지정하게 하자.

 

 


명령어

 

docker ps -a 모두 보기

 

 

이미지 명령어

docker build

-t repository명칭:tag명칭

-f 경로 

도커파일위치 보통 .

 

 

 

컨테이너 명령어

docker run   새로운 컨테이너 실행  

docker run -i   stdin 열린 상태로 사용

docker run -t   터미널을 생성

 

docker start    컨테이너를 재실행  

docker stop     컨테이너 중지

docker rm        컨테이너를 완전히 제거

 

--name      컨테이너 이름 지정

docker run -p 80:80 이미지명      포트연결

 

 

 

attach & detach

실행중인 컨테이너는 attach, detach 할 수 있다.

attach 모드면 실행중인 container log를 계속 수신

 

detach모드:   -d 추가

attach모드:   -a 추가

 

다시연결:   docker attach 컨테이너명

attach 컨테이너 종료시키지 않고 나오기:   ctrl + p + q

로그보기: docker [-f] logs 컨테이너명

 

 

 

interactive terminal

간단한 프로그램 실행

-it

 

shell로 접근

docker exec -it 컨테이너 bin/bash  

 

 

 

 

 

 

 

 

반응형

' > docker' 카테고리의 다른 글

Docker 다시보기 정리  (1) 2023.10.18
[Docker] 도커로 스프링 프로젝트 배포하기  (0) 2022.10.18
반응형

 

 

resoruces에 위치한 data.json을 읽어야 한다.

 

 

 

{
  "hello": "123"
}

data.json의 내용은 다음과 같다.

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");
        BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));

        // br.readLine() 이 null 인지 검사할 때 한번 사용되므로 String 에 먼저 저장해둬야한다.
        String s = "";
        while((s = br.readLine()) != null){
            System.out.println(s);

        }
    }
}

스프링에서 resources 파일을 위해 사용되는 ClassPathResource를 사용하면 파일 경로를 src/main/resoruces/부터 바로 읽을 수 있다.

 

 

 

 

File file = new File("src/main/resources/data.json");

ClassPathResource를 안쓰고 경로를 전부 적어서 읽어도 되지만, 경로가 달라질 가능성이 있으므로 ClassPathResource 권장

 

 

 

 

사용법

ClassPathResoruce의 getInputStream() 메서드를 사용하여 InputStream으로 변환한다.

InputStream를 문자를 위한 Reader로 바꾸는 InputStreamReader로 한번 더 감싼다.

InputStreamReader를 성능 향상을 위한 보조스트림 BufferedReader로 한번 더 감싼다.

 

BufferedReader는 Line 단위로 읽는다.

 

출력을 할 때는 while(br.readLine() != null)을 하게 되면, 버퍼의 내용을 한번 소모하므로 String에 저장 대입 후 null인지 검사해야 한다.

while( (s = br.readLine())!= null)

 

 

 


String s = "";
StringBuilder sb = new StringBuilder();
while((s = br.readLine()) != null){
    sb.append(s);
}
ObjectMapper om = new ObjectMapper();
JsonNode jsonNode = om.readTree(sb.toString());
System.out.println(jsonNode);

 Json으로 사용할 경우

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");

        File file = new File("src/main/resources/data.json");
        File copyFile = new File("src/main/resources/data1.json");

        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(copyFile)));

        int size = 0;
        char[] arr = new char[1024];
        while((size = br.read(arr)) != -1){
            bw.write(arr, 0, size);
        }
        
        br.close();
        bw.flush();
        bw.close();
    }
}

파일을 복사할 경우

 

 

 

 

@SpringBootApplication
public class RestApIsApplication {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(RestApIsApplication.class, args);

        ClassPathResource resource = new ClassPathResource("data.json");
        ClassPathResource resourceCopy = new ClassPathResource("data1.json");
        File file = new File(resourceCopy.getURI());

        BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));

        int size = 0;
        char[] arr = new char[1024];
        while((size = br.read(arr)) != -1){
            bw.write(arr, 0, size);
        }
        br.close();
        bw.flush();
        bw.close();
    }
}

ClassPathResource로 사용하면 build/resources/main/ 경로에 생성된다.

 

 

 

반응형
반응형

 

 

Rest API 서버에 요청을 해서 해당 값을 Controller에서 사용할 때

 

RestTemplate를 이용해서 요청을 보낼 수 있다.

* 현재는 Spring이 RestTemplate보다는 WebClient를 지원

 

@GetMapping("/test3")
public void test3() {
    // 요청할 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:8080")
            .path("/api/test")
            .build()
            .toUri();

    RestTemplate rt = new RestTemplate();
    // HTTP GET, 응답을 객체로 받기.
    String s1 = rt.getForObject(uri, String.class);

    // HTTP GET, 응답을 ResponseEntity 로 받기.
    ResponseEntity<String> responseEntity = rt.getForEntity(uri, String.class);
    System.out.println(responseEntity.getStatusCode());
    System.out.println(responseEntity.getBody());

    // HTTP GET, 응답을 JSON 을 받을 때
    // dto 객체로 받는다.
    ResponseEntity<User> responseEntity1 = rt.getForEntity(uri, User.class);


}

 

URL를 만든 후 RestTemplate를 이용하여 요청을 한다.

 

 

 

 

URI uri = UriComponentsBuilder
        .fromUriString("http://localhost:8080")
        .path("/api/test")
        .queryParam("name", "cha")
        .queryParam("age", 25)
        .build()
        .toUri();

UriComponentsBuilder의 queryParam() 메서드를 이용해 Parameter 값을 요청해줄 수 있다.

 

 

 

 

URI uri = UriComponentsBuilder
        .fromUriString("http://localhost:8080")
        .path("/api/test/{name}/{age}")
        .encode()
        .build()
        .expand("cha", 30)
        .toUri();

UriComponentsBuilder의 expand() 메서드를 이용해 PathVariable 값을 요청해줄 수 있다.

 

 

 

 

 

RestTemplate rt = new RestTemplate();
// HTTP POST, 서버에 객체를 보내서 생성하기
User user = new User("cha",33);
// uri, request 객체, response type
ResponseEntity<User> userResponseEntity = rt.postForEntity(uri, user, User.class);

POST 요청 

서버의 명세를 보고 맞게 dto 를 작성하면 된다.

 

 

 

 


RequestEntity<User> request = RequestEntity
        .post(uri)
        .contentType(MediaType.APPLICATION_JSON)
        .header("headerDummy", "asdf")
        .body(new User("user",22));

RestTemplate rt1 = new RestTemplate();
ResponseEntity<User> responseEntity = rt1.exchange(request, User.class);

HTTP header를 커스텀해서 요청을 보낼 수도 있다.

헤더를 추가할 경우 RestTemplate의 exchange() 메서드를 사용

 

 

 


kakao Oauth 요청

 

@GetMapping("/complete")
public String complete(@RequestParam String code){
    System.out.println(code);

    // uri
    URI uri = UriComponentsBuilder
            .fromUriString("https://kauth.kakao.com/oauth/token")
            .encode(StandardCharsets.UTF_8)
            .build()
            .toUri();

    // header
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.add("Content-type","application/x-www-form-urlencoded;charset=utf-8");

    // body
    MultiValueMap<String, String> map = new LinkedMultiValueMap();
    map.add("grant_type" ,"authorization_code" );
    map.add("client_id","================" );
    map.add("redirect_uri","http://localhost:8080/complete" );
    map.add("code", code );

    // Header + body
    HttpEntity<Map<String, String>> httpEntity = new HttpEntity(map, httpHeaders);

    RestTemplate rt = new RestTemplate();
    ResponseEntity<HashMap> responseEntity = rt.exchange(uri, HttpMethod.POST, httpEntity, HashMap.class);
    responseEntity.getHeaders().forEach((k, v) -> System.out.println(k + " " + v));
    responseEntity.getBody().forEach((k, v) -> System.out.println(k + " " + v));

    return "completion";

 

 

 

 

 

네이버 api 사용

 

URI uri = UriComponentsBuilder
        .fromUriString("https://openapi.naver.com/v1/search/book.json")
        .queryParam("query", word)
        .queryParam("display", 20)
        .queryParam("sort", "sim")
        .encode(StandardCharsets.UTF_8)
        .build()
        .toUri();

RequestEntity requestEntity = RequestEntity
        .post(uri)
        .header("X-Naver-Client-Id", "아이디")
        .header("X-Naver-Client-Secret", " 키 ")
        .body(" ");

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<NaverBook> responseEntity = restTemplate.exchange(requestEntity, NaverBook.class);

RequestEntity

 

HttpEntity, HttpHeaders를 사용하지 않고

header, body, contentType 을 좀 더 편하게 만들어줄 수 있다.

 

 


 

정리

 

UriComponentsBuilder: 요청할 uri를 만든다.

RequestEntity: 요청[HTTP header, body]을 만들고 RestTemplate으로 요청한다.
ResponseEntity: 응답[HTTP header, body]을 RestTemplate으로부터 받아 사용한다.
RestTemplate: HTTP 프로토콜을 이용해서 서버에 요청, 전달받은 응답을 사용할 수 있게 해준다.

 

 

반응형
반응형

 

[1] goormide 에 가입하여 Blank를 선택하여 container를 만든다.

Blank를 선택하면 기본 Ubuntu만 제공된다.

 

[2] apache2 웹 서버를 설치한다.

sudo apt-get install apache2 

 

[3] apache2 웹 서버 start 

sudo service apache2 start

 

[4-1] 컨테이너 설정에서 URL과 PORT를 지정하여 접근할 수 있다.

해당 도메인 네임으로 접근할 수 있다.

 

[4-2] 포트 포워딩를 설정하여 외부에서 접근할 수 있다.

NAT: 클라우드 서버는 하나의 공인 ip에 여러 개의 사설 ip를 두고 사용자에게 하나의 사설 ip를 제공한다. 

ifconfig 으로 사설 ip 주소 확인

사설 ip는 외부에서 접근할 수 없으므로 포트 포워딩을 이용해 [공인 ip:port]로 [사설 ip:port]에 매핑시켜줘야 한다.

 

웹 서버를 로컬에 둘 경우 공유기의 설정을 변경하여 포트 포워딩하면 되지만,

goormide에서는 컨테이너 설정에서 포트 포워딩 설정이 가능

 


 

 

 

 

 

 

반응형

' > Linux' 카테고리의 다른 글

[리눅스] 쉘 스크립트  (0) 2022.10.18
리눅스 기초  (0) 2022.08.08
반응형

 

하나의 운영체제를 여러명의 유저가 사용

각각 권한이 있고 super user가 있다.

 

 

 

 

 

Kernel - 하드웨어의 자원을 관리, process, memory, IO system...

Shell - Kernel을 실행시키는 명령어 소프트웨어 [사용자와 커널의 인터페이스] 여러 종류가 있다.

 


리눅스의 특징

 

 

리눅스 명령어는 전부 하나의 process 프로그램이다.


** [명령어] [-간략]  [--풀네임] [내용]

옵션은 process에 인자를 주는 것이다.

 

 

 

 

IO redirection    [결과를 다른곳으로 out]


ls -l > hi.txt    화면결과를 hi.txt에 복사시켜놓음 [결과를 redirection]
1>     standard output 를 redirection
2>     standard error 를 redirection
<   input

>>  기존 내용에 추가로 append
<<문자  문자를 전부입력을 받다가 이 특정 문자가 나오면 입력이 끝난다.

 



package manager  

apt 도구를 이용해서 linux package repository에서 가져와 사용한다.

일종의 앱스토어 역할과 같다.

 

 

 

파이프 라인

명령어의 출력을 다른 명령어의 입력으로 사용해서 최종 출력을 내는 것

ls - al | grep 찾을단어

 

 

 

Job Controll

ctrl + Z      background에 올려둔다. 

jobs    background 실행중인 job 목록을 본다.

fg %N     foreground로 전환

bg %N    background의 stopped를 running으로 만든다.

kill %N   background 종료

 

 

 

daemon

항상 실행되어야 하는 프로그램

etc/init.d/ 에 위치한다.

sudo service 데몬 start

sudo service 데몬 stop

 

service --status-all

 


디렉토리

 

/home - 각 사용자를 위한 디렉토리

/bin - 실행 프로그램

/sbin - 시스템 프로그렘

/etc - 설정 파일

/var - 자주 변경되는 프로그램, 로그 파일

/tmp - 임시 파일, 자동 삭제

/opt - 옵션

 

/lib, /boot, /mnt, media 시스템 디렉토리들

 

 

웹서버 설정파일들은 설정파일이 있는 곳인 /etc에 있다.

설정파일을 보면 DocumentRoot가 요청에 대한 파일을 찾는 경로

로그는 /var에 담겨진다.

 

 


 

멀티 유저

 

sudo - super user do

superuser #

일반유저 $

최초 실행시 sudo passwd 유저 로 root의 비밀번호 설정, 일반 사용자 비밀번호 설정도 같은 명령어.

 

sudo useradd -m 유저명  /  유저 추가

su - 유저명  /  유저 변경

sudo usermod -a -G sudo 유저명 / 유저에게 sudo 권한 주기

 

chmod

type | user rwx,  group rwx, other rwx | 링크 수 | user, group | 파일사이즈 | 시간 | 파일명

chmod o-r 파일명   other에 read 권한이 없도록 한다.

chmod u+w 파일명   user에 write 권한을 추가한다.

chmod 771 파일명   rwx 8진수로 가능

 

그룹

groupadd 그룹명

/etc/group  에 추가된다.

usermod -a -G 그룹명 유저명   

유저 그룹에 추가

sudo chown [owner:group] .  

디렉토리 그룹 변경

 


명령어 모음

 

세미콜론 

여러 명령어를 이어서 실행

 

ls -al 현재 디렉토리의 목록들을 보여준다. [숨김파일, 형식]

cd 디렉토리 이동

mkdir 디렉토리 만들기

pwd 현재 위치 보기

rm 삭제

cat / head / tail 파일 보기

 

mv [현재경로] [이동할경로]   // 이름 변경도 가능
cp [현재경로] [이동할경로]

ps 실행중인 프로세스를 본다.

grep 문자열 검색

find / -name 파일명 파일 검색

 

who 접속 사용자 정보

whoami 내 정보

wget url 파일 다운로드

curl url 요청

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

' > Linux' 카테고리의 다른 글

[리눅스] 쉘 스크립트  (0) 2022.10.18
[goormide] goormide 웹 서버 설치  (0) 2022.08.09
반응형

본 글은 책 자바의 정석을 ref 하여 요약한 것입니다.


https://komas.tistory.com/68

 

[JAVA]자바 익명 클래스와 람다식 정리

익명 클래스는 말 그대로 클래스의 이름이 없다는 말이다. 이름이 없으므로 생성자를 가질 수 없다. 익명 클래스를 정의하려면 상속을 받거나 인터페이스로 구현해야한다. 참조 변수에 넣어두

komas.tistory.com

 

https://komas.tistory.com/69

 

[JAVA] 자바 메소드 참조

메서드 참조란 말 그대로 다른 클래스의 메소드를 참조하여 사용한다입니다. 빌려쓴다고 이해하시면 편합니다. (호출이 아닌 구현부를 빌림) 코드로 설명하겠습니다. public class ProviderClass { public

komas.tistory.com

 

 

 

 


 

스트림

자바 8부터 추가된 함수적 스타일 처리

중간처리는 지연되고, 최종 처리가 시작되면 처리를 시작한다.

 

✔ 스트림의 종류

Stream, IntStream, LongStream, DoubleStream

 


 

✔ 스트림 만들기

List<Integer> list = Arrays.asList(1,2,5,3);
Stream<Integer> slist= list.stream();

long[] arr = {1,2,10,5,3,4,3};
LongStream sarr = Arrays.stream(arr);

 

컬렉션 프레임워크는 Stream<T>  로 변환된다.

mapToXXX()로 변환하면 IntStream, LongStream, DoubleStream로 변경할 수 있다.

 

기본형 배열은 type에 맞게  IntStream, LongStream, DoubleStream 로 변환된다.

객체 배열일 경우 Stream<T> 로 변환된다.

 

 

객체는 Stream<T>

기본형 배열은 IntStream, LongStream, DoubleStream

 

 


 

✔ 중간 처리 함수

매핑, 필터링, 정렬

 

 

skip(n)

개수만큼 건너뛴다.

 

 

limit(n)

개수만큼 제한한다.

 

 

peek(Consumer)

forEach와 비슷한 역할을 하는 중간 연산이라고 보면 된다.

보통 단순히 중간 조회를 위해 사용됨

 

 

distinct()

중복을 제거한다. equals() 값이 같으면 제거, IntStream, LongStream, DoubleStream 일 경우 값이 같으면 제거

 

 

filter(Predicate)

true인 값만 남기고 제거한다.

 

 

map(Function)

요소를 대체하는 새로운 요소로 구성된 스트림을 만든다. (보통 type변환, 일괄 연산 등)

 

map() -> (객체 -> 객체), (기본형 -> 기본형), 값 변환

mapToInt -> IntStream으로 변환

mapToLong -> LongStream으로 변환

mapToDouble -> LongStream으로 변환

mapToObj -> 객체로 변환

type 변환과 값 변경도 가능

 

List<Integer> list = Arrays.asList(1,2,5,3);
Stream<Integer> slist= list.stream();
IntStream is = slist.mapToInt(e->e);

long[] arr = {1,2,10,5,3,4,3};
LongStream sarr = Arrays.stream(arr);
Stream<Integer> bs = sarr.mapToObj(e -> (int) e);

 

flatMap()

스트림의 요소가 배열일때  flatten Stream을 만들 때 사용한다.

 

 

boxed()

IntStream, LongStream, DoubleStream  ---->   Stream<T>

 

 

sorted(Comparator)

정렬을 제공한다.

Comparable이 제공된 객체를 사용하거나, Comparator를 제공해주면 된다.

* IntStream, LongStream, DoubleStream 일 경우 인수를 제공하지 않으면 오름차순으로 정렬, 내림차순을 원할 경우 Comparator.reverseOrder()

 

* thenComparing() - 정렬 추가가 가능

* reversed() - 내림차순 가능

 

List<Student> students = Arrays.asList(
                new Student("Lee", 1),
                new Student("Lee", 2),
                new Student("Park",1),
                new Student("Park",2),
                new Student("Kim",2)
        );

       List<Student> sortedList= students.stream().sorted(
               Comparator.comparing(Student::getName)
               .thenComparing(Student::getNumber)
               .reversed()
               ).collect(Collectors.toList());

        System.out.println(sortedList);
        
//        [Student{name='Park', number=2}, Student{name='Park', number=1}, 
//        Student{name='Lee', number=2}, Student{name='Lee', number=1}, Student{name='Kim', number=2}]

 


 

✔ 최종 처리 함수

반복, 카운팅, 평균, 총합

최종 처리 함수의 반환 타입은 기본형이거나 OptionalXXX

 

 

forEach(Consumer)

요소를 하나하나씩 처리한다.

 

 

sum() - IntStream, LongStream, DoubleStream

반환 타입

타입에 맞는 기본형

 

 

count() - IntStream, LongStream, DoubleStream

반환 타입

long

 

 

max(), min() - IntStream, LongStream, DoubleStream

Stream<T> 일 경우 인수로 Comparator 필요

IntStream, LongStream, DoubleStream 일 경우 필요하지 않다.

반환 타입

Stream<T>일 경우 Optional<T>

IntStream, LongStream, DoubleStream 일 경우 OptionalXXX

 

 

average() - IntStream, LongStream, DoubleStream

반환 타입

OptionalDouble

 

 

findFirst()

반환 타입

Stream<T>일 경우 Optional<T>

IntStream, LongStream, DoubleStream 일 경우 OptionalXXX

 

 

reduce(identify, BinaryOperator)

identify는 요소가 없을 때 반환하는 값(Default)

BinaryOperator에 요소 하나하나씩 누적해가며 계산한다.

반환 타입

identify를 지정하면 기본형으로 반환

지정하지 않으면 OptionalXXX로 반환

 

 

allMatch(Predicate)

모든 요소가 일치하면 true 반환

anyMatch(Predicate)

하나의 요소라도 true라면 true 반환

noneMatch(Predicate)

모든 요소가 false라면 true 반환

 


 

✔ collect(Collector)

collect 함수는 내부에 수집기를 정의해줘야 한다.

보통 Collectors(수집기 구현체)에 구현된 메서드들을 활용한다.

 

Student[] students = {new Student("Lee",23), new Student("Park",26), new Student("Kim",26)};

List<Integer> ageList = Arrays.stream(students)
        .map(e-> e.age)
        .collect(Collectors.toList());
System.out.println("ageList " + ageList);

Set<Integer> ageSet = Arrays.stream(students)
        .map(e-> e.age)
        .collect(Collectors.toSet());
System.out.println("ageSet " + ageSet);

TreeSet<Integer> ageTreeSet = Arrays.stream(students)
        .map(e -> e.age)
        .collect(Collectors.toCollection(()-> new TreeSet<Integer>())); // TreeSet::new
System.out.println("ageTreeSet " + ageTreeSet.higher(23));

 

 

 

collect() 사용자 직접 정의

Collector(수집기) 인터페이스를 구현하기 위해선 대략 3가지의 요소가 필요하다.

 

supplier

 - 수집된 데이터를 담을 구현체 생성 (싱글 스레드는 하나, 멀티 스레드는 여러개가 생성)

accumalator

 - 생성된 객체에 원하는 요소 수집

combiner

 - 병렬 처리된 각 컨테이너 객체들을 결합 지정

 

List<Student> students = Arrays.asList(
        new Student("Lee", 1),
        new Student("Park",1),
        new Student("Kim",2),
        new Student("Cha",2)
);

List<Student> ls = students.stream().
        filter((s)-> s.getNumber() == 1).
        collect(ArrayList::new, List::add, List::addAll);

System.out.println(ls);
// [Student{name='Lee', number=1}, Student{name='Park', number=1}]

 

 

 

collect() 그룹핑

Collectors.groupingBy()

- 내부에서 key, Collector(수집기)를 지정한다.

 

Collectors.mapping(Function, Collector)

- 매핑한 후 Collector(수집기)를 사용한다.

 

List<Student> students = Arrays.asList(
        new Student("Lee", 1),
        new Student("Park",1),
        new Student("Kim",2),
        new Student("Cha",2)
);

Map<Integer, List<Student>> m1 = students.stream()
        .collect(Collectors.groupingBy(Student::getNumber)); // key
System.out.println(m1);
// {1=[Student{name='Lee', number=1}, Student{name='Park', number=1}], 2=[Student{name='Kim', number=2}, Student{name='Cha', number=2}]}


Map<Integer,List<String>> m2 = students.stream()
                .collect(Collectors.groupingBy(
                        Student::getNumber, // key
                        Collectors.mapping(Student::getName, Collectors.toList()))); // value
System.out.println(m2);
// {1=[Lee, Park], 2=[Kim, Cha]}


Map<Integer,List<String>> m3 = students.stream()
        .collect(Collectors.groupingBy(
                Student::getNumber, // key
                HashMap::new, // 구현체 지정 가능
                Collectors.mapping(Student::getName, Collectors.toList()))); // value
System.out.println(m3);
// {1=[Lee, Park], 2=[Kim, Cha]}

 

 

 

partioningBy(Predicate, Collector)

true인 것만 적용하여 분리

반환 타입은 <boolean>

 

 

Collectors 수집기 함수

averagingDouble() 

counting()

maxBy(Comparator)

minBy(Comparator)

summingInt, Long, Double()

 

reducing(identity, BinaryOperator

joining(구분자) - 문자열 결합

 

 

 

collect 개략도

collect( 수집기 )

collect( Collectors.groupingBy ( 키, 수집기 ) )

collect( Collectors.groupingBy ( 키, Collectors.mapping ( 매핑, 수집기 ) )

 

 

 


 

반응형
반응형

본 글은 책 자바의 정석을 ref 하여 요약한 것입니다.


 

프로세스

실행 중인 프로그램

하나의 프로세스는 하나 이상의 스레드 이상을 갖는다.

 

스레드

실제 작업을 수행하는 단위

 

멀티태스킹

여러 프로세스를 동시에 실행

 

멀티쓰레딩

여러 스레드가 동시에 실행

 

* 하나의 cpu코어는 번갈아서 여러 작업을 동시성 처리한다. 

매우 빠른 속도로 여러 스레드를 처리하기 때문에 병렬적으로 처리하는 것처럼 보인다.

 


 

구현

Thread를 상속받아서 run()을 오버 라이딩

Runnable을 구현해서 run()을 오버 라이딩, Thread 생성자의 인수로 제공

 

실행

start() 메서드로 시작, 사실 시작하는 것은 아니고 실행 대기상태가 된다.

* start()는 새로운 call stack을 생성한 후, run()을 호출한다.

* 한 스레드에서 예외가 발생해도 다른 스레드에 영향을 미치지 않는다.

종료된 스레드는 재실행이 불가능하다. 새로 생성하여 start() 해야 한다.

 


 

스레드 그룹

폴더 안에 폴더를 만들듯이 스레드 그룹에 하위 스레드 그룹을 생성할 수 있다.

쓰레드 그룹을 지정하지 않는다면 main 쓰레드 그룹에 속한다.

모든 스레드 그룹은 기본적으로 main스레드 그룹 하위에 속하게 된다.

 

데몬 쓰레드

일반 스레드가 모두 종료되면 강제적으로 종료되는 보조 쓰레드

데몬 스레드를 지정 후 start() 해야 한다. 역은 성립하지 않는다.

 


 

스레드의 실행 제어

NEW - 아직 start()되지 않은 상태

RUNNABLE - 실행 대기 상태, 실행 중

* 실행 대기열에서 저장되어 자신의 차례가 되면 실행

BLOCKED - 동기화 블록에 의해 일시 정지된 상태

WAITING, TIMED_WAITING - 일시 정지 상태

TERMINATED - 종료

 

 

static sleep()

스레드를 멈추게 한다.

항상 try - catch문으로 예외 처리해줘야 한다.

interrupt()가 호출되면 깨어나 실행 대기 상태가 된다.

Thread.sleep()   자신에게 적용한다. 

 

static yield()

다음 차례의 스레드에게 양보한다.

Thread.yield()   자신에게 적용한다.

 

join()

ThreadB.join()

A인 자신을 멈추고, ThreadB가 먼저 작업을 수행하도록 한다.

ThreadB가 작업을 마치면 수행한다.

항상 try - catch문으로 예외 처리해줘야 한다.

sleep()과 같이 interrupt()로 대기상태에서 벗어날 수 있다.

 

interrupt()

일종의 flag

1. 스레드를 멈추라고 요구한다.

** 강제로 멈추진 못하고 interrupted(), isInterrupted() 상태를 true로 바꾼다.

2. 일시정지 상태인 스레드를 실행 대기 상태로 바꾼다.

** 일시정지 상태에서 실행 대기 상태로 바뀌면, interrupted(), isInterrupted() 상태를 false로 바꾼다.

 


 

스레드의 동기화

공유 데이터를 사용하는 곳에 임계 영역을 설정하여 하나의 스레드만 접근할 수 있도록 한다.

1. 메서드 전체를 임계 영역으로 지정

2. 특정한 영역을 임계 영역으로 지정

** 공유 데이터가 있는 곳에 synchronized를 선언한다.

 

wait(), notify(), notifyAll()

** 동기화 블록 내에서만 사용할 수 있다.

** Object에 정의되어 있다.

 

번갈아 작업하는 교대작업 시

 

wait()

현재 공유 객체를 사용중인 쓰레드를 정지

스레드가 락을 반납하고 대기한다.

 

notify()

이전에 작업을 중지했던 임의의 스레드를 실행 대기 상태로 만든다.

오래 기다린 스레드가 락을 얻는 것을 보장하진 못한다.

 

** 제공자 스레드가 데이터를 충분히 제공했다면 wait(), 소모자 스레드를 깨운다. notify()

** 소모자 쓰레드가 데이터를 전부 소진했다면 wait(), 제공자 쓰레드를 깨운다. notify()

 

 

Lock과 Condition을 이용한 동기화

ReentrantLock 

** 락을 풀고 다시 락을 얻어서 진입한다.

** 수동으로 락을 걸고, 해제해야 한다.

 

ReentrantReadWriteLock

** 읽기 락이 걸려있다면 다른 읽기 락도 중복해서 접근할 수 있다.

 

StampedLock

** 낙관적 읽기 락이 추가됐다.

** 낙관적 읽기 락은 쓰기 락에 의해 바로 풀린다.

** 낙관적 읽기 락이 실패했다면, 읽기 락을 걸어서 읽어와야 한다.

 

 

Condition

스레드 종류에 따라 구분하여 통지할 수 있게 한다.

락으로부터 여러 Condition을 생성한다.

Condition을 제공자, 소모자에게 부여하여 통지 대상을 명확하게 한다.

 


 

volatile

싱글 코어에서는 문제가 없지만, 멀티 코어에서는 캐시 메모리에 저장된 값을 사용하여 문제가 발생한다.

volatile을 사용하게 되면 캐시가 아닌 메모리에서 읽어오기 때문에 불일치가 해결된다.

synchronized 또한 캐시와 메모리 간의 동기화가 이루어지기 때문에 불일치가 해결된다.

 

JVM이 4byte로 데이터를 처리하기 때문에 int 이하에서는 한 번에 읽고 쓰는 것이 가능하지만,

long, double은 8byte이기 때문에 데이터를 처리하는 중에 다른 스레드가 끼어들 수 있다.

이러한 문제를 해결하기 위해 변수에 volatile을 선언해주어 원자화로 만들어 주면 된다. (동기화는 아니다.)

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

본 글은 책 자바의 정석을 ref 하여 요약한 글입니다.


 

제네릭스

컴파일 타임 시 타입 체크를 해주는 기능

다룰 객체의 타입을 미리 명시하여 형변환을 제거

추가적인 형변환을 하지 않아 성능이 향상된다.

컴파일 이후부터는 지정된 타입으로 바뀐다.

클래스와 메서드에 선언할 수 있다.

 

 

제한

static 에는 사용 불가능 (클래스 변수는 공유가 되는데, 인스턴스마다 제네릭 타입이 달라지므로)

new 배열 생성 코드 불가능 (new 연산자는 컴파일 타임 때 타입을 정확히 알아야 함)

 

 

형변환

지정된 제네릭 타입의 자손을 사용하면 형변환이 된다.

void add(T t)

*T가 부모일 경우 T의 자손도 인수로 사용할 수 있다.

 

 

제한된 지네릭 

<T extends 부모 클래스>

부모 클래스와 자손 클래스들만 사용 가능

불필요한 다른 타입의 사용을 막는다.

사용할 수 있는 필드, 메서드는 부모 클래스의 것으로 제한된다.

 

 

✔ 제네릭 메서드

메서드의 리턴 타입 앞에 제네릭 타입을 지정하며 지역변수와 비슷하게 사용된다.

클래스의 제네릭 타입과는 관련이 없다. 메서드 내부에서만 사용된다.

<사용할 제네릭 타입> 리턴타입 메서드명(매개인자)

 

 

 

 

✔ 와일드카드

메서드의 리턴 타입, 매개 인자에 사용된다.

제네릭 타입이 불공변이기 때문에 사용한다.

 * 불공변: 변하지 않는다.

 * List<Object> list = new ArrayList<Integer>()

 * 컴파일 안된다. 제네릭은 형변환 개념이 없다 <Object> 와 <Integer>는 아무 상관이 없다.

 

제네릭 형 변환

Clazz<String>  <==>  Clazz<Object>   형변환 // 불가능 (불공변)

Clazz<?>   <==>   Clazz<Object>   형변환  //  가능 (와일드 카드 사용 이유)

논제네릭과 제네릭   형변환  //  가능

 

 

메서드 인자 제네릭 타입

메서드는 타입이 달라야 오버 로딩이 가능한데, 지네릭 타입이 불공변이라 오버로딩이 되지 않는다.

 

void add(FruitBox<Fruit> f)   // FruitBox<Fruit> 만 인수로 들어올 수 있다.
void add(FruitBox<Apple> f)    // FruitBox<Apple> 만 인수로 들어올 수 있다.
오버로딩 불가능

어떤 제네릭 클래스가 들어올지 몰라서,
void add(FruitBox<?> f)

 

 

와일드카드는 메서드의 인수가 제네릭 타입만 다를 때 여러 제네릭 타입이 사용될 수 있도록 한다.

와일드카드를 사용하면 제네릭 타입이 무엇인지 알 수 없다.

정확한 제네릭 타입을 몰라서 확실하게 사용할 수 있는 기능만 사용한다.

 

메서드 인자로 와일드 카드를 사용할 때 메서드 구현부에서의 제한

 

(1)  <?> 제한 없음

 

(2)  <? extends T> T와 그 자손들만 가능

상한 제한으로 하게 되면 출력은 할 수 있고, 입력이 불가능하다.

출력: T의 출력 메서드를 자손 클래스들이 모두 상속했거나, 오버라이딩하여 가능하다.

입력: 제네릭 클래스가가 T의 하위 타입을 사용할 경우 T를 입력할 수 없다.

 

(3)   <? super T> T와 그 조상들만 가능

하한 제한으로 하게 되면 반대로 출력을 할 수 없고, 입력만 할 수 있다.

입력: T와 그 조상들을 타입 변수로 제한하므로 T 타입은 무조건 입력이 될 수 있다.

출력: 상위 타입이 T타입의 메서드를 갖고 있지 않을 수 있다.

 

(가능한 경우에도 자바에서는 상한 제한에서 입력, 하한 제한에서의 출력을 불가능하게 막아둠)

 

 

 

 

✔ 제네릭 타입의 제거

하위 자바 버전과의 호환성을 유지하기위해 컴파일때 제거

 


 

열거형 (Enum)

 

서로 관련있는 상수를 편하게 관리하기 위해 등장

사용할 도메인 범위를 지정할 수 있다는 장점을 갖는다.

값뿐만 아니라 타입까지 사용하기 때문에 정확한 비교가 가능하다.

 

메서드

ordinal() 열거형 상수의 순서를 반환

name() 열거형 상수의 이름을 반환

values() 열거형 상수들을 배열에 담아서 반환

 

지정된 멤버 추가

enum Direction { EAST(-1), SOUTH(5), WEST(100), NORTH(10);

private final int value;

Direction(int value){

     this.value = value;

}

불연속 열거형 값을 사용하기 위해선 생성자와 인스턴스 변수가 필요하다. (생성자는 private가 생략됨)

상수에 두가지 값을 사용하려면 두가지의 인스턴스 변수가 필요하다.

 

열거형 상수의 이해

열거형 상수 하나하나가 객체다.

객체의 주소가 바뀌지 않으므로 == 비교가 가능하다.

 

 

 


 

어노테이션

주석처럼 프로그램에 영향을 미치지 않으면서, 다른 프로그램에게 유용한 정보를 제공

 

 

메타 어노테이션 - 어노테이션을 위한 어노테이션

 

@Target

적용 가능한 대상을 지정

 

@Retention

어노테이션이 유지되는 기간

Runtime으로 지정시 Reflection을 통해 정보를 읽어서 처리할 수 있다.

 

@Inherited

자손 클래스에 상속되도록 한다.

 

@Repeatable

해당 어노테이션을 여러번 지정 가능

 

 

 

 

어노테이션 타입 지정

어노테이션에 선언된 메서드를 요소라고 한다.

요소들의 값을 빠짐없이 적어주어야 한다. (default가 지정되어 있으면 적어주지 않아도 된다.)

요소가 하나일 경우 이름 생략 가능

 

[규칙]

메서드 선언에는 매개변수를 사용할 수 없다.

예외를 선언할 수 없다.

메서드의 반환형에는 기본 타입, 문자열, 배열, 열거형, Class<> 클래스, 어노테이션 

상수를 선언할 수도 있다.

 

 

 

 

반응형

+ Recent posts