부트스트래핑 bootstraping
애플리케이션이 실행될 떄 필요한 객체들을 초기화하고 준비하는 과정이다. 이 과정에서 데이터 상호작용을 추상화를 위해 ORM기술을 도입했다.
컨테이너 방식의 부트스트래핑
WAS가 실행될 때 초기화하고 WAR배포를 기반으로 두고 실행한다. 이때 서버가 생명주기를 관리하고 설정은 주로 XML이나 Spring에서 설정을 한다.
EntityManager
EntityManager은 jpa가 제공하는 기능으로 내부에는 Persistence Context, 1차캐시, 변경 감지 상태, 트랜잭션 hibernate는 이에 대한 보충으로 생명주기를 포함한 메서드를 제공해준다.
jpa는 하나의 작업당 하나의 entityManager을 사용하게 된다.
EntityManager와 ThreadSafe
Entitymanager은 상태(state)를 가지 객체이기 때문에 threadSafe하지는 않다.
만약 이 상황에서 멀티쓰레드로 사용한다면 위험할 수가 있다. 그래서 요청 단위로 생성해서 사용하는 것이 좋다.
Spring framework에서는 tomcat webserver을 사용하여 하나의 요청당 하나의 쓰레드를 생성하기 때문에 entityManager를 바인딩하여 처리를 하게 된다.
Singleton 구조인 EntityManagerFactory를 만들어서 시행하기
EntityManagerFactory라는 직접 비지니스 작업을 수행하지 않고 persistence unit정보, DB 연결 설정, 매핑 메타데이터 등의 공유해도 되는 정보를 갖고 있는 객체를 만들고 각 쓰레드가 자기만의 EntityManager을 받도록 하여 처리를 한다.
참고 singleton으로 두어야 설정
- 무거운 초기화 비용이 크다.
- 설정 정보를 중심으로 가진다.
- 여러 요청이 공유해도 안전헤야 한다
public class App {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("myUnit");
public void doWork() {
EntityManager em = emf.createEntityManager();
try {
// 작업
} finally {
em.close();
}
}
}
위 코드와 같이 싱글톤으로 설정한 EntityManageFactory 객체를 두고,
각 쓰레드로 사용할 EntityManager은 EntityManageFactory로 부터 만들어지게 해서 수행을 하였다.
사용한 annotation 정리
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
}
- @Entity
- Entity로 만들기 위해 사용
- @Table
- java의 객체이름과 SQL에서 설계한 테이블의 이름이 다를 수 있기 때문에
sql에서도 이를 인식할 수 있게 어노테이션으로 적어놓는다.
- java의 객체이름과 SQL에서 설계한 테이블의 이름이 다를 수 있기 때문에
- @Id
- Entity의 primary key를 정하는 어노테이션이다.
- @Enumerated(EnumType.STRING)
- enum 타입으로 선언한 요소를 사용하고 싶을 때 쓰는 어노테이션이다.
- @GeneratedValue(strategy = ?)
- 엔터티의 기본 키를 어떻게 생성할 지 전략을 지정하는 어노테이션
- 전략 종류
- strategy = GenerationType.IDENTITY
- DB가 PK를 자동생성, JPA가 아닌 DB가 키를 만드는 방법으로
가장 많이 사용하는 방법이다.
- DB가 PK를 자동생성, JPA가 아닌 DB가 키를 만드는 방법으로
- strategy = GenerationType.SEQUENCE
- DB의 SEQUENCE를 이용한다. DB 내용물의 해쉬처럼 순서를 매겨서 이를 가져와서 사용한다.이를 사용한다면 INSERT전에 jPA가 id를 먼저 확보하고 진행하여 성능은 되게 좋다고 볼 수가 있다. 쓰기 지연(Transactional Write-Behind)을 사용하는 것이 가능하다.
- strategy = GenerationType.TABLE
- 별도의 테이블로 id를 관리한다.
하지만 동시성 문제가 많이 발생할 우려가 있어서 실무에서는 거의 쓰지 않는다.
- 별도의 테이블로 id를 관리한다.
- strategy = GenerationType.AUTO
- DB종류를 보고 알아서 결정한다
편하지만 예측하기 어렵다. 별도의 strategy를 설정하지 않는다면 default 설정은 auto로 동작한다.
- DB종류를 보고 알아서 결정한다
- strategy = GenerationType.IDENTITY
- @Column()
테이블의 컬럼에 관한 설정하는 어노테이션이다. 제약조건 같은 것을 덧붙일 수가 있다.- (nullable = false)
null값을 방지하는 제약을 건다 - (columnDefinition = “INT DEFAULT 0”)
Default 값을 0으로 설정한다.
- (nullable = false)
- @ManyToMany, @OneToMany, @ManyToOne
다른 테이블과의 관계 설정 - @JoinTable
@JoinTable(
name = “product_categories”,
joinColumns = @JoinColumn(name = “product_id”),
inverseJoinColumns = @JoinColumn(name = “category_id”)
)
product와 category의 관계를 product_categories라는 중간테이블로 연결
joinColumns = @JoinColumn(name = “product_id”)은 현재 엔티티의 FK컬럼이고 자기 자신의 PK를 저장하는 칼럼을 의미한다. inverseJoinColumns은 연결대상의 엔티티의 PK를 저장하게 된다.
자기순환 문제
자기참조는 category와 같이 부모자식 형태를구현할 수 있다는 장점이 있지만 이게 양방향 관계에서 나타내게 된다면 문제가 될 수가 있다. 양방향 관계에서 이게 나타나기가 쉽고 실행시켰을 때 무한 재귀가 발생할 수가 있다. 또한 영속성 컨테스트에서 데이터 불일치까지 나올 수가 있다.
단방향 관계와 양방향 관계
단방향 관계는 한쪽 엔티티만 다른 엔티티를 참조하는 구조 양방향 관계는 서로 참조하는 구조이다. 단방향 관계에서 외래키를 가진 쪽이 주인이 되어 @ManyToOne을 설정한 column이 있는 쪽이 주인이 된다. 양방향은 상태 불일치, 순환 참조문제, 관리 포인트 증가와 같은 문제로 사용하는 것이 권장되지 않는다. 만약 양방향을 사용하고 싶으면 편의 메서드를 반드시 작성해야 한다.
편의 메서드
연관 관계를 한 번에 올바르게 설정해주는 메서드이다. 양방향은 설정하면 안되지만 한쪽을 작업하면 다른 한쪽도 반영할 필요가 있기 때문에 사용한다. 일관성 유지를 위해 사용한다.
public void addOrderItem(OrderItem item) {
items.add(item);
item.setOrder(this);
}
참고: entity 객체는 final을 쓰지 말것
편의 메서드를 사용하여 일대일 관계에서 관계의 주도권이 address 클래스가 가지고 있는데 member 클래스에서 setMember을 호출하여 정보를 가져올 수가 있다. 이 경우 순환구조가 생길 우려가 있어서 조심해야 하는데 이때 메서드를 protected를 사용해 연관관계 편의 메서드에 접근제한을 하여 외부 클래스에서의 접근을 막는다.
CRUD 적용 실습
private static void updateMember(Long id, String newName) {
EntityManager entityManager = emf.createEntityManager();
EntityTransaction tx = entityManager.getTransaction();
try{
tx.begin();
//select * from member where id = -99999;
//resultset = ``````
Member findMember = entityManager.find(Member.class, id);
if(findMember != null){
findMember.setName(newName);
}
//안써줘도 상관없음 변경감지를 하기 때문에
entityManager.persist(findMember);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally {
entityManager.close();
}
}
이때 사용하는 것은 update작업이라서 entityManager에서 변경감지를 하여 굳이 .persist 메서드를 사용할 필요가 없는 것이다.
참고 : java bin 방식으로도 xml 파일을 초기화 하는 것이 가능하다.
팁 : 개발 시 마크다운에 기능별 to do list 같은 것을 만들어 체크하면서 진행하면 좋다.
참고 : MyBatis의 2차캐시는 앵간하면 끄는 것이 맞다. 데이터 정합성 문제, 캐시 무효화 타이밍 어려움, 멀티 서버 환경에서 위험이라는 문제가 있어서 기본적으로 끄는 것이 좋다.