2차 캐시

vs 1차 캐시

  • 1차 캐시는 영속성 컨텍스트를 통해 이루어진다.
  • 1차 캐시는 하나의 트랜잭션이 시작하고 종료할 때 까지만 유효하다.
  • OSIV(Open Session In View)를 이용하면 클라이언트의 요청이 들어오고 해당 요청이 끝날때 까지 1차 캐시를 유효하게하지만 애플리케이션 관점에서 DB에 대한 접근 횟수를 줄이기에는 역부족이다.

2차 캐시에 대하여

  • 애플리케이션 레벨에서 공유되므로 공유 캐시(shared cache)라 부르기도 한다.
  • 애플리케이션 종료시 까지 캐시가 유지된다.
  • 1차 캐시에 원하는 Entity가 없을 때 2차 캐시를 뒤지고, 이 때에도 없다면 DB를 조회헤 2차 캐시에 해당 Entity를 저장한다. 그리고 해당 Entity에 대한 복사본을 1차 캐시로 반환해준다. 이는 여러 곳에서 같은 객체를 동시에 수정하는 문제를 방지해준다.
  • Persistenct Unit 범위의 캐시이다.
  • 데이터베이스 기본키를 기준으로 캐시하지만, 영속성 컨텍스트가 다르면 객체의 동일성(a==b)을 보장하지 않는다.

설정방법

  1. @Cacheable
    @Entity
    public class Member {
       @Id @GeneratedValue
       private Long id;
    
       ...
    }

    와 같이 @Cacheable 어노테이션을 사용해준다.

  2. <persistencce-unit name="2ndCache">
       <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
    </persistencce-unit>

    와 같이 persistence.xmlshared-cache-mode를 설정함으로써 persistence unit 단위 캐시를 적용해준다.

'Backend > SpringBoot-JPA' 카테고리의 다른 글

@RestController 어노테이션  (0) 2021.04.29
Optimistic/Pessimistic Lock  (1) 2021.04.19
@Transactional  (0) 2021.03.21

@RestController 어노테이션

  • Spring에서 Rest API/Web API를 개발하기 위해 사용하는 어노테이션
  • @Controller@ResponseBody를 포함한다.
  • MessageConvertor가 @RestController에서 다음과 같은 중요한 역할을 수행한다.
    • 외부에서 전달받은 JSON메서드를 내부에서 사용할 수 있는 객체로 변환
    • Controller가 리턴한 객체를 클라이언트에게 JSON으로 변환해 전달
    • @EnableWebMvc를 사용하면 기본으로 제공된다. 이는 Jackson 라이브러리를 추가할 경우 기본으로 설정되어 있다. 만약 Jackson 라이브러리를 추가해 주지 않은 경우 500 오류가 발생한다.
    • @ResponseBody는 자바 객체를 HTTP 요청의 body 내용으로 매핑하는 역할을 수행하며, @RequestBody는 HTTP 요청의 body 내용을 자바 객체로 매핑하는 역할을 수행한다.
@RestController
public class MemberRestController
{
    @RequestMapping(value="api/item/new", method = RequestMethod.POST)
    public Item item(@RequestBody ItemDto item) {
        Item savedItem = itemService.join(item); 
        return savedItem;
    }
}
  • Body 부분의 ItemDto 데이터를 파라미터로 받기위해 @RequestBody를 사용함.
  • @ResponseBody@RestController에 포함되어있어, savedItem이 Body에 맵핑되어 전달된다.

'Backend > SpringBoot-JPA' 카테고리의 다른 글

2차 캐시 - @Cacheable  (0) 2021.04.30
Optimistic/Pessimistic Lock  (1) 2021.04.19
@Transactional  (0) 2021.03.21

트랜잭션 ACID 특성

  1. Atomicity
  • All or Nothing의 개념
  • 트랜잭션 중 Exception이 발생하면 해당 트랜잭션에서 진행한 모든 내용을 rollback 시켜야 한다.
  1. Consistency
  • 트랜잭션의 작업 처리 결과가 항상 일관성있어야 한다는 내용
  • 트랜잭션 진행 중 데이터가 변경되더라도 업데이트 된 내용으로 트랜잭션이 진행되는것이 아니라, 처음 트랜잭션을 진행할 때 참조한 데이터로 트랜잭션을 진행한다.
  1. Isolation
  • 둘 이상의 트랜잭션이 동시에 수행될 때, 한 트랜잭션의 수행이 트랜잭션에 영향을 주어서는 안된다.
  1. Durability
  • 트랜잭션이 성공적으로 완료되었을 때, 그 트랜잭션의 결과는 영구적으로 반영되어야 한다.

'Backend > 기타' 카테고리의 다른 글

REST API 와 Web API  (0) 2021.04.29

REST API란?

  • REST는 Representational State Transfer의 약자이고, REST AIP는 이러한 REST 형식의 API를 의미한다.
  • REST를 만족하기위한 요구조건은 다음과 같다.
    • Client-Server : HTTP를 이용함으로써 만족함
    • Stateless : HTTP를 이용함으로써 만족함
    • Cache : HTTP를 이용함으로써 만족함
    • Uniform interface
      • Resource는 URI로 식별되어야 한다.
      • Resource를 생성, 수정, 추가 시 HTTP 메시지에 표현을 해 전송해야한다.
      • 메시지는 Self-Descriptive 해야한다. JSON 메시지가 어디에 전달되는지, 이 메시지의 구성요소가 어떤 의미를 표현해야 Self-Descriptive라 부른다.
      • 애플리케이션의 상태는 Hyperlink를 이용해 전이되어야 한다.(HATEOAS) <a>태그와 같이 웹 페이지에서 제공하고 있으나 이를 API에서 제공하기는 쉽지않다.
    • Layer system : HTTP를 이용함으로써 만족함
    • Code on Demand : HTTP를 이용함으로써 만족함
  • REST의 요구조건 중 Uniform interface를 지원하는 것이 쉽지 않아 서비스에서 제공하는 API들을 Web API(HTTP API)라고 부르기도 한다.

Web API란?

  • URI는 정보의 자원을 표현해야 한다.
    • GET /members
  • 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.
  • 슬래시(/)는 계층을 나타낼 때 사용한다.
    • http://jay.com/member/1/items
    • 마지막 /는 포함하지 않는다.
    • 하이픈(``)은 URI의 가독성을 높이고자 할 때 사용한다.
    • 언더스코어(_)는 사용하지 않는다.
    • URI 경로는 소문자만을 사용한다.
    • 파일 확장자는 포함하지 않는다.

'Backend > 기타' 카테고리의 다른 글

트랜잭션의 ACID  (0) 2021.04.29

LOCK

OPTIMISTIC LOCK

  • 트랜잭션의 대부분은 충돌이 발생하지 않는다는 것을 가정하고 접근하는 방법으로 트랜잭션이 커밋하기 전까지는 충돌 여부를 알 수 없다.
  • JPA에서 OPTIMISTIC LOCK을 @Version을 사용하므로 Entity 정의 시 @Version이 명시된 필드를 추가하도록 하자. 또한, @Version이 정의되어 있다면 OPTIMISTIC LOCK을 사용할 것을 명시하지 않더라도 DEFAULT 적용 된다.
  • 이러한 OPTIMISTIC LOCK은 COMMIT 시점에 충돌을 알 수 있으며 충돌 시 다음과 같은 Exception이 발생한다.
    • javax.persistence.OptimisticLockException in JPA
    • org.hibernate.StaleObjectStateException in Hibernate
    • org.springframework.orm.ObjectOptimisticLockingFailureException in Spring

@Version

  • OPTIMISTIC LOCK 사용 시 @Version 어노테이션을 이용해 버전관리 기능을 추가해야 한다. 이는 Long, Integer, Short, Timestamp 타입에 적용 가능하다.
    • @Version
      private Long version;
  • 엔티티를 수정할 때 마다 버전이 하나씩 자동으로 증가하며, 수정 시 조회 시점의 버전과 수정 시점의 버전이 다르면 예외를 발생시킨다. 이를 통해 second lost update problem최초 커밋만 인정함으로써 방지할 수 있다. 다른 방법으로는 마지막 커밋만 인정, 충돌 내용 병합이 있으나 여기서 다룰 문제는 아니다.
    • tx-1> Member1 조회 (version 1)
    • tx-2> Member1 조회 (version 1)
    • tx-2> Member1 수정 (version 1->2)
    • tx-1> Member1 수정 (충돌 발생)
  • 엔티티 수정 시 다음과 같은 UPDATE 쿼리가 발생된다.
    • UPDATE MEMBER
      SET
         NAME=${NEW_NAME},
         VERSION=${OLD_VERSION + 1} 
      WHERE
         ID=${ID}
         AND VERSION=${OLD_VERSION}
    • 이러한 버전관리 필드는 JPA가 직접 관리하는데, 벌크 연산시에는 개발자가 관리해 주어야 한다.
    • UPDATE MEMBER m SET m.name = ${NEW_NAME}, m.version = m.version + 1

PESSIMISTIC LOCK

  • 트랜잭션의 충돌이 발생한다고 가정하고 우선 LOCK을 걸고 보는 방법이다.
  • SELECT FOR UPDATE, `` 등의 방법이 있다.
  • 데이터를 수정하는 즉시 충돌여부를 확인할 수 있으며, 다음과 같은 Exception이 발생한다.
    • javax.persistence.PessimisticLockException in JPA
    • org.springframework.dao.PessimisticLockingFailureException in Spring
  • PESSIMISTIC LOCK을 얻은 트랜잭션이 LOCK을 풀기 전까지 다른 트랜잭션들은 무한히 대기하게 되므로 다음과같이 timeout을 설정하는 것이 좋다.
    • Map<String, Object> properties = new HashMap<String, Object>();
      // timeout for 10 seconds
      properties.put("javax.persistence.lock.timeout", 10 * 1000);
      
      Member member = em.find(Member.class, id, LockModeType.PESSIMISTIC_WRITE, properties);
  • PessimisticLockScope를 통해 해당 Entity만 Lock을 걸지, 연관관계에 있는 Entity들도 Lock을 걸지 결정할 수 있다.
    • PessimisticLockScope.NORMAL: 해당 Entity만 Lock을 건다.
    • PessimisticLockScope.EXTENDED: 연관된 Entity들도 Lock이 걸린다.

JPA Lock

  • JPA 사용 시, READ COMMITTED + OPTIMISTIC LOCK을 조합하여 사용하는 것이 좋다.
  • Lock을 사용하는 대표적인 예는
    • Entity Manager: Member foundMember = em.find(Member.class, id, LockModeType.OPTIMISTIC);, em.lock(foundMember, LockModeType.OPTIMISTIC);, em.refresh()
    • Query: em.createQuery("select ~").setLockMode(LockModeTpye.OPTIMISTIC_INCREMENT);
    • @NamedQuery(name="lock", query="SELECT ~~", lockMode = OPTIMISTIC_INCREMENT)
      가 있다.

LockModeType의 종류

Lock Mode Type 설명
OPTIMISTIC LOCK OPTIMISTIC - OPTIMISTIC LOCK을 사용한다.
- @Version만 적용했을때에는 Entity 수정 시에만 버전을 확인하지만, 여기에서는 조회만 하여도 버전을 체크함으로써 트랜잭션이 종료될 때 까지 다른 트랜잭션에서 변경하지 않음을 보장해준다.
- 트랜잭션 커밋 시 버전정보를 조회(SELECT)해 Entity의 버전과 같은지 확인한다.
- DIRTY READ, NON-REPEATABLE READ를 방지한다.
OPTIMISTIC LOCK OPTIMISTIC_FORCE_INCREMENT - OPTIMISTIC LOCK과 함께 버전정보를 강제로 증가한다.
- Entity를 수정하지 않아도 트랜잭션 커밋 시 버전정보를 강제로 증가시킨다. 이 때, 조회시점의 버전과 다르면 Exception을 발생시킨다.
- Entity 수정 시 버전 UPDATE가 발생하는데, 트랜잭션 커밋 시점에도 버전정보가 증가하므로 2번의 버전 증가가 발생할 수 있다.
- 연관관계가 있는 Entity들에 대해 연관관계의 주인인 Entity의 변경이 발생하면 연관관계에 있는 Entity의 버전정보도 동일하게 변경된다. (Member 변경 시 Team 또한 Version이 같이 업데이트)
PESSIMISTIC LOCK PESSIMISTIC_READ - PESSIMISTIC LOCK, READ LOCK을 사용한다.
- 데이터를 읽기만하고 수정하지 않는 용도로 사용한다.
- shared lock으로 select ... for share와 같다.
PESSIMISTIC LOCK PESSIMISTIC_WRITE - PESSIMISTIC LOCK, WRITE LOCK을 사용한다.
- Lock이 걸린 row는 다른 트랜잭션이 수정할 수 없다.
- 내부적으로 select ... for update를 사용해 lock을 건다.
- exclusive lock
PESSIMISTIC LOCK PESSIMISTIC_FORCE_INCREMENT - PESSIMISTIC LOCK, 버전정보를 강제로 증가한다.
- PESSIMISTIC LOCK 중 유일하게 버전정보를 사용한다.
- for update nowait을 사용한다.
기타 NONE - LOCK을 사용하지 않는다.
- @Version필드가 있으면 OPTIMISTIC LOCK이 적용된다.
- Entity 수정 시, 버전 필드를 체크하면서 버전을 증가시킨다.

'Backend > SpringBoot-JPA' 카테고리의 다른 글

2차 캐시 - @Cacheable  (0) 2021.04.30
@RestController 어노테이션  (0) 2021.04.29
@Transactional  (0) 2021.03.21

@Transactional

: @Transactional 어노테이션을 사용하면 tx.begin(), tx.commit()을 자동으로 수행해주고, 예외 발생시 rollback처리를 자동으로 해 주는 편리한 기능이다.

public void register(Member member) {
    try {
      tx.begin();
      memberRepository.save(member);
      tx.commit();
    } catch (Exception e) {
      tx.rollback();
    }
}

와 같은 메소드는 @Transactional 어노테이션 사용 시

@Transactional
public void register(Member member) {
    memberRepository.save(member);
}

로 바꿀 수 있다.

 

내부 구현

: Spring은 프록시 객체를 사용해 위와 같이 tx.being()등의 코드를 삽입해 줍니다. 프록시 객체는 다음과 같이 생성되는데

public class MemberServiceProxy {
  private final MemberService memberService;
  private final TransactonManager manager = TransactionManager.getInstance();

  public MemberServiceProxy(MemberService memberService) {
    this.memberService = memberService;
  }

  public void register(Member member) {
    try {
      manager.begin();
      memberService.register(member);
      manager.commit();
    } catch (Exception e) {
      manager.rollback();
    }
  }
}

개발자가 정의한 메소드를 한번 감싼 뒤, 해당 프록시 메소드를 호출함으로써 이루어집니다.

여기서 발생할 수 있는 문제점과 같이 더욱 자세한 내용은 Spring @Transactional 사용시 주의해야할 점를 참고해 주세요.



사용할 수 있는 Options

 

Propagation

: @Transactional 어노테이션이 붙여진 메서드는 하나의 트랜잭션 안에서 진행될 수 있도록 만들어주는 역할을 한다. 그런데 트랜잭션 안에서 트랜잭션이 호출된다면 어떻게 처리할지를 결정하는것이 propagation 옵션이다. @Transactional(propagation = Propagation.NOT_SUPPORTED)와 같은 방식으로 사용할 수 있다.

옵션 설명
REQUIRED - 기본 옵션
- 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류, 그렇지 않다면 새로운 트랜잭션을 만든다.
- 중간에 자식/부모에서 rollback이 발생된다면 자식과 부모 모두 rollback 한다.
REQUIRES_NEW - 무조건 새로운 트랜잭션을 만든다.
- nested한 방식으로 메소드 호출이 이루어지더라도 rollback은 각각 이루어 진다.
MANDATORY - 무조건 부모 트랜잭션에 합류시킨다.
- 부모 트랜잭션이 존재하지 않는다면 예외를 발생시킨다.
NESTED - 부모 트랜잭션이 존재하면 부모 트랜잭션에 중첩시키고, 부모 트랜잭션이 존재하지 않는다면 새로운 트랜잭션을 생성한다.
- 부모 트랜잭션에 예외가 발생하면 자식 트랜잭션도 rollback한다.
- 자식 트랜잭션에 예외가 발생하더라도 부모 트랜잭션은 rollback하지 않는다. 이때 롤백은 부모 트랜잭션에서 자식 트랜잭션을 호출하는 지점까지만 롤백된다. 이후 부모 트랜잭션에서 문제가 없으면 부모 트랜잭션은 끝까지 commit 된다.
NEVER - 트랜잭션을 생성하지 않는다. 만약 부모 트랜잭션이 존재한다면 예외를 발생시킨다.


 

Isolation

: 격리수준을 정하는 옵션으로, 트랜잭션 내에서 일관성 없는 데이터를 허용하도록 하는 수준을 정한다. Spring에서는 기본적으로 READ_COMMITED를 사용하는데 Persistence Context를 사용함으로써 REPEATABLE_READ가 보장된다. 다음과 같이 @Transactional(isolation = Isolation.READ_COMMITTED) 사용할 수 있다.

옵션 설명
READ_UNCOMMITED - 트랜잭션에서 commit되지않은 데이터를 다른 트랜잭션에서 읽는 것을 허요한다.
- Dirty Read가 발생한다.
READ_COMMITED - 트랜잭션에서 commit되어 확정된 데이터만을 읽는 것을 허용한다.
REPEATABLE_READ - 한 트랜잭션 내에서 수차례 SELECT를 수행하더라도 동일한 값이 읽혀지는 것을 보장한다.
- Phantom read(새로운 row가 추가/삭제되어 새로 입력된 데이터를 읽는/사라지는 현상)는 여전히 발생 가능하다.
SERIALIZABLE - 모든 작업을 하나의 트랜잭션에 처리하는 것과 같은 높은 고립수준을 제공하는데, 이로인해 동시성 처리 효율은 매우 떨어진다.

여기서 발생되는 Row Lock과 같은 내용은 Lock으로 이해하는 Transaction의 Isolation Level을 참고해 주세요.



rollbackFor

  • 특정 예외가 발생할 경우 rollback하는 것을 명시하는 옵션이다. Spring은 기본적으로 RuntimeException과 Error를 롤백 정책으로 가진다.
  • 사용은 @Transactional(rollbackFor = {RuntimeException.class, Error.class}) 과 같이 할 수 있다. 만약 모든 예외에 대해 rollback하고싶다면 Exception.class를 추가하면 된다.
  • 이와 반대로 특정 Exception에 rollback하고 싶지 않다면 noRollbackFor 옵션을 사용하자.


Read-only

  • 트랜잭션을 읽기 전용으로 설정할 수 있다.
  • 이 옵션이 적용된 트랜잭션은 UPDATE, INSERT, DELETE등과같은 쓰기 작업이 진행되면 예외를 발생시킨다.
  • 이 옵션이 적용되지 않은 트랜잭션은 영속성 컨텍스트에 스냅샷을 만들어두고 트랜잭션이 끝날 때 스냅샷과 Entity의 값을 비교해 쓰기지연 SQL 저장소에 UPDATE 쿼리를 생성해 DB에 flush하는 작업을 거치는데, 이러한 과정이 사라지게 되어 효율적인 작업이 수행될 수 있다.
  • @Transactional(readOnly=true)로 사용할 수 있다.

timeout

  • 지정한 시간 내에 해당 메소드가 수행 완료되지 않으면 rollback을 수행한다.
  • @Transactional(timeout=10)와 같이 사용가능하다. (단위는 sec) (default -1로 no-timeout)

'Backend > SpringBoot-JPA' 카테고리의 다른 글

2차 캐시 - @Cacheable  (0) 2021.04.30
@RestController 어노테이션  (0) 2021.04.29
Optimistic/Pessimistic Lock  (1) 2021.04.19

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

데이터베이스

: (시스템계정) system / root
: sqlplus
: create user root idientified by root;
: grant connect, resource to root;
: exit
: show user
: drop user root cascade; (system 계정에서 실행)

CREATE TABLE member (
    memId VARCHAR2(10) CONSTRAINT memId_pk PRIMARY KEY,
    memPw VARCHAR2(10),
    memMail VARCHAR2(15),
    memPurcNum NUMBER(3) DEFAULT 0 CONSTRAINT memPurNum_ck CHECK (memPurcNum < 3)
);

INSERT INTO member (memId, memPw, memMail) values ('b', 'bb', 'bbb@gamil.com');

SELECT * FROM member;

DELETE FROM member WHERE memId = 'b';

DROP TABLE member;

commit;

JDBC

: 드라이버 로딩 -> DB 연결 -> SQL 작성 및 전송 -> 자원 해제

// MemberDao.java

private String driver = "oracle.jdbc.driver.OracleDriver";
private String url = "jdbc:oracle:thin:@localhost:1521:xe";
private String userid = "root";
private String userpw = "root";

private Connection conn = null;
private PreparedStatement pstmt = null;
private ResultSet rs = null;

public int memberInsert(Member member) {
    int result = 0;

    try {
        // Driver 로딩
        Class.forName(driver); 

        // DB 연결
        conn = DriverManager.getConnection(url, userid, userpw);

        // Query 전송
        String sql = "INSERT INTO member (memId, memPw, memMail) values (?, ?, ?)";
        pstmt = conn.preparedStatement(sql);
        pstmt.setString(1, member.getMemId());
        pstmt.setString(2, member.getMemPw());
        pstmt.setString(3, member.getMemMail());
        result = pstmt.executeUpdate();

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally { // 자원 해제
        try {
            if (pstmt != null) pstmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }   
    }
    return result;
}

public Member memberSelect(Member member) {
    Member mem = null;
    try {
        // Driver 로딩
        Class.forName(driver);

        // DB 연결
        conn = DriverManager.getConnection(url, userid, userpw);

        // Query 전송
        String sql = "SELECT * FROM member WHERE memId = ? AND memPw = ?";
        pstmt = conn.preparedStatement(sql);
        pstmt.setString(1, member.getMemId());
        pstmt.setString(2, member.getMemPw());
        rs = pstmt.executeUpdate();

        while (rs.netx()) {
            String memId = rs.getString("memid);
            String memPw = rs.getString("mempw);
            ...

            mem = new Member();
            mem.setMemId(memId);
            ...

        }
    } catch (Exception e) {
        ...
    } finally { // 자원해제
        try {
            if (rs != null) rs.close();
            if (pstmt != null) pstmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    return mem;
}

: 매번 Driver 로딩, DB 연결, 자원 해제를 해 주어야 한다는 단점 존재한다. 이를 간편하게 하기위해 JdbcTemplate을 사용한다.

JDBC Template 사용

: JDBC 사용시 Driver 로딩, DB 연결, 자원 해제를 JDBC Template에서 자동으로 해 주고, 각각의 함수에서는 SQL Query문만 작성해서 실행해주면 된다.

// pom.xml

<!-- DB -->
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>12.1.0.2</version>
</dependency>
<dependency>                            // 스프링 JDBC 추가
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.1.6.RELEASE</version>
</dependency>
<dependency>                            // 추가
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5</version>
</dependency>
// MemberDao.java

public class MemberDao implements IMemberDao {

    private DriverManagerDataSource dataSource;
    private JdbcTemplate template;

    /*
    private Connection conn = null;
    private PreparedStatement pstmt = null;
    private ResultSet rs = null;
    */

    public MemberDao() {
        /* c3p0 의 DriverManagerDataSource 사용 */
        dataSource = new DriverManagerDataSource();
        dataSource.setDriverClass(dirver); //  드라이버 로딩
        dataSource.setJdbcUrl(url); // DB 연결
        dataSource.setUser(userid);
        dataSource.setPassowrd(iserpw);

        /* spring의 DriverManagerDataSource 사용
        dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(dirver); 
        dataSource.setUrl(url); 
        dataSource.setUsername(userid);
        dataSource.setPassowrd(iserpw);
        */

        template = new JdbcTemplate(); // JDBC Template 사용
        template.setDataSource(dataSource);
    }
}

public int memberInsert(Member member) {
    int result = 0;

    String sql = "INSET INTO member (memId, memPw, memMail) values (?, ?, ?)";

    // 1st 방법
    result = template.update(sql, member.getMemId(), ...);

    /*
    // 2nd 방법 (이 때에는, sql문이 외부에서 사용될 수 있으므로 final로 선언해야한다 > final String sql)
    result = template.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, member.getMemId());
            ...

            return pstmt;
        }
    });

    // 3rd 방법 (이 때에는, sql문이 외부에서 사용될 수 있으므로 final로 선언해야한다 > final String sql)
    result = template.update(sql, new PreparedStatementSetter()) {
        @Override
        public void setValues(PreparedStatement pstmt) throws SQLException {
            pstmt.setString(1, member.getMemId());
            ...
        }
    });
    */

    return result;
}

public Member memberSelect(Member memeber) {
    List<Member> members = null;

    String sql = "SELECT * FROM member WHERE memId = ? AND memPw = ?";

    // 1st
    members = template.query(sql, new PreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement pstmt) throws SQLException {
            pstmt.setString(1, member.getMemId());
            pstmt.setString(2, member.getMemPw());
        }
    }, new RowMapper<Member>() {
        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member mem = new Member();

            mem.setMemId(rs.getString("memId"));
            ...

            return mem;
        }
    });

    if (member.isEmpty()) return null;

    /*
    // 2nd
    members = template.query(new PreparedStatementCreator() {

        @Override
        public PreparedStatement createPreparedStatement(Connection conn)
                throws SQLException {
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, member.getMemId());
            pstmt.setString(2, member.getMemPw());
            return pstmt;
        }
    }, new RowMapper<Member>() {

        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member mem = new Member();
            mem.setMemId(rs.getString("memId"));
            mem.setMemPw(rs.getString("memPw"));
            mem.setMemMail(rs.getString("memMail"));
            mem.setMemPurcNum(rs.getInt("memPurcNum"));
            return mem;
        }
    });

    // 3rd
    members = template.query(sql, new RowMapper<Member>() {

        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member mem = new Member();
            mem.setMemId(rs.getString("memId"));
            mem.setMemPw(rs.getString("memPw"));
            mem.setMemMail(rs.getString("memMail"));
            mem.setMemPurcNum(rs.getInt("memPurcNum"));
            return mem;
        }

    }, member.getMemId(), member.getMemPw());

    // 4th
    members = template.query(sql, new Object[]{member.getMemId(), member.getMemPw()}, new RowMapper<Member>() {

        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member mem = new Member();
            mem.setMemId(rs.getString("memId"));
            mem.setMemPw(rs.getString("memPw"));
            mem.setMemMail(rs.getString("memMail"));
            mem.setMemPurcNum(rs.getInt("memPurcNum"));
            return mem;
        }

    });
    */

    return members.get(0);
}

커넥션풀

: 다수의 사용자가 DB 접속 시 드라이버를 로딩하고, 커넥션을 만드는 작업은 메모리를 상당히 많이 차지하고 이는 서버에 상당한 부하를 줄 수 있다. 따라서 사용자 요청이 들어오지 않아도 미리 커넥션을 만들어 놓고 요청 있을 때 미리 만들어 놓은 것을 이용하는 방법.
: c3p0모듈의 ComboPooledDataSource를 사용
: 사용방법은 이전(public MemberDao() { ... })과 동일. 하지만,

dataSource = new ComboPooledDataSource();
try {
    dataSource.setDriverClass(driver);
    ...
} catch (PropertyVetoException e) {
    e.printStackTrace();
}

와 같이 try-catch 문을 사용해 주어야 한다.

학습하며 정리한 내용을 올리는 것이니, 참고용으로만 봐 주시길 바랍니다.
자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌

세션 & 쿠키

  1. 세션 생성
public String memLogin(Member member, HttpServletRequest request) {
    ...
    HttpSession session = request.getSession();
    session.setAttribute("member", mem);
    return "/member/loginOk";
}

public String memLogin(Member member, HttpSession session) {
    ...
    session.setAttribute("member", mem);
    return "/member/loginOk";
}

1.1 세션 회원정보 수정

public ModelAndView modifyForm(HttpServletRequest request) {
    HttpSession session = request.getSession();
    Member member = session.getAttribute("member");

    ModelAndView mav = new ModelAndView();
    mav.addObject("member", service.memberSearch(member));
    ...
  1. 세션 삭제
public String memLogout(Member member, HttpSession session) {
    ...
    session.invalidate();
    return "/member/logout ";
}

세션 주요 메소드

  • getId(): 세션 ID 반환
  • setAttribute(): 세션 객체에 속성을 저장
  • getAttribute(): 세션 객체에 저장된 속성을 반환
  • removeAttribute(): 세션 객체에 저장된 속성 제거
  • setMaxInactiveInterval(): 세션 객체의 유지 시간 설정
  • `get ~
  • invalidate(): 세션 객체의 모든 정보삭제
  1. 쿠키 생성
public String mallMain(Mall mall, HttpServletResponse) {
    Cookie genderCookie = new Cookie("gende", mall.getGender());

    if (mall.isCookieDel()) {
        genderCookie.setMaxAge(0);
        mall.setGender(null);
    } else {
        genderCookie.setMaxAge(60 * 60);
    }
    response.addCookie(genderCookie);
    return "/mall/main";
  1. 쿠키 사용
@public String mallIndex(Mall mall, @CookieValue(value="gende", required=false) Cookie genderCookie, HttpServletRequest request) {
    if (genderCookie != null)
        mall.setGender(genderCookie.getValue());
    return "/mall/index";
}
}

리다이렉트 & 인터셉터

리다이렉트

: reutrn "redirect:/page";
:

public ModelAndView removeForm(HttpServletRequest request) {
    ModelAndView mav = new ModelAndView();
    ...
    if (null == member) {
        mav.setViewName("redirect:/");
    } else {
        mav.addObject("member", member);
        mav.setViewName("/member/removeForm");
    }

    return mav;
}

인터셉터

: 리다이렉트를 사용해야 하는 경우가 많을 때 HandlerInterceptor를 이용할 수 있다.

--------------- ###########################
--------------- # ------ Request -------- #
--------------- ###########################
--------------------------- |
--------------- ###########################
--------------- #--- DispatcherServlet ---#
--------------- ###########################
--------------------------- |
#######################################################
#---------- HandlerInterceptor(인터페이스) ------------#
#-----------------------------------------------------#
# PreHandle()------PostHandler()-----afterCompletion()#
#######################################################
------ | -------------- ^ ----- | --------- ^
------ | -------------- | ----- | --------- |
--- ######################## -- | --------- |
--- #  Handler(Controller) # -- | --------- |
--- ######################## -- |  #####################
------------------------------- ㄴ># ------ View ----- #
---------------------------------- #####################
        
#######################################################
# ---------------------- Response ------------------- #
#######################################################
  • preHandle(): Controller가 작업하기 전 작업한다. 리다이렉트를 대체 가능하다. 주로 쓰인다.
  • postHandle(): Controller가 작업한 후 작업한다.
  • afterCompletion(): Controller 와 View가 작업한 후에 작업한다.

: 주로 HandlerInterceptor 인터페이스를 구현하기 보다는, 이들이 구현되어 있는 HandlerInterceptorAdapter 클래스를 상속받아 사용한다.

public class MemberLoginInterceptor extends HandlerInterceptorAdaptor {

    @Override
    public bollean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HpptSession session = request.getSession(false);
        if (session != null) {
            return true;
        }

        response.sendRedirect(request.getContextPath() + "/"); // main 페이지로 보낸다
        return false;
    }
}
// servlet-context.xml
// 스프링 설정파일

<interceptors>
    <interceptor>
        <mapping path="/member/modifyForm" /> // 
        <mapping path="/member/removeForm" /> // 이곳으로 요청이 들어왔을 때 
        <beans:bean class="com.bs.lec21.member.MemberLoginInterceptor" /> // 이 인터셉터가 동작을 해라
    </interceptor>
</interceptors>
<mapping path="/member/**" /> // 멤버 하위의 모든 경로에 대해 동작을 하는데
<exclude-mapping path="/member/joinForm" /> // 여기는 제외해라
<exclude-mapping path="/member/join" />
<exclude-mapping path="/member/login" />

'Backend > Spring' 카테고리의 다른 글

Spring - 데이터베이스  (0) 2021.02.14
Spring - MVC  (0) 2021.02.04
Spring - 스프링 컨테이너의 생성관련  (0) 2021.02.02
Spring - Bean과 의존객체 주입  (0) 2021.02.01
Spring - Dependency Injection  (0) 2021.01.31

+ Recent posts