function Memo({ item, Delete, Edit, SetPosition, SetWidthHeight }) {
  const handleRef = useRef(null);

  return (
    <Draggable handleRef={handleRef} x={0} y={0} onMove={(x,y) => console.log(x,y)}>
    <div
      className="memo-container"
      style={{ width: `${250}px`, height: `${300}px` }}
    >
      <div className="menu">
        <DragHandleIcon 
          ref={handleRef}
          sx={{ cursor: "move", fontSize: "25px" }} />
        <CloseIcon
          sx={{ cursor: "pointer", fontSize: "25px", float: "right" }}
        />
      </div>
      <textarea
        className="memo-text-area"
        defaultValue={"Enter memo here"}
        name="txt"
        placeholder="Enter memo here"
      ></textarea>
    </div>
    </Draggable>
  );
}

 

위와 같이 Draggable 커스텀 컴포넌트를 사용하는데 아래와 같은 오류가 발생했다.

 

오류의 원인은 3가지 정도가 있다고 한다.

 

  • react, renderer 버전
    • react-dom 버전이 16.8.0 이하인 경우 발생할 수 있으나 현재 사용하고 있는 버전이 18.1.0 이기 때문에 해당되지 않음
  • Hooks 규칙 위반
    • 컴포넌트 내에서 훅 함수를 사용했기 때문에 문제 없어보인다.
  • 동일 앱에 1개 이상의 리액트 
    • 즉 오류의 원인은 여기인 듯 함. 자세히 살펴보자

 

Draggable 컴포넌트를 사용하기 위해 다운받았던 @billy-fe/draggable@1.0.2 패키지에서 react 버전 17.0.2에 종속되어 있는 것을 확인할 수 있다. 필자는 현재 18.1.0을 사용하고 있는데, 일부 패키지에서 17.0.2에 종속되어 있기 때문에 이러한 오류가 발생한 듯 하다.

 

 

해당 라이브러리 폴더(@billy-fe/draggable)로 이동 후 npm link를 이용해서 해결

npm link는 아직 잘 모르지만, 해당 라이브러리의 리액트를 프로젝트의 리액트로 연결하는 기능 같음.

 

 

npm link C:\workspace/sticker-memo/node_modules/react

 

 

참고

https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react

 

0. 스프링이란?

스프링은 애플리케이션 개발에 사용되는 프레임워크이다. 이는 개발을 빠르고 효율적으로 할 수 있도록 바탕이 되는 틀과 곹오 프로그래밍 모델, API 등을 제공해준다.
  • 스프링은 스프링 컨테이너 또는 애플리케이션 컨텍스트라고 불리는 스프링 런타임 엔진을 제공한다. 
  • 스프링 컨테이너는 설정정보를 참고로 해서 애플리케이션을 구성하는 오브젝트를 생성하고 관리한다.
프레임워크는 애플리케이션을 구성하는 오브젝트가 생성되고 동작하는 방식에 대한 틀을 제공해줄 뿐만 아니라 어떻게 프로그래밍해야 하는지 기준도 제시해준다. 이런 틀을 프로그래밍 모델이라고 한다

프로그래밍 모델 

  • Ioc/DI는 오브젝트의 생명주기와 의존관계에 대한 프로그래밍 모델이다
  • 서비스 추상화. 특정 기술과 환경에 에 종속되지 않도록 유연한 추상 계층을 두는 방법을 제공한다
  • AOP. 애플리케이션 코드에 산재해서 나타나는 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델이다.


01. 오브젝트와 의존관계

빈이란 ?
  • 디폴트 생성자: 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다
  • 프로퍼미: 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 setter와 getter를 이용해 수정 또는 조회할 수 있다.

애플리케이션의 변화는 끊이지 않는다. 문제는 변화는 대체로 집중된 한 가지 관심에 대해 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다. 즉 코드의 수정이 여러곳에서 일어나고 이러한 작업에 대한 시간, 검증에 대한 불안감이 있을 수 있다. 이러한 문제를 해결하기 위해서는 분리와 확장을 고려한 설계가 필요하다.

상속을 사용하여 관심사를 분리할 수 있지만 상속을 이용하면 단점이 있다. 일단 자바는 다중 상속을 지원하지 않는데 관심사를 분리해야 하는 대상이 다른 클래스를 상속하고 있을 수 있다. 또한 상속관계는 부모-자식 간의 긴밀한 결합을 허용한다. 서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있고 슈퍼클래스의 변경이 있을 경우 서브클래스도 함께 수정 또는 개발이 이루어져야 한다.  

개발 폐쇄 원칙은 '클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다'는 원칙이다. 클래스나 모듈은 확장은 손쉽게 가능하지만 변경에 대한 코드 변경은 있지 않아야 한다는 원칙이다.  개방 폐쇄 원칙은 높은 응집도와 낮은 결합도가 만족되어야 한다. 높은 응집도 라는 것은 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다는 의미이다. 낮은 결합도란 책임과 관심사가 다른 오브젝트 또는 모듈과는 느슨하게 연결된 형태를 유지해야 한다는 것이다. 느슨하다는 것은 특정 클래스에 대한 연결이 아닌 인터페이스로 연결된다는 의미이다. 

전략 패턴은 자신의 기능 맥락(context)에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게하는 디자인 패턴이다. 즉 Dao클래스에서(context) DB커넥션을 구하는 알고리즘을 외부로 분리한 뒤 필요에 따라 DB커넥션을 구하는 알고리즘을 변경하며 Dao에서 사용되도록 하는 패턴이다. 

제어관계 역전(Ioc)은 일반적인 프로그램의 흐름과는 다른 개념이다. 일반적인 프로그램은 main 메소드에서 필요한 오브젝트를 생성하고 오브젝트의 메소드를 호출하는 식이다. 즉 main 메소드에서 필요한 오브젝트를 '선택하여 사용'하는 것인데 제어 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않고 생성도하지 않는다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다. 스프링이 제어권을 가지고 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다.

간단한 용어 정리.

  • 빈: 스프링이 Ioc 방식으로 관리하는 오브젝트

  • 빈 팩토리: 스프링의 Ioc를 담당하는 핵심 컨테이너를 가리킴. 빈을 등록하고, 생성하고, 조회하고 반환하고, 그 외에 부갖거인 빈을 관리하는 기능을 담당
  • 애클리케이션 컨텍스트: 빈 팩토리를 확장한 Ioc 컨테이너이다. 기본적인 기능은 빈 팩토리와 동일하지만 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다

  • 설정정보/메타정보: 스프링의 설정정보란 애플리케이션 컨텍스트 또는 빈 팩토리가 Ioc를 적용하기 위해 사용하는 메타정보를 말함

  • 컨테이너 또는 Ioc컨테이너: Ioc 방식으로 빈을 관리하다는 의미에서 빈 팩토리나 애플리케이션 컨텍스트를 컨테이너 또는 Ioc컨테이너라고 한다
  • 스프링 프레임워크: Ioc 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말함.

애플리케이션 컨텍스트는 빈을 싱글톤으로 관리한다. 따라서 빈 내의 인스턴스 변수는 읽기전용이 아니라면 매우매우 위험하다. 스프링은 보통 멀티스레드 환경에서 사용되는데 다양한 사용자가 싱글톤 오브젝트를 사용하여 변경 작업을 했을 때 인스턴스 변수를 사용했을 경우 여러 사용자의 요청이 혼합되어 변경이 될 것이다. 따라서 빈 내에서는 읽기전용일 경우에만 인스턴스 변수로 가지고 그 외에는 파라미터나 로컬변수 리턴값을 이용해야 한다.

의존관계는 두 개의 클래스 또는 모듈이 의존관계에 있다고 말할 떄는 항상 방향성을 부여해줘야 한다. A가 B에 의존하고 있다는 것은 B가 변하면 A에 영향이 있다는 것이다. 반면 B는 A의 변화에 영향을 받지 않는다. 인터페이스에 대해서만 의존관계를 만들어두면 인터페이스 구현 클래스와의 관계는 느슨해지면서 변화에 영향을 덜 받는 상태가 된다. 의존관계 주입은 런타임 시에 사용할 오브젝트(의존 오브젝트)를 클라이언트와 연결해주는 작업을 말한다.

의존관계 주입(DI)의 장점

  • 인터페이스를 통해 결합도가 낮은 코드
  • 확장에는 열려있고(인터페이스를 통한 주입) 변경에는 닫혀 있다.


02. 테스트

스프링의 가장 중요한 가치는 객체지향과 테스트라고 한다. 그렇다면 굳이 테스트를 작성해야 하는 이유가 뭘까? 

  • 웹을 통한 테스트를 한다면 하나의 클래스를 테스트하기 위해 서비스 계층, MVC 프레젠테이션 계층까지 포함한 모든 입출력 기능을 만들어야 한다. 스프링의 테스트를 이용하면 테스트할 데이터도 코드로 작성하고 결과도 바로바로 확인할 수 있다.
  • 큰 단위의 기능에 문제가 생겼을 경우 작은 단위의 테스트가 작성되어 있다면 문제 해결이 쉬워진다. 

테스트를 작성하는데 주의해야 할 점

  • 외부 환경에 영향을 받지 않는 테스트를 작성해야 한다.
  • 테스트 실행 순서에 상관없이 동일한 결과를 반환해야 한다.

TDD란 만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법이다. TDD의 기본 원칙은 "실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다"는 것이다. 

테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라고 한다. 일반적으로 픽스처는 여러 테스트에서 반복저ㅏㄱ으로 사용되기 때문에 @Before 메소드를 이용해서 생성하면 편리하다.

@Before 메소드는 @Test 메소드를 실행하기 전에 실행되며 각 @Test 메소드가 실행될 때마다 매번 실행된다. 그러나 @BeforeClass 스태틱 메소드를 클래스 전체에 걸쳐 단 한번만 실행된다. 하지만 보통 스프링이 직접 제공하는 애플리케이션 컨텍스트 테스트 지원기능을 사용하는 것이 더 편리하다.

1
2
3
4
5
6
7
@RunWith(SpringJUnit4ClassRunner.class// 스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능
@ContextConfiguration(locations="/applicationContext.xml"// 테스트 컨텍스트가 자동으로 만들어줄 애플리케이션 컨텍스트의 위치 지정
public class UserDaoTest { 
    // 테스트 오브젝트가 만들어지고 나면 스프링 테스트 컨텍스트에 의해 자동으로 값이 주입됨
    @Autowired
    private ApplicationContext context;
}
cs


03. 템플릿

템플릿은 개방 폐쇄 원칙과 관련이 있다. 이 원칙은 코드에서 어떤 부분은 변경을 통해 그 기능을 다양해지고 확장하려는 성질이 있고, 어떤 부분은 고정되어 있고 변하지 않으려는 성질이 있음을 말해준다. 
템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는방법이다.

전략 패턴의 기본 구조에 익명 내부 클래스를 활용한 방식을 스프링에서는 템플릿/콜백 패턴이라고 부른다. 전략 패턴의 컨텍스트를 템플릿이라 부르고, 익명 내부 클래스로 만들어지는 오브젝트를 콜백이라고 부른다. 템플릿에 콜백함수(전략)을 전달하는 것이 DI와 비슷한데 일반적인 DI라면 템플릿에 인스턴스 변수를 만들어두고 사용할 의존 오브젝트를 수정자 메소드로 받아서 사용할 것이다. 반면에 템플릿/콜백 방식에서는 매번 메소드 단위로 사용할 오브젝트를 새롭게 전달받는다는 것이 특징이다. 

04. 예외

런타임 예외 중심의 전략은 낙관적인 예외처리 기법이라고 할 수 있다. 일단 복구할 수 있는 예외는 없다고 가정하고 예외가 생겨도 어차피 런타임 예외이므로 시스템 레벨에서 알아서 처리해 줄 것이고 꼭 필요한 경우는 런탕미 예외라도 잡아서 복구하거나 대응할 수 있다. 반면에 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고 반드시 catch 해서 무엇인가 조치를 취하도록 요구하는를 애플리케이션 예외라고 한다. 애플리케이션 예외는 체크 예외로 만들어서 예외상황에 대한 로직을 강제로 구현하도록 하는 것이 좋다.  

SQL Exception과 같이 코드레벨에서 대응이 불가능한 예외는 필요도 없는 기계적인 throws 선언이 등장하도록 방치하지 말고 가능한 한 빨리 언체크/런타임 예외로 전환해줘야 한다. 

체크 예외와 언체크 예외를 구분하는 기준은 코드 레벨에서 복구가능한지 여부로 결정하거나 또는 애플리케이션 로직에 영향을 줄 수 있으므로 예외 상황에 대해 강제로 인식이 필요할 때 인듯함

  • 예외를 잡아서 아무런 조취를 취하지 않거나 의미 없는 throws 선언을 남발하는 것은 위험하다
  • 예외는 복구하거나 예외처리 오브젝트로 의도적으로 전달하거나 적절한 예외로 전환해야 한다.
  • 좀 더 의미 있는 예외로 변경하거나, 불필요한 catch/throws를 피하기 위해 런타임 예외로 포장하는 두가지 방법의 예외전환이 있다.
  • 복구할 수 없는 에외는 가능한 한 빨리 런타임 예외로 전환하는 것이 바람직하다.
  • 애플리케이션의 로직을 담기 위한 예외는 체크 예외로 만든다
  • JDBC의 SQL Exception은 대부분 복구할 수 없는 예외이므로 런타임 예외로 포장해야한다.
  • SQL Exception의 에러 코드는 DB에 종속되기 때문에 DB에 독립적인 예외로 전환될 필요가 있다.
  • 스프링은 DataAccessException을 통해 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층을 제공한다.
  • DAO를 데이터 엑세스 기술에서 독립시켜려면 인터페이스 도입과 런타임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.

05. 서비스 추상화

  • 비즈니스 로직을 담은 코드는 데이터 엑세스 로직을 담은 코드와 깔끔하게 분리되는 것이 바람직하다. 비즈니스 로직 코드 또한 내부적으로 책임과 역할에 따라서 깔끔하게 메소드로 분리되어야 한다. 
  • 이를 위해서는 DAO의 기술 변화에 서비스 계층의 코드가 영향을 받지 않도록 인터페이스와 DI를 활용해서 결합도를 낮춰야 한다.
  • DAO를 사용하는 비즈니스 로직에는 단위 작업을 보장해주는 트랜잭션이 필요하다
  • 트랜잭션의 시작과 종료를 지정하는 일은 트랜잭션 경계설정이라고 한다. 트랜잭션 경계설정은 주로 비즈니스 로직안에서 일어나는 경우가 많다.
  • 시작된 트랜잭션 정보를 담은 오브젝트를 파라미터로 dao에 전달하는 방법은 매우 비효율적이기 때문에 스프링이 제공하는 트랜잭션 동기화 기법을 활용하는 것이 편리하다.
  • 자바에서 사용되는 트랜잭션 API의 종류와 방법은 다양하다. 환경과 서버에 따라서 트랜잭션 방법이 변경되면 경계설정 코드도 함께 변경돼야 한다.
  • 트랜잭션 방법에 따라 비즈니스 로직을 담은 코드가 함께 변경되면 단일 책임 원칙에 위배되며,  DAO가 사용하는 특정 기술에 대해 강한 결합을 만들어낸다.
  • 트랜잭션 경계설정 코드가 비즈니스 로직 코드에 영향을 주지 않게 하려면 스프링이 제공하는 트랜잭션 서비스 추상화를 이용하면 된다.
  • 서비스 추상화는 로우레벨의 트랜잭션 기술과 api의 변화에 상관없이 일관된 API를 가진 추상화 계층을 도입한다.
  • 서비스 추상화는 테스트하기 어려운 JavaMail 같은 기술에도 적용할 수 있다. 테스트를 편리하게 작성하도록 도와주는 것만으로도 서비스 추상화는 가치가 있다.
  • 테스트 대상이 사용하는 의존 오브젝트를 대체할 수 있도록 만든 오브젝트를 테스트 대역이라고 한다.
  • 테스트 대역은 테스트 대상 오브젝트가 원할하게 동작할 수 있도록 도우면서 테스트를 위해 간접적인 정보를 제공해주기도 한다.
  • 테스트 대역 중에서 테스트 대상으로부터 전달받은 정보를 검증할 수 있도록 설계된 것을 목 오브젝트고 한다.


c부트스트랩이란 반응형 웹[각주:1]을 좀더 쉽게 개발할수 있도록 도와주는 프론트엔드 프레임워크의 한 종류이다. 


왜 부트스트랩을 써야하는가? 

부트스트랩의 이점은 아래와 같은데 개인적으로 4번의 이유가 매우 중요해보인다.
  1. 사용하기 쉽다
  2. 반응형이다
  3. 모바일 우선 방식이다 ( 모바일을 지원한다는 의미인듯..)
  4. 브라우저가 호환된다


Container

사이트의 요소를 감싸기위해 포함 요소가 필요한데 부트스트랩은 2개의 클래스를 지원한다. 예시 이미지로 보면 둘의 차이점이 이해가 될 것이다.
  1. .container: 고정폭을 반응형으로 제공
  2. .container-fluid: 뷰포트에 따른 전체 너비 제공 ( 모바일이든 웹이든 전체 너비를 제공한다는 듯..)



Grid

부트스트랩은 페이지 전체를 12개의 컬럼으로 구성할 수 있다. 아래의 예시를 보면 12개의 컬럼을 각자 개별적으로 사용할수도, 또는 몇개의 그룹으로 묶어서 사용할 수 있는 것을 확인할 수 있다.

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1

 span1



그리드 클래스는 col-**-** 형태이며 처음 ** 에는 아래의 디바이스 크기에 맞는 적설한 값을 마지막 **에는 12개의 컬럼중 몇개로 구성할지를 작성하면 된다. 이러한 그리드 클래스는 행을 구분하는 row 클래스 내에서 사용하면 된다.  


  • xs (for phones - screens less than 768px wide)
  • sm (for tablets - screens equal to or greater than 768px wide)
  • md (for small laptops - screens equal to or greater than 992px wide)
  • lg (for laptops and desktops - screens equal to or greater than 1200px wide)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-lg-12"  style="background-color:gray;"><tiles:insertAttribute name="header" /></div>
    </div>
    <div class="row">
        <div class="col-lg-3"  style="background-color:green;"><tiles:insertAttribute name="menu" /></div>
        <div class="col-lg-9"  style="background-color:blue;"><tiles:insertAttribute name="body"/></div>
    </div>
    <div class="row">
        <div class="col-lg-12"  style="background-color:red;"><tiles:insertAttribute name="bottom" /></div>
    </div>
</div>
</body
cs


  1. 디바이스(크기)에 따라 자동적으로 모습이 바뀌는 웹페이지 [본문으로]

'웹 개발' 카테고리의 다른 글

JSTL (작성중)  (0) 2018.05.04
jsonp  (0) 2017.02.09
OAuth 2.0 ( RFC 6749)  (0) 2017.01.19
JSP 와 Servlet의 한글처리.  (0) 2016.10.06
Apache 와 Tomcat  (0) 2016.07.26

이번 포스팅에서는 엔티티매니저와 영속컨텍스트에 대해서 알아보려한다. 그전에 엔티티와 엔티티매니저가 무엇인지 알아야 하는데 이에 대해서는 이전에  정리한 내용이 있으니 참고하길 바란다.

+ JPA


JPA를 이용하여 개발한 프로그램 코드는 보통 다음과 같은 패턴으로 개발된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
EntityManager emf = Persistence.createEntityManagerFactory("jpastart");
EntityManager em = emf.createManager();
EntityTransaction transaction = em.getTransaction();
 
try {
    transaction.begin();
    Sight sight = em.find(Sight.class1);
    sight.setDetail(new SightDetail('오전9~오후5시''연중무휴''100여대 주차가능'));
    transaction.commit();
catch(Exception e) {
    transaction.rollback();
    throw e;
finally {
    em.close();
emf.close();
}
cs


엔티티매니저를 생성한 이후 트랜잭션을 시작하고 엔티티매니저를 사용하여 엔티티(Sight)를 검색하고 명소정보(SightDetail)를 지정한 이후 트랜잭션을 커밋한다. 얼핏 일반적인 jdbc를 이용한 프로그래밍 패턴과 비슷하지만 JPA는 큰 차이점이 있다.



영속 컨텍스트

앞서 JPA의 가장 큰 특징은 영속성(Persistence)이다. JPA를 사용해 검색한 DB매핑정보(엔티티)는 메모리(영속 컨텍스트)에 저장되고 이러한 엔티티는 영속객체라 부른다. JPA는 매번 데이터베이스에 접근하는 것이 아니라 엔티티매니저를 사용하여 메모리상에 작업을 한후 트랜잭션이 커밋되는 시점에 데이터베이스에 반영되는 구조를 가지고 있다. 


엔티티, 엔티티매니저와 영속 컨텍스트의 관계는 어느정도 이해가 됐을 것 같은데 간단히 정리하면 아래의 그림과 같다.


프로그램에서 엔티티매니저를 이용하여 데이터를 검색요청하면 영속 컨텍스트에 데이터가 없을 경우 DB에서 검색하여 영속 컨텍스트에 엔티티로 저장한다. 이후에 같은 식별자를 가지는 데이터를 요청할 경우에는 DB에서 검색하는 것이 아니라 영속 컨텍스트에서 검색한 결과를 반환한다.



전체적인 흐름은 위와 같고 먼저 엔티티매니저에 대해 자세히 알아보도록 하자.



EntityManager(엔티티매니저)


앞서 엔티티매니저는 영속컨텍스트에 저장되어 있는 엔티티를 관리하기위한 객체라고 설명했었다. 엔티티매니저를 관리하는 주체에 따라 종류가 구분된다

 종류

설명 

애플리케이션 관리 엔티티매니저 

 애플리케이션에서 직접 emf를 생성하고 관리

컨테이너관리 엔티티매니저 

 JBoss EAP, 웹로직, TomEE와 같은 JEE 컨테이너가 EMF를 생성하고 관리


전자는 앞선 코드에서 처럼 프로그램 코드에서 직접 EntityManager를 직접 생성하고 종료하는 방식을 말한다. 반면 컨테이너 관리 엔티티매니저는 프로그램 코드가 아닌 JEE 컨테이너[각주:1]에서 EntityManager의 라이프사이클(생성~종료)을 관리한다. 

 아래의 예제에서 보이다시피 EntityManager에 대한 생성과 종료에 대한 코드가 없다. JEE 컨테이너는 @Transactional 애노테이션이 적용된 메서드를 트랜잭션 범위에서 실행한다. 즉 메소드가 실행될때 트랜잭션이 시작(begin)되고 메서드가 종료될 때 트랜잭션이 완료(commit)되는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
@PersistenceContext EntityManager em;
 
@Transactional
public void withdraw(String email) {
    User user = em.find(User.class, email);
    if(user == null) {
        throw new UserNotFoundException();
    }
    
    em.remove(user);
}
cs



트랜잭션

컨테이너관리 엔티티매니저를 사용했을 때 트랜잭션에 대해 상세한 제어가 필요한 경우가 있다. 애플리케이션에서 관리하는 앤티티매니저의 경우는 개발자가 직접 트랜잭션을 제어할 수 있기 때문에 큰 어려움은 없겠지만 컨테이너가 관리하는 엔티티매니저의 경우는 전파방법에 대해 알아두어야 한다.

그전에 먼저 트랜잭션 종류에 대해 알아보자. JPA는 자원 로컬(Resource Local) 트랜잭션 타입과 JTA(Java Transaction Api) 타입의 두 가지 트랜잭션 타입을 지원한다. 트랜잭션 타입은 persistence.xml 파일의 transaction-type 속성값을 설정하면 된다. 

먼저 자원 로컬 트랜잭션은 JPA가 제공하는 EntityTransaction을 이용하는 방식이다. 아래의 코드는 JPA가 제공하는 자원 로컬 트랜잭션을 사용한 예시이다.
1
2
3
4
5
6
7
8
9
10
11
12
EntityManager em = emf.createEntityManager();
Transaction tx = em.createTransaction();
try {
    tx.begin();
    //...
    tx.commit();
catch(Exception e) {
    tx.rollback();
finally {
    em.close();
}
 
cs


다음은 JTA 트랜잭션 방식인데 JTA 트랜잭션을 사용하면 JPA에서 트랜잭션을 관리하지 않는다. 대신 EntityManager를 JTA 트랜잭션에 참여시켜 트랜잭션을 관리한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UserTransaction utx = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
utx.begin(); // JTA 트랜잭션 시작
 
EntityManager em = emf.createEntityManager();
em.joinTransaction(); // JTA 트랜잭션에 참여
 
try {
    // ...
    utx.commit();
catch(Exception e) {
     utx.rollback();
finally {
    em.close()
}
 
cs


위의 코드에서 5라인의 joinTransaction()을 호출하지 않으면 엔티티의 변경이 있을 경우 실제 DB에 반영되지 않는다. 그 이유는 JPA는 트랜잭션 내에서의 변경만 DB에 반영하는데 JTA트랜잭션 방식을 사용하면 엔티티매니저는 트랜잭션에 관여하지 않고 엔티티매니저가 JTA 트랜잭션에 참여하지 않았으니 실제 DB에 반영되지 않는 것이다. 


엔티티매니저의 영속 컨텍스트 전파


  1. Enterprise급 개발에 필요한 모든 기술을 담고 있는 기술 (트랜잭션, JSP, 서블릿 등..)을 사용할 수 있는 환경 [본문으로]

'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

@Embeddable  (0) 2019.02.19
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

@Embeddable 애노테이션을 지정한 클래스를 밸류 클래스라고 합니다. 밸류 클래스란 int, double 처럼 하나의 값을 나타내는 클래스를 말합니다. 보통 주소(address)라는 값을 저장하기 위해서는 아래와 같이 String 변수에 저장하여 관리할 것입니다. 

1
String address = "서울시 강동구 천호동 113-12번지 개발아파트 110동 1101호";
cs


하지만 주소 역시 값을 분리하여 저장하는 경우도 많습니다. address1과 address2,, zipCode가 합쳐져야 하나의 정확한 의미를 나타내는데 변수를 분리하여 관리하기 때문에 불편한 점도 있을 것입니다. 이러한 경우에 "밸류 클래스"를 사용하여 값이 가지는 의미를 강화시킬 수 있습니다.


1
2
3
String address1 = "서울시 강동구 천호동";
String address2 = "개발아파트 110동 1101";
String zipCode = "113-12";
cs


밸류 클래스

밸류 클래스는 여러개의 값(address1, address2, zipCode)을 가지지만 개념적으로 하나의 값(주소)을 표현합니다. 또한 다른 밸류 객체와 식별하기 위한 식별자를 가지지 않는 것이 특징입니다. 아래는 주소라는 값을 나타내는 Address 클래스입니다. 변수를 address1, address2, zipCode로 각각 관리했을 때보다 좀 더 의미가 뚜렷한 것 같지않으신가요? 

1
2
3
4
5
class Address {
    String address1;
    String address2;
    String zipCode;
}
cs


하지만 밸류 클래스는 보통 값으로 사용되기 때문에 equals 메소드와 hashCode 메소드를 오버라이딩하여 사용해야 합니다. 


@Embeddable 애노테이션과 밸류 매핑

먼저 @Embeddable 애노테이션은 설정한 클래스가 다른 엔티티의 일부로 저장될 수 있음을 설정하는 애노테이션입니다. 즉 @Embeddable 애노테이션을 설정한 밸류 클래스는 다른 엔티티에 포함될 수 있다는 것이죠. 밸류를 포함하는 엔티티에서는 @Embedded로 밸류 클래스임을 설정합니다. 호텔(엔티티)과 주소(밸류)의 설정을 살펴보시면 이해가 될 것 입니다.

1
2
3
4
5
6
7
8
create table jpastart.hotel (
    id varchar(100not null primary key,
    name varchar(50),
    grade varchar(255),
    zipcode varchar(5),
    address1 varchar(255),
    address2 varchar(255)
engine innodb character set utf8;
cs


호텔 엔티티
1
2
3
4
5
6
7
8
9
10
11
@Entity
public class Hotel {
    @Id
    private String id;
    private String name;
    @Enumberated(EnumType.STRING)
    private Grade grade;
 
    @Embedded
    private Address address;
}
cs

주소 밸류클래스

1
2
3
4
5
6
7
8
@Embeddable
public class Address{
    private String zipCode;
    private String address1;
    private String address2;
 
    //...
}
cs



그럼 엔티티매니저를 사용하여 Hotel을 검색하면 어떻게 될까요? address1, address2, zipCode는 자동으로 Hotel 엔티티안의 Address 밸류클래스로 매핑되기 때문에 호텔의 get메소드를 호출하여 사용할 수 있습니다. 


1
2
3
4
5
Hotel hotel = em.find(Hotel.class"H100-01");
Address address = hotel.getAddress();
// address.getAddress1(); 서울시 강동구 천호동
// address.getAddress2(); 개발호텔
// address.getZipCode();  113-12
cs



@Embeddable 접근 타입

@Embeddable 매핑한 대상은 기본적으로 엔티티의 접근 방법( 필드 접근타입 또는 프로퍼티 접근 타입 )을 따릅니다. 엔티티에서 프로퍼티 접근 타입(getter / setter)를 사용한다면 @Embeddable 매핑한 밸류 클래스 역시 프로퍼티 접근 타입을 사용하기 때문에 getter, setter 메소드를 정의해야 합니다.



@AttributeOverrides

관광지라는 엔티티가 있고 이 엔티티는 한글로 된 주소, 영어로 된 주소 2개를 가진다고 해보자. 

1
2
3
4
5
6
7
8
9
10
create table jpastart.sight (
    id varchar(100not null primary key,
    name varchar(50),
    kor_zipcode varchar(5),
    kor_address1 varchar(255),
    kor_address2 varchar(255)
    eng_zipcode varchar(5),
    eng_address1 varchar(255),
    eng_address2 varchar(255)
engine innodb character set utf8;
cs


위의 sight 테이블을 엔티티로 매핑하면 아래와 같은데 Address 밸류 클래스는 address1, address2, zipcode를 필드로 설정하고 있다. Sight 엔티티에서 Address 밸류클래스를 중복 선언하였기 떄문에  필드가 중복됐다는 JPA의 에러 메세지를 볼 수 있을것이다. 

1
2
3
4
5
6
7
8
9
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    private Address korAddress;
    @Embedded
    private Address engAddress;
}
cs



@AttributeOverrides 또는 @AttributeOverride 애노테이션을 사용하면 위와 같은 문제를 해결할 수 있다. 이 두 애노테이션은 @Embedded로 매핑한 값 타입의 매핑 설정을 재정의할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
class Sight {
    private String id;
    private String name;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="kor_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="kor_address1"),
        @AttributeOverride(name="address2", column=@Column(name="kor_address2")
    })
    private Address korAddress;
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="zipcode", column=@Column(name="eng_zipcode"),
        @AttributeOverride(name="address1", column=@Column(name="eng_address1"),
        @AttributeOverride(name="address2", column=@Column(name="eng_address2")
    })
    private Address engAddress;
}
cs


위와 같이 설정하면 Address 밸류 클래스 필드값의 컬럼 설정을 재정의할 수 있다. 기존에는 address1, address2, zipcode가 중복되어서 오류가 발생하였지만 @AttributeOverrides, @AttributeOverride 애노테이션을 이용하여 컬럼이 중복되지 않도록 재정의하였다. 


데이터를 객체를 모델링하다보면 밸류 클래스의 중첩이 일어날 수 있는데 JPA에서는 중첩 @Embeddable도 지원된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Entity
public class City {
    // ...
    
    // 도시 연락처정보
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="address.zipcode", column=@Column(name="city_zip")),
        @AttributeOverride(name="address.address1", column=@Column(name="city_addr1")),
        @AttributeOverride(name="address.address2", column=@Column(name="city_addr2"))
    })
    private ContractInfo contractInfo;
}
 
@Embeddable
public class ContractInfo {
    @Column(name="ct_phone")
    private String phone;
 
    @Column(name="ct_email");
    private String email;
 
    @Embedded
    // City 클래스에서 재정의 안했을 경우 ContractInfo 클래스에서도 
    //@AttributeOverrides({
    //    @AttributeOverride(name="zipcode", column=@Column(name="ct_zip")),
    //    @AttributeOverride(name="address1", column=@Column(name="city_addr1")),
    //    @AttributeOverride(name="address2", column=@Column(name="city_addr2"))
    //})
    private Address address;
}
 
 
cs



밸류를 다른 테이블에 저장하기

sight 테이블을 Sight 엔티티와 Address 밸류로 매핑한 것처럼 하나의 테이블을 엔티티와 밸류로 저장할 수도 있지만, 
엔티티와 밸류를 각각의 테이블로도 저장할 수 있다. UML로 표현하면 아래와 같은 구조를 가진다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Embeddable
public class SightDetail {
    @Column(name="hours_op")
    private String hourcOfOperation;
    private String holidays;
    private String facilites;
}
 
@Entity
@SecondaryTable(
    name ="sight_detail",
    pkJoinColumns= @PrimaryKeyJoinColumn(     //sight_detail의 sight_id(기본키=외래키)가 sight테이블의 id(식별자)를 참조함
        name = "sight_id", referencedColumnName = "id")
public class Sight {
    // 생략..
 
    // table = sight_detail은 데이터를 해당 테이블에서 가져오겠다는 의미
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(
            name="hoursOfOperation",
            column=@Column(name="hours_op", table="sight_detail")),
        @AttributeOveride( 
            name="holidays",
            column=@Column(table="sight_detail")),
        @AttributeOverride(
            name="facilities",
            column=@Column(table="sight_detail"))
    })
    private SightDetail detail;
}
cs




'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

이번 포스팅에서는 JPA에서 식별자를 생성하는 방식에 대하여 알아보도록 하겠습니다. 식별자 생성 방식에는 애플리케이션에서 직접 생성하는 방식(특별한 식별자 규칙이 있는 경우)과 JPA 에서 생성하는 방식이 있습니다. JPA가 식별자를 생성하는 방식은 다시 식별 칼럼 방식, 시퀀스 방식, 테이블 방식이 있습니다.


JPA에서 식별자를 생성하는 방식


식별 칼럼 방식

JPA가 식별 칼럼을 이용해서 식별자를 생성하려면 @Id 애노테이션 대상에 추가적인 설정이 필요합니다. @GeneratedValue 애노테이션을 추가하고 strategy 값을 설정하면 됩니다.

@Entity @Table(name="hotel_review") public class Review { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; }


GenerationType.IENDTITY 는 식별자 생성을 데이터베이스의 식별자 생성 방식을 사용하여 식별자를 생성하는 방법입니다. 데이터베이스에서 기본키(식별자)를 auto_increment로 생성한다면 이에 의존하여 엔티티의 식별자를 생성하는 것이죠. 


사실 데이터베이스의 식별 칼럼이라는 것을 잘 모르겠어서 아래에서 정리할 "시퀀스 방식"과 큰 차이점을 모르겠네요.. 글을 읽으신 선배님이 계시다면 가르침 부탁드립니다 ㅠㅠ 


시퀀스[각주:1] 방식

@SequenceGenerator 애노테이션을 이용하면 데이터베이스의 시퀀스를 이용하여 식별자를 생성할 수 있습니다. 

    @Id
    @SequenceGenerator(
        name = "review_seq_gen",
        sequenceName = "hotel_review_seq",
        allocationSize = 1
    )
    @GeneratedValue(generator="review_seq_gen")
    private Long id;



@SequenceGenerator의 name으로 설정한 값은 @GeneratorValue 애노테이션에서 generator로 사용됩니다. sequenceName으로 설정한 값은 실제 데이터베이스의 시퀀스이며, allocationSize는 시퀀스 증가값을 설정합니다. 참고로 allocationSize의 기본값은 50인데 1로 사용해야 별탈없이 사용이 가능한데 다음에 기회가 된다면 정리하도록 하겠습니다.


시퀀스 방식을 사용하는 경우는 persist() 시점에 insert 쿼리를 실행하지 않고 시퀀스 관련 쿼리만 실행하여 식별자를 생성합니다.


1
2
3
transaction.begin();
Review review = new Review("KR-S-01"5"최고입니다", new Date());
entityManager.persist(review); // select hotel_review_seq.nextval from dual 
cs



테이블 방식

식별자를 구분하기 위해 사용할 주요키 칼럼과 다음 식별자로 사용할 숫자를 보관할 컬럼을 가지는 테이블을 이용하는 방법입니다. 아래의 id_gen 테이블은 entity 컬럼에서 엔티티를 구분하기위한 용도로 사용되고 nextid 컬럼은 해당 엔티티의 다음 식별자로 사용된다.


1
2
3
4
create table id_gen(
    entity varchar(100not null primary key,
    nextid int
engine innodb character set utf8;
cs


그럼 테이블 방식으로 엔티티의 식별자를 생성해보도록 하겠습니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class City {
    @Id
    @TableGenerator(name = "idgen",
        table = "id_gen",
        pkColumnName = "entity",
        pkColumnValue = "city",
        valueColumnName = "nextid",
        initialValue = 0,
        allocationSize = 1)
    @GeneratedValue(generator = "idgen")
    private Long id;
}
cs


  • name : 테이블 생성기의 이름을 지정합니다. @GeneratedValue의 generator 속성값으로 사용됩니다.
  • table: 식별자를 생성할 때 사용할 테이블을 지정합니다.
  • pkColumnName: 식별자 생성용 테이블의 주요키 칼럼을 지정합니다.
  • pkColumnValue: 주요키 칼럼에 사용할 값을 지정합니다. 각 @TableGenerator마다 다른 값을 사용해야하며 보통 엔티티 클래스의 이름을 사용합니다.
  • valueColumnName: 생성할 식별자를 갖는 칼럼을 지정합니다.
  • initialValue: 식별자의 초기값을 지정합니다. 
  • allocationSize: 식별자를 해당값만큼 증가시킵니다. 이 값 역시 1로 설정해야 합니다.


1
2
3
4
5
6
7
8
9
10
11
transaction.begin();
 
City city = new City("서울");
entityManager.persist(city);
 
transaction.commit();
 
 
//select tbl.nextid from id_gen tbl where tbl.entity='city' for update
//insert into id_gen (entity, nextid) values ('city', 1) // 레코드가 존재하지 않을 때 식별자 테이블에 insert, 이미 식별자가 존재하는 경우는 쿼리 
//update id_gen set nextid=2 where nextid=1 and entity='city'
cs



위의 코드는 persist() 메서드를 실행하는 시점에 식별자 테이블에서 식별자를 구하고 다음에 식별자로 사용할 값을 업데이트하는 것을 확인할 수 있습니다.

  1. 순차적으로 증가하여 유일한 값을 생성해주는 객체 [본문으로]

'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
@Embeddable  (0) 2019.02.19
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01
JPA란  (0) 2018.12.29

이번에는 엔티티(Entity) 객체와 엔티티를 다루는 엔티티매니저(EntityManager) 객체에 대해 알아보겠습니다.


엔티티(Entity)

엔티티는 영속성을 가진 객체로 DB 테이블에 보관할 대상입니다. 즉 영속 컨텍스트에 속한 객체를 말합니다. 이러한 엔티티는 특정한 시점에 DB에 영향을 미치는 쿼리를 실행하게 됩니다.


이러한 역할을 하는 엔티티를 설정하는 방법은 2가지가 있는데 하나는 @Entity 애노테이션을 이용하는 것이고 다른 하나는 xml 설정을 이용하는 것입니다. 대부분 애노테이션을 이용하므로 애노테이션을 이용하여 엔티티를 설정하는 방법을 알아보도록 하겠습니다.



엔티티를 설정하기 위한 애노테이션


@Entity 애노테이션

 이전에 JPA에서는 쿼리를 자동으로 생성해준다고 했었습니다. JPA는 클래스 이름을 테이블 이름으로 사용하는데 아래의 Room 엔티티는 기본적으로 Room 테이블과 매핑됩니다.

@Entity
public class Room {
//...
}



@Table 애노테이션

그렇다면 클래스이름과 테이블의 이름이 다른 경우는 어떻게하면 될까요? @Table 애노테이션을 이용하면 클래스이름과 테이블 이름이 다르더라도 name 속성을 설정하여 테이블의 이름을 지정할 수 있습니다.

@Entity
@Table(name="room_info")
public class Room{
// ...
}



@Id 애노테이션

DB가 레코드를 구분하기 위해 주요키를 사용하는 것처럼 JPA는 엔티티의 @Id 애노테이션을 이용하여 식별자를 지정합니다. @Id 애노테이션을 지정한 필드 값은 엔티티를 식별하기 위한 식별자로 사용됩니다. 예를 들면 아래 코드에서 id = ? 쿼리에서 ? 자리에 @Id 설정한 필드가 사용됩니다.
public Optional findRoom(int roomId) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpastart");
    EntityManager em = emf.createEntityManager();
    try {
        Room room = em.find(Room.class, roomId);  // SELECT ... FROM Room r WHERE id = ? 
        return Optional.ofNullable(room);
    } finally {
        em.close();
    }
}



@Basic 애노테이션

@Id 애노테이션을 제외한 나머지 영속 대상은 @Basic 애노테이션을 사용합니다. 아래의 Room 엔티티는 @Basic 애노테이션이 보이지 않는데 생략된 것으로 로 이해하시면 됩니다.
 
@Entity
@Table(name="room_info")
public class Room {
    @Id
    private String number;
    private String name;
    private String description;
}


@Enumerated 애노테이션

열거타입에 대한 매핑은 @Enumerated 애노테이션을 사용합니다. 

// 호텔 등급 열거형 public enum Grade { STAR1, STAR2, STAR3, STAR4, STAR5 }

//호텔 엔티티
@Entity
public class Hotel {
    @Id
    private String id;
    private String name;
    @Enumerated(EnumType.STRING)
    private Grade grade;
}

Hotel 엔티티를 보면 @Enumerated의 속성으로 EnumType.STRING 으로 지정하고 있습니다. 이 속성은 @Enumerated 애노테이션으로 설정한 열거형을 DB로 저장할 때 어떤 값으로 저장할지 결정합니다. EnumType.STRING으로 저장하는 경우 hotel 테이블에는 grade 필드에 "STAR1, STAR2, STAR3, STAR4, STAR5"란 문자열이 저장될 것이고 EnumType.ORDINAL을 사용하는 경우 grade필드에는 0~4(인덱스)가 저장될 것입니다. 여기서 인덱스0은 STAR1, 인덱스4는 STAR5를 가리킵니다. 열거형의 순서는 바뀔 가능성이 있기때문에 ORDINAL을 사용하는 것보다는 STRING을 사용하는 것이 유지보수에 더 유리할 것으로 보입니다.



@Column 애노테이션

@Column 애노테이션은 프로퍼티의 이름과 테이블의 칼럼 이름이 같다면 생략이 가능하지만, 다를 경우에는 @Column 애노테이션을 지정해줘야 합니다. 
@Entity
@Table(name="room_info")
public class Room {
    @Id
    private String number;
    private String name;
        
    @Column(name="description")
    private String desc;
}


좀 이후에 정리하겠지만 JPA는 INSERT, UPDATE, DELETE의 동작이 보통과 다르기 때문에 예상하지 못하거나 실수를 방지하기 위해 읽기 전용 매핑설정이  가능합니다. 읽기 전용 프로퍼티를 설정하는 방법은 insertable 속성과 updateble 속성을 false로 설정하면 됩니다. 읽기 전용 프로퍼티는 JPA가 자동으로 생성하는 쿼리에서 제외됩니다.

@Entity @Table(name="room_info") public class Room { //... @Column(name="id", insertable=false, updatable=false) private Long dbId; }



아래와 같이 insertable 속성과 updatable 속성을 false로 지정하면 JPA에서 쿼리를 생성할 때 해당 컬럼을 제외하는 것을 확인할 수 있습니다.

entityManager.persist(new Room("R202", "홍길동", "2번룸")); // insert into room_info (description, name, number) values (?, ?, ?);

- insert 제외 예시(id가 쿼리에서제외)

entityManager.getTransaction().begin();
Room room = entityManager.find(Room.class, "R202");
room.changeName("리뉴얼 2번룸"); // name을 바꾸는 메서드.
entityManager.getTransaction().commit(); 
// update room_info set description = ?, name = ? where number = ?;
- update 제외 예시(id가 쿼라에서 제외)



@Access 애노테이션

JPA는 DB에서 읽어온 데이터를 엔티티 객체에 매핑하거나, 엔티티 객체를 DB에 반영할 때 엔티티 클래스의 필드 또는 프로퍼티(get/set 메서드)를 사용합니다. 
 예시의 Room 엔티티를 살펴보면 필드에 @Id, @Column 애노테이션을 사용한 것이 아니라 프로퍼티에 사용하고 있습니다. 이렇게 프로퍼티에 애노테이션을 설정하면 DB에서 값을 읽어와 엔티티 객체에 전달할 때는 set메소드를 이용하고, 엔티티를 DB에 반영할 때는 get메소드를 이용하기 때문에 get/set 메소드를 모두 정의해야 합니다.


@Entity @Table(name="room_info") public class Room { private String number; private String name; private String desc; private Long dbId; @Id public String getNumber() { return number; } // setter.. @Column(name="description") public String getDesc(){ return desc; } // setter.. @Column(name="id", insertable=false, updatable = false) { public String getDbId() { return dbId; } // setter.. }


근데 일부는 필드에 일부는 프로퍼티에 설정해보았는데 각각 필드와 프로퍼티를 이용하여 매핑이 되는 것을 확인했습니다. @Access 애노테이션을 사용하여 접근 방식을 지정해야하는 필요성은 잘 모르겠는데 아마 클래스 단위로 접근 방식을 제어하기 위한 것이 아닌가 싶습니다. 

@Entity
@Table(name="room_info")
@Access(AccessType.PROPERTY) // or @Access(AccessType.FIELD)
public class Room {
    // @Id 만약 애노테이션을 필드에 사용하면 에러가 발생함..
    private String number;
    private String name;
    private String desc;
    private Long dbId;

    @Id
    public String getNumber() {
        return number;
    }
    // setter..
    @Column(name="description") 
    public String getDesc(){
        return desc;
    }
    // setter..
    @Column(name="id", insertable=false, updatable = false) {
    public String getDbId() {
        return dbId;
    }
    // setter..
}



@Transient 애노테이션

필드에 transient 키워드나 @Transient 애노테이션을 사용하면 영속대상에서 제외됩니다.
@Entity
@Table(name="room_info")
public class Room {
    @Id
    private String number;
    //...

    @Transient
    private long timestamp = System.currentTimeMills();
}


@Transient 애노테이션을 사용한 엔티티를 조회하면 아래와 같이 쿼리에서 해당 필드가 제외되는 것을 확인할 수 있습니다.

Room find = em.find(Room.class, room.getNumber());
//Hibernate: select room0_.number as number1_0_0_ from room_info room0_ where room0_.number=?



엔티티매니저(EntityManager)

엔티티매니저는 영속 컨텍스트에 접근하여 엔티티에 대한 DB 작업을 제공합니다. 엔티티 매니저의 메서드에 대해 알아보도록 하겠습니다.
 

find() 메서드

find() 메서드는 영속 컨텍스트에서 엔티티를 검색하고 없을 경우 DB에서 데이터를 찾아 영속 컨텍스트에 저장합니다. 여기서 식별자는 Entity 클래스에서 @Id 애노테이션으로 지정한 값을 사용해야 합니다. 

public find(Class entityClass, Object primaryKey)



persist() 메서드

persist() 메서드는 엔티티를 영속 컨텍스트에 저장 후 INSERT 쿼리를 실행합니다. 보통 커밋시점에 INSERT 쿼리를 실행하는데 바로 실행하는 경우도 있습니다. 이는 식별자 생성 방법에 따라 달라지는데 해당 내용은 이후에 정리하도록 하겠습니다.
public void persist(Object entity)


persist() 메서드는 트랜잭션 범위 내에서 실행해야 합니다. persist() 메서드는 실행시점에 영속 컨텍스트에 엔티티를 저장하고, 트랜잭션을 commit() 하는 시점에 insert 쿼리가 실행되므로 트랜잭션 범위에서 실행하지 않는다면 실제로 DB에 반영되지 않습니다.  

EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
    transaction.begin();
    Hotel hotel = new Hotel("H101","서울호텔","STAR5");
    entityManager.persist(hotel);
    transaction.commit();
} catch(Exception e) {
    transaction.rollback();
    ...
} finally {
    entityManager.close();
}



remove() 메서드

remove() 메서드는 엔티티 클래스를 영속 컨텍스트에서 삭제 후 DELETE 쿼리를 실행합니다
public void remove(Object entity)

 

remove() 메서드 역시 트랜잭션 범위 내에서 실행되어야 하며 트랜잭션이 commit() 하는 시점에 delete 쿼리가 실행됩니다.

EntityManager em = emf.createEntityanager();
EntityTransaction transaction = em.getTransaction();

try {
    transaction.begin();
    
    Room room = em.find(Room.class, "R101");
    if(room != null) {
        em.remove(room);
    }
    transaction.commit();
} catch(Exception e) {
    transaction.rollback();
    ...
} finally {
    em.close();
}



엔티티 수정

엔티티 매니저는 별도의 update 메서드를 제공하지 않습니다. JPA는 트랜잭션 범위에서 엔티티 객체의 상태가 변경되면 이를 트랜잭션 커밋 시점에 반영한다. 이 사실을 모르고 사용하면 매우 위험할 것 같으니 주의가 필요합니다

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
try {
    transaction.begin();
    Room room = em.find(Room.class, "R101");
    if(room != null) {
        room.changeName("카프리");
    }
    transaction.commit();
} catch (Exception e) {
    transaction.rollback();
    //...
} finally {
    em.close();
}




'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
@Embeddable  (0) 2019.02.19
JPA 식별자 생성 방식  (0) 2019.01.29
JPA란  (0) 2018.12.29


실무에서 JPA를 사용하고 있는데 올바르게 사용하고 있는지 확신이 들지않아 최근에 공부를 시작했습니다. "JPA 프로그래밍 입문"이란 책을 읽고 공부하며 제가 이해한 내용을 정리하는 곳이니 참고용으로만 봐주시면 감사할 것 같습니다. 


JPA(Java Persistence API)란 무엇인가? 

JPA란 DB 테이블과 자바 객체 사이의 매핑을 처리해주는 ORM[각주:1]이란 기술의 표준입니다. 따라서 JPA에는 객체와 DB 사이의 매핑을 어떻게 설정하고 어떻게 동작해야하는지 기술하고 있습니다. 즉 자바의 클래스와 DB의 테이블을 매핑하는 기술이란 뜻입니다.


JPA를 살펴보다면 하이버네이트(hibernate)란 말을 많이 접하게 됩니다. 그렇다면 하이버네이트란 무엇일까요?


JPA 프로바이더

하이버네이트란 JPA 프로바이더의 한 종류입니다. JPA 프로바이더는 JPA의 표준을 실제로 구현하고 있습니다. 쉽게 말하자면 JPA는 DB와 자바 객체를 매핑하기 위한 인터페이스(API)를 제공하고 JPA 프로바이더는(하이버네이트) 이 인터페이스를 구현한 것이죠.


JPA 특징

  1. 자바 객체와 DB 테이블 사이의 매핑 설정을 통해 SQL을 생성합니다. 보통 JDBC를 사용하여 개발을 하다보면 코드가 비슷한 형태로 반복되게 됩니다.
    DB커넥션을 구하고, 쿼리를 작성하고, 파라미터를 설정하고 실행한 결과를 자바 객체에 설정합니다.
     여기서 문제점은 테이블의 컬럼명이 추가, 삭제, 변경이 된다면 관련되어 있는 모든 쿼리를 수정해야 할 것인데 JPA는 매핑 설정만 변경하면 됩니다.
    또한 실행한 쿼리를 자바 객체에 설정해줘야하는데 JPA는 자바 객체로 매핑하여 검색할 수 있습니다. 즉 유지보수가 쉽습니다.

  2. 객체를 통해 쿼리를 작성할 수 있는 JPQL(Java Persistence Query Language)를 지원합니다.

  3. JPA는 성능 향상을 위해 지연 로딩이나 즉시 로딩과 같은 몇가지 기법을 제공하는데 이것을 잘 활용하면 SQL을 직접 사용하는 것과 유사한 성능을 얻을 수 있습니다. 하지만 잘못이해하고 사용하는 JPA는 성능을 크게 감소시킬 수 있습니다.

JPA 설정

JPA는 기본적으로 클래스패스에 있는 META-INF/persistence.xml 파일을 설정 파일로 사용합니다. 다음은 일반적인 persistence 파일입니다.

<persistence-unit> 태그의 name은 영속 단위를 설정하고 transaction-type은 JPA가 사용할 트랜잭션을 설정합니다. <class> 태그는 영속 관리할 클래스를 지정하며 <exclude-unlisted-classes>속성을 true로 설정하면 <class>태그에서 설정하지 않은 클래스들은 영속 관리 대상에서 제외됩니다. 




JPA 영속성

앞에서부터  "영속성"이란 반복되고 있는데 영속성이란 무엇일까요? JPA에서는 @Entity 애노테이션을 붙인 클래스를 "엔티티"라고 부릅니다. 엔티티는 다음에 자세히 알아보도록 하겠습니다. 

영속 컨텍스트는 엔티티를 담고있는 집합인데 JPA는 이 영속 컨텍스트에 속한 엔티티를 DB에 반영합니다. 즉 프로그램에서는 엔티티를 영속 컨텍스트에 
속한 엔티티를 검색하거나 삭제 또는 추가, 변경하는 작업을 하면 영속 컨텍스트의 내용이 DB에 반영되는 것이죠.


하지만 프로그램에서는 영속 컨텍스트에 직접 접근할 수 없고 EntityManager를 통해서만 영속 컨텍스트에 접근할 수 있습니다. 






  1. Object-Relational Mapping [본문으로]

'웹 개발 > JPA 프로그래밍' 카테고리의 다른 글

EntityManager 와 영속 컨텍스트  (0) 2019.03.03
@Embeddable  (0) 2019.02.19
JPA 식별자 생성 방식  (0) 2019.01.29
엔티티(Entity)와 엔티티매니저(EntityManager)  (0) 2019.01.01

+ Recent posts