Whiteship's Note


하이버네이트 VS JPA

Hibernate/Chapter 10 : 2008.09.09 22:28


 하이버네이트
JPA
 Transaction API를 사용하여 JDBC와 JTA를 사용할 수 있다.  EntityTransaction API는 resource-local 트랜잭션을 사용할 때에만 유용하다.
 하이버네이트 JTA랑 EJB의 CMT를 연동할 때 사용할 수 있다.  Java SE와 Java EE 에서 DB 커넥션 이름이 바뀌는 것 빼고 추가적인 설정은 필요없다.
 하이버네이트 automatic versioning을 사용해서 optimistic concurrency control을 제일 효율적으로 제공한다.  자동 버전관리로 낙천적인 동시성 관리하는 것을 표준화 했다.


top


하이버네이트에서 Nontransactional data access

Hibernate/Chapter 10 : 2008.04.01 17:11


Session session = sessionFactory.openSession();
session.get(Item.class, 123l);
session.close();
  1. Session이 열리고 이 순간 Connection을 얻어오진 않는다.
  2. get()을 호출하는 순간 Select 문을 날리는데, 이 때 Connection을 pool에서 꺼낸다. 하이버네이트는 기본으로 그 즉시 autocommit mode를 끈다. setAutoCommit(false). 이렇게 효율적으로 JDBC 트랜잭션을 시작한다.
  3. SELECT는 JDBC 트랜잭션 내부에서 실행된다. 하이버네이트는 Connection의 close()를 호출하여 Connection을 poll에 반납한다. 이 때 남아있는 트랜잭션은 어떻게 될까?
  • 사용하는 DB에 달려있다. JDBC 스펙에서는 이 것과 관련되서 정해논게 없다.
  • 오라클은 트랜잭션을 커밋한다.
  • 다른 많은 JDBC 밴더들은 트랜잭션을 롤백한다.
  • 위는 Select 문이라서 상관없지만, sequence를 가져온 다음에 INSERT은 날리지 않고 flush 될 때까지 기다린다. 그러다 그냥 끝나게 되니까 INSERT 문이 날아가지 않는다.
  • idendity 전략으로 PK를 생성할 때에는 DB에 들어가야 얻을 수 있으니까, INSERT문이 바로 날아간다.
  • 이렇게 트랜잭션 경계를 설정하지 않으면 위와 같은 일이 발생하는데, 이때에는 JDBC 커넥션을 오토커밋 모드로 설정해준다.
<property name="connection.autocommit">true</property>
  • 즉 DB Connection을 가져올 때 setAutoCommit(false) 이걸 호출하지 않는다.
top


autocommit에 관한 오해

Hibernate/Chapter 10 : 2008.04.01 17:11


트랜잭션 없이 쿼리를 날릴 수 있는가?

  • 트랜잭션 범위 밖에서 DB와 뭔가를 할 수가 없다. 불가능하다. 어떤 SQL 문도 DB 트랜잭션 밖에서 날릴 수는 없다.
  • nontransactional은 명시적인 트랜잭션 경계가 없다는 것이다. 시스템 트랜잭션이 없다는 것이다. 그렇게 하면 데이터에 접근할 때 autocommit 모드로 실행된다.

성능 향상

  • 잘 생각해봐야한다. 모든 SQL 문마다 트랜잭션을 열고 닫고 할 텐데 아마도 성능이 떨어질 것이다.

애플레이션 확장성 증가

  • 잘 생각해봐야한다. DB 트랜잭션이 길어지면 분명 락을 가지고 있는 시간이 길어지니까 확장성이 안 좋을 수 있다. 하지만 하이버네이트의 persistence context와 write-behind 특징을 생각해보면 모든 쓰기 락들은 매우 짧은 기간 동안만 가지고 있게 된다. isolation level을 높였을 때 사용하게 되는 읽기 락의 비용은 무시해도 좋은 수준이다. 아니면 아예 읽기 락이 아닌 multiversion concurrency를 지원하는 DB(오라클, PostgreSQL, Infomix, Firebird)를 사용하면 된다.

포기해야 하는 것들

  • SQL 문장들을 그룹핑하여 원자성을 보장하는 것이 불가능.
  • 데이터가 병렬적으로 수정될 때 isolation level이 낮아진다. repeatable read가 불가능하다.

Data Access Type

  • 보통의 트랜잭션
  • Read-only 트랜잭션
  • Nontransactional
top


Isolation 단계 더 높이기

Hibernate/Chapter 10 : 2008.03.30 00:25


Explicit pessimistic locking

  • 격리 수준을 read comitted 보다 높게 설정하는 것은 애플리케이션의 확장성을 고려할 때 좋치 않다.
  • Persistence context cache가 repeatable read를 제공하긴 하지만 이걸로 항상 만족스럽지 않을 수도 있다.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);
String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 코드는 DB에서 같은 데이터를 두 번 읽어온다. 이 때 isolation level이 read committed 였다면, 두 번째에 읽어오는 값이 처음 읽어온 데이터와 다를 수 있다.(둘 사이에 어떤 트랜잭션이 해당하는 값을 바꾸고 커밋했을 수 있다.)
  • 전체 트랜잭션의 isolation level을 높이는 것이 아니라 lock() 메소드를 사용하여 해당하는 부분의 트랜잭젼의 isolation level을 높일 수 있다.
Session session = sessionFactory.openSession(); 
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);

session.lock(i, LockMode.UPGRADE);

String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 LockMode.UPGRADE 는 item 객체 대응하는 레코드의 pessimistic lock을 가지고 다니게 된다.
Item i = (Item) session.get(Item.class, 123, LockMode.UPGRADE);
  • 위와같이 코드를 한 줄 줄일 수도 있다.
  • LockMode.UPGRADE는 롹을 가져올 때까지 대기하게 된다. 대기 시간은 사용하는 DB에 따라 다르다.
  • LockMode.NOWAIT는 롹이 없으면 기다리지 않고 쿼리가 바로 fail하도록 한다.

The Hibernate lock modes

  • LockMode.NONE - 락 사용하지 않음. 캐시에 객체가 존재하면 그 객체를 사용.
  • LockMode.READ - 모든 캐시를 무시하고 현재 메모리에 있는 엔티티의 버전과 실제 DB에 있는 버전이 같은지 확인한다.
  • LockMode.UPGRADE - LockMode.READ가 하는 일에 더해서 DB에서 pessimistic upgrade lock을 가져온다. SELECT ... FOR UPDATE 문을 지원하지 않는 DB를 사용할 때는 자동으로 LockMode.READ로 전환된다.
  • LockMode.UPGRADE_NOWAIT - UPDGRADE와 동일한데, SELECT ... FOR UPDATE NOWAIT를 사용한다. 락이 없으면 바로 예외를 던진다. NOWAIT를 지원하지 않으면 자동으로 LockMode.UPGRADE로 전환된다.
  • LockMode.FORCE - 객체에 버전을 DB에서 증가시키도록 강제한다.
  • LockMode.WRITE - 하이버네이트가 현재 트랜잭션에 레코드를 추가했을 때 자동으로 얻어온다.(사용자가 명시적으로 애플리케이션에서 사용할 일 없음.)
  • load()와 get()은 기본으로 LockMode.NONE을 사용한다.
  • Detached 상태의 객체를 reattach 할 때 LockMode.READ 를 유용하게 사용할 수 있다. 자동으로 reattach까지 해주니까.(하이버네이트의 lock()메소드만 reattch까지 해주지, JP의 lock()메소드는 reattch해주지 않느다.)

reattche를 할 때 반드시 lock() 메소드를 사용해야 하는 것은 아니다. 이전에도 살펴봤듯이 Session에 update() 메소드를 사용하면 Transiecnt 상태의 객체가 Persistent 상태가 된다. lock(LockMode.READ)는 Persistent 상태로 사용하려는 객체의 데이터들이 이전에 로딩된 그 상태 그대로 인지, 혹시 다른 트랜잭션에 의해 데이터들이 변경되지는 않았는지 확인하기 위한 용도다. 그렇게 확인을 함과 동시에 덤으로 Persistent 상태로 전환(reattach)시켜 주는 것이다. 즉, Transient 상태의 객체를 lock() 메소드의 인자로 넘겨줄 수 있다는 것인데, 이것은 하이버네이트에서만 할 수 있다. JP에서는 이미 Persistent 상태인 객체한테만 lock()을 호출할 수 있다.

Item item = ... ; 
Bid bid = new Bid();
item.addBid(bid);
...
Transaction tx = session.beginTransaction();
session.lock(item, LockMode.READ);
tx.commit();

Forcing a version increment

  • 하이버네이트가 개발자가 수정한 내용을 버전을 올려야 하는 변경사항인지 모를 수가 있다. 이럴 때 명시적으로 버전을 올리라고 알려줘야 한다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 하이버네이트는 객체와 직접적으로 닿아있는 값의 변화만을 알지 한 단계 걸친 변화는 해당 객체의 수정사항으로 인식하지 않는다.
  • 위의 코드에서 BillingDetail 객체만 버전을 올리게 된다. 하지만 개발자는 정보를 수정한 BillingDetail(aggregate)을 가지고 있는 User(root object) 역시 버전을 올리고 싶어 할 수 있다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
session.lock(u, LockMode.FORCE);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 이렇게 하면 현재 User 객체에 대응하는 레코드를 가지고 작업하고 있는 모든 Unit of work들이 해당 객체의 버전이 올라갔다고 인식한다.
  • 심지어 아무런 변경을 가하지 않았더라도 해당 Root 객체의 버전은 올라간다.

공부할 것

  • 사용하는 DB에 따라 다른 결과가 나올 수 있다. Select ... FOR UPDATE NOWAIT 문을 지원하느냐 안 하느냐에 따라 LockMode.UPGRADE 와 LockMode.UPGRADE_NOWAIT의 결과가 LockMode.READ 와 같게 나올 수도 있다.
  • 결국 원하는 Isolation level을 정하는 것이 중요하고, repeatable read를 보장하려면 LockMode.UPGRADE 또는 LockMode.UPGRADE_NOWAIT를 사용하여 pessimisitc locking하면된다.
  • LockMode.READ는 DB에서 데이터를 읽어와서 버전을 확인한다. isolation level을 미리 올려두는 것이 아니라, optimistic 한 방법으로 DB에 쓰기 직전에 확인하기 위한 용도라고 생각된다.
  • 자동 버전 증가는 오직 엔티티가 직접적으로 물고 있는 속성, 콜렉션 자체의 변화만 인식한다. 객체 맵의 루트를 올려야 한다면, 해당 루트 객체를 가져올 때 LockMode.FORCE를 사용하며 이 녀석은 isolation level과 별 상관이 없어 보이지만, 해당 엔티티를 사용하는 트랜잭션들의 isolation level을 repeatable read로 보장해야 하는 경우에 유용하게 사용할 수 있을 것 같다.
  • 결국 테스트 코드를 많이 만들어서 테스트해봐야겠다.
top


낙천적인 동시접근 제어

Hibernate/Chapter 10 : 2008.03.28 19:30


특징

  • 모든 게 다 잘 될거라고 가정을 하는 접근법이다.
  • unit of work의 마지막에 데이터를 쓰려고 할 때 에러를 발생시킨다.

낙천적인 전략 이해하기

사용자 삽입 이미지

  • 두 트랜잭션 모두 read commited는 기본이니까 dirty read는 허용하지 않는다.
  • 하지만 non repeatable read는 가능하다. 그리고 둘 중 하나의 update가 분실 될 수도 있다.
  • lost updat를 처리할 수 있는 세 가지 방법
    1. Last commit wins - 마지막에 커밋하는 녀석이 앞선 변경 사항을 덮어쓴다. 에러 메시지 없다.
    2. First commit wins - 두 번째로 같은 데이터에 커밋하는 녀석이 있을 때 예외를 던진다. 사용자는 새로운 컨버세이션을 시작여 새로운 데이터를 가져와서 다시 작업해야 한다.
    3. 충돌하는 업데이트 병합하기 - 두 번째로 같은 데이터에 커밋하려는 녀석이 있을 때 예외를 던지고 사용자가 선택적으로 무조건 덮어 쓸 수 도 있고 다시 시작할 수도 있도록 한다.
  • optimistic concurrency control이 없거나 기본값인 상태에서는 무조건 last commit wins 전략으로 동작한다. 비추다.
  • 하이버네이트와 JP는 automatic optimistic locking을 사용하여 first commit wins 전략을 사용하게 해준다.
  • Merge conflicting changes가 가장 사용자 입장에서 좋은데, 이런 창을 띄우고 메시지를 출력하는 것은 개발자의 몫이다.

하이버네이트에서 Versioning 하기

  • 각각의 Entity들은 version을 가지고 있고 숫자나 타입스탬프로 표현할 수 있다.
  • 하이버네이트는 이 version을 Entity가 수정될 때마다 버전을 증가 시키고 만약 충돌이 발견되면 예외를 던진다.
  • 이 version 속성을 Entity에 추가해준다.
public class Item {
...
private int version;
...
}
  • XML에서 설정할 때 이 속성에 대한 맵핑은 반드시 id 속성 설정 바로 다음에 위치해야 한다.
  • 세터를 사용하지 말고 필드에 직접 쓰도록 설정하고 세터를 만들어 두지 않는게 좋다. 하이버만 쓸 수 있도록...
public class Item {
...
private Date lastUpdated;
...
}
  • Timestamp를 사용할 수도 있는데 이건 약간 덜 안전하다. 비추한다.

비추하는 이유

Clustered 환경에서 JVM으로부터 현재 시간을 가져오는 건 위험하다. jvm이 두 개니까 둘이 같을 수도 있겠지. 한 개에서 가져오면 같을 일이.. 없겠지만, 어쨋든 그래서 타입스탬프를 DB에서 가져오도록 설정할 수 있다. source="db"라고 <timestamp> 맵핑에 추가하면 된다. 그런데 이것도 DB를 매번 다녀오니까 추가비용이 발생하고 하이버네이트의 모든 SQL Dialect가 이걸 지원하는 것도 아니다.

자동 버전 관리


자동 버전 관리 동작 원리

두 개의 트랜잭션이 같은 데이터에 가져온다. 이때 버전 넘버를 확인한다. 1이라고 하자. 그뒤에 Update문이 실행 될 때 다시 버전 넘버를 가져와서 확인한다. 맨 처음에 가져온 넘버와 같으면 커밋하고 버전 넘버를 증가시킨다. 버전 넘버가 다르면 누군가 데이터를 변경한 것이기 때문에 현재 트랜잭션은 예전 데이터를 가지고 작업하고 있는 것이다. 그러면 Update문을 날리지 않는다. 그럼 SQL의 결과 수정된 레코드의 수가 0이다. 이 숫자가 0이면 하이버는 StaleObjectStateException을 던진다. 이 예외를 잡아서 화면에 에러 메시지 보여주고 사용자가 컨버세이션을 다시 시작하도록 할 수 있다.

  • 언제 Entity의 버전을 올리는가? Entity 객체가 가지고 있는 속성들이 dirty 할 때에만 올린다.

버전 넘버나 Timestamp 없이 버전 관리하기

  • 레거시 DB나 기존의 자바 클래스를 사용하고 있어서 버전 컬럼을 추가할 수 없어도, 하이버네이트는 자동 버전 관리를 할 수 있다.
  • 단, 객체를 가져오고 수정하는 Persistent Context(Session)가 같아야 한다. 따라서 Detached 객체를 가지고 Conversation을 구현할 때에는 자동 버전 관리가 불가능하다. 그 때는 버전 넘버나 Timestamp가 필요하다.
  • 버전 컬럼 대신에 모든 컬럼을 비교한다. optimistic-lock="dirty" 라고 설정하면 dirty 상태의 속성만 비교한다. 이때에는 update문을 dirty한 컬럼으로만 생성해야 하니까 dynamicUpdate를 true로 설정해야 한다.

Java Persistent 사용하여 버전 관리하기

  • Java Persistent는 동시 접근을 Versioning을 사용하여 낙천적으로 관리한다고 가정한다.
@Entity
public class Item {
...
@Version
@Column(name = "OBJ_VERSION")
private int version;
...
}
  • JPA는 버전 속성 없이 optimistic versioning을 못하니까 하이버 애노테이션을 사용해야 한다.
@Entity
@org.hibernate.annotations.Entity(
optimisticLock = org.hibernate.annotations.OptimisticLockType.ALL
)
public class Item {
...
}
  • 락을 OptimisticLockType.DIRTY로 설정할 수 있다. 그럴 때는 dynamicUpdate 속성의 값도 true로 설정해야 줘야 한다.
top


DB 수준에서 동시접근 이해하기

Hibernate/Chapter 10 : 2008.03.28 10:23


특징

  • 하이버네이트 애플리케이션 개발자로써 반드시 갖춰야 할 역량은 사용하는 DB의 기능과 특정 상황에서 DB isolation 레벨을 어떻게 조정할 수 있을지 이해하는 것이다.
  • 완벽한 격리(Isolation)는 비용이 크다. 그 단계(lebel)를 조정해서 완전한 분리의 정도를 낮추고 시스템의 성능과 확장성을 높일 수 있다.

트랜잭션 격리 이슈

  • 이슈들을 지칭하는 용어는 ANSI SQL 표준에 정의되어 있다.
  • lost update: 두 개의 트랜잭션이 하나의 데이터에 업데이트를 할 때 나중에 업데이트한 트랜잭션이 롤백 되버리면 첫 번째 트랜잭션이 커밋한 데이터까지 날아가 버린다.

사용자 삽입 이미지

  • dirty read: 하나의 트랜잭션이 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽었을 때 다른 트랜잭션이 롤백 될 수 있기 때문에 이상한 데이터를 읽은 꼴이 될수도 있다.

사용자 삽입 이미지

  • unrepeatable read: 하나의 트랜잭션이 두 번 같은 데이터를 읽어왔는데 그 사이에 다른 트랜잭션이 해당 데이터를 조작하여 그 값이 다를 수 있다.

사용자 삽입 이미지

  • second lost updates problem: unprepeatble read의 한가지 형태로 첫 번째 트랜잭션이 데이터에 쓰기를 하고 커밋을 했는데 다른 트랜잭션이 같은 데이터에 쓰기를 하고 커밋을 하면 앞선 트랜잭션의 결과는 날아가 버린다. 근데 이건 어쩌면 당연한 걸 수도 있다. 나중에 자세히 다룬다.
  • phantom read: 트랜잭션이 쿼리를 두 번 날렸는데 그 사이에 다른 트랜잭션이 데이터를 추가하거나 삭제해서 그 결과가 다를 수 있다.

사용자 삽입 이미지

ANSI 트랜잭션 격리 레벨

Read uncommitted

  • lost update는 막고 dirty read는 허용.
  • 다른 트랜잭션에 의해 커밋되지 않은 데이터에 쓰기 작업을 할 수 없다. 하지만 읽기는 가능하다.
  • exclusive write lock을 사용한다.

Read committed

  • dirty read는 막고 unrepeatable read는 허용.
  • 커밋되지 않은 쓰기 트랜잭션은 다른 트랜잭션들이 해당 레코드에 접근도 못하게 한다. 읽기 쓰기 전부 안 됨. 하지만 읽기 트랜잭션은 다른 트랜잭션이 자신이 읽고 있는 레코드에 (읽기 쓰기 모두) 접근하는 것을 허용한다.
  • shared read lock과 exclusive write lock을 사용한다.

Repeatable read

  • unrepeatable read와 dirty read 둘 다 막는다. phantom read는 허용.
  • 읽기 트랜잭션이 사용하는 데이터에 대한 쓰기 트랜잭션을 막는다. 쓰기 트랜잭션이 사용하는 데이터에 접근하는 다른 모든 트랜잭션을 막는다.

Serializable

  • 트랜잭션들을 일렬로 세워서 차례 차례 실행시키는 것과 같다.
  • low-level lock만 사용해서는 구현하기 어렵다.

격리 수준(Isolation level) 선택하기

  • 다음은 권고 사항이지 돌에 새겨넣은 말 같은 것이 아니다.
  • read uncommitted 격리 수준은 사용하지 말아라.
  • 대부분의 경우 serializable 격리 수준 까지느 필요없다. phantom read가 보통 문제를 일어키지는 않는다. 성능에 심각한 영향을 끼친다.
  • 그러면 read committed와 repeatable read 둘 만 남았네.
  • repeatable read: read lock이 write lock을 막는다. 이 방법 말고 versioned data를 사용하면 하이버네이트가 알아서 해준다. 하이버네이트의 Persistence context cache와 versioning이 repeatable read를 보장해준다. 따라서, versioned data를 사용하기만 하면 모든 DB 트랜잭션이 이 격리 수준을 사용할 수 있다.

격리 수준 설정하기

  • DBMS마다 기본 격리 수준이 있다. 보통 read committed 또는 repeatable read 중 하나다.
  • hibernate.connection.isolation = 4 이렇게 설정할 수 있다.
    • 1—Read uncommitted isolation
    • 2—Read committed isolation
    • 4—Repeatable read isolation
    • 8—Serializable isolation
  • 이렇게 하이버 설정에 설정해버리면 이건 global 한 설정이 된다.
  • 때로는 특정 트랜잭션 마다 설정할 수 있다.

top


동시접근 제어하기

Hibernate/Chapter 10 : 2008.03.28 10:19


  • transaction isolation: 각각의 트랜잭션 입장에서 보면 현재 진행중인 트랜잭션이 자기 밖에 없는 것으로 보이는 것.
  • 예전에는 이것을 locking으로 보장했었다. 트랜잭션들이 특정 데이터에 대한 lock을 가지고 다녔디.
  • multiversion concurrency control(MVCC): 몇몇 오라클이나 PostreSQL와 같은 DB는 transaction isolation을 이걸 사용해서 구현했다.
  • DB가 동시 접근 제어를 어떻게 구현했는지는 하이버네이트나 Java Persistence 애플리케이션을 사용할 때 가장 중요한 사항이다.
  • 낙천적인 동시접근 제어
  • Isolation 단계 더 높이기
top


하이버네이트 애플리케이션의 트랜잭션

Hibernate/Chapter 10 : 2008.03.27 07:52


Java SE에서의 코딩을 통한 트랜잭션

  • hibernate.transaction.factory_class 설정 기본 값이 org.hibernate.transaction.JDBCTransactionFactory로 되어 있기 때문에 별도의 설정이 필요없다.
  • TransactionFactory 구현체를 만들어서 Transaction 인터페이스를 확장하거나 커스터마이징 할 수 있다.
Session session = null;
Transaction tx = null;
try {
session = sessionFactory.openSession();
tx = session.beginTransaction();
concludeAuction(session);
tx.commit();
} catch (RuntimeException ex) {
tx.rollback();
} finally {
session.close();
}
  • JDBC Connection은 트랜잭션이 시작할 때 얻어온다.
  • beginTransaction()은 setAutoCommit(false)를 설정하게 된다.
  • 이렇게 하면 Session이 db 커넥션과 묶이게 된다.
  • 트랜잭션을 commit() 하면 db 커넥션은 반환되고, Session을 풀리게 된다. 또 다른 트랜잭션을 해당 Session에서 시작하면, 새로운 커넥션을 얻어온다.

예외 처리하기

  • 3쩜대 버전 부터는 하이버네이트의 모든 메소드가 RuntimeException을 던지게 되어 있다.
  • tx.rollback() 마져도 RuntimeException을 던지기 때문에, 로깅을 남길 때, Exception객체를 인자로 넘겨주는 것을 잊지 않도록 한다.
  • tx.setTimeout(int second)를 사용하여 일정 시간 이내에 처리되지 않으면 예외를 던지도록 설정할 수 있다.
  • Exception들이 분류되어 있다.
  1. HibernateExcepion은 getCause()를 사용해서 좀 더 구체적인 정보를 확인해봐야 한다.
  2. JDBCException은 SQL 문에서 발생한 에러이다. getSQL()을 호출하여 SQL을 확인해볼 수 있다. DB 벤더 에러 코드를 보려면 getErrorCode()를 호출하면 된다.

JTA를 사용하여 코드에서 트랜잭션 처리하기

  • JTA의 javax.transaction.UserTransaction 인터페이스를 사용하여 리소스를 관리한다.
  • 꼭 Application Seerver가 있어야만 JTA를 사용할 수 있는 건 아니다. JBoss Transactions는 오픈소스 독립적인 JTA 프로바이더를 제공한다. 이것을 Tomcat에서 돌리고 있는 하이버네이트 애플레키에션에 설치할 수 있다.
  • JTA로 관리되는 리소스를 사용하는 장점
  1. 트랜잭션 관리 서비스는 모든 리소스를 통합하고 트랜잭션 제어를 단일 표준 API로 관리할 수 있도록 해준다.
  2. JEE 트랜잭션 관리자는 여러개의 리소스를 하나의 트랜잭션으로 묶어서 관리할 수 있다.
  3. JTA 구현체의 품질이 간단한 JDBC 커넥션 풀 보다 좋다.
  4. JTA 프로바이더는 런타인시에 불필요한 오버헤드를 발생시키지 않는다.
  • 하이버에서 JTA 트랜잭션 사용하기
  1. 애플리케이션 코드는 수정할 필요 없이 그냥 하이번네이트의 Transaction API를 사용한다.
  2. 설정파일에서 hibernate.transaction.factory_class 속성의 값을 org.hibernate.transaction.JTATransactionFactory로 설정한다.
  3. hibernate.transaction.manager_lookup_class 속성의 값에 JTA 구현체의 Lookup 클래스를 설정해준다.(ex. org.hibernate.transaction.JBossTransaction-ManagerLookup)
  4. 하이버는 더이상 JDBC 커넥션 풀을 관리하지 않는다. 커넥션들을 JNDI를 통해서 JTA 프로바이더로부터 받아온다.
  • JTA를 사용할때와 그렇지 않을 때의 차이
    • JTA를 사용할 때: DB 커넥션을 Session을 사용하려고 할 때 가져온다.(more aggressive), 같은 트랜잭션 내에서는 항상 같은 커넥션을 가져오도록 Application Server가 보장해준다.
    • JTA를 사용하지 않을 때: DB 커넥션을 트랜잭션 시작할 때 가져와서 끝날 때 반환한다.
  • JTA 시스템은 전역적인 트랜잭션 타임아웃을 지원한다.
UserTransaction utx = (UserTransaction) new InitialContext()
.lookup("java:comp/UserTransaction");
Session session1 = null;
Session session2 = null;
try {
utx.begin();
session1 = auctionDatabase.openSession();
session2 = billingDatabase.openSession();
concludeAuction(session1);
billAuction(session2);
session1.flush();
session2.flush();
utx.commit();
} catch (RuntimeException ex) {
try {
utx.rollback();
} catch (RuntimeException rbEx) {
log.error("Couldn't roll back transaction", rbEx);
}
throw ex;
} finally {
session1.close();
session2.close();
}
  • JTA 인터페이스를 사용하여 구현할 때에 기본 설정은 명시적으로 flush()를 해야하며, session도 명시적으로 닫아줘야 한다.
  • hibernate.trans-action.flush_before_completion 속성과 hibernate.transaction.auto_close_session 속성을 설정하면 코드를 줄일 수 있다.
UserTransaction utx = (UserTransaction) new InitialContext()
.lookup("java:comp/UserTransaction");
Session session1 = null;
Session session2 = null;
try {
utx.begin();
session1 = auctionDatabase.openSession();
session2 = billingDatabase.openSession();
concludeAuction(session1);
billAuction(session2);
utx.commit();
} catch (RuntimeException ex) {
try {
utx.rollback();
} catch (RuntimeException rbEx) {
log.error("Couldn't roll back transaction", rbEx);
}
throw ex;
}
  • 가능하면 JTA를 직접 사용하기를 권하고 있다. 이식성을 애플리케이션 밖으로 끌어내기 위해서..

컨테이너가 관리하는 트랜잭션

  • 선언적인 트랜잭션 경제는 컨테이너에게 트랜잭션 관리의 책임을 넘기는 것이다.(스프링은 IoC 컨테이너에게 넘긴다. 여기서는 WAS를 얘기하고 있음.)
  • CMT는 독입적인 JTA 구현체에서는 제공하지 않는다.
@Stateless
public class ManageAuctionBean implements ManageAuction {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void endAuction(Item item) {
Session session1 = auctionDatabase.openSession();
Session session2 = billingDatabase.openSession();
concludeAuction(session1, item);
billAuction(session2, item);
}
...
}
  • 컨테이너가 TransactionAttribute를 읽은 뒤, 그에따라 트랜잭션을 적용한다.
  • 위에서는 REQUIRED기 때문에 현재 트랜잭션이 있으면 그걸 사용하고 없으면 새로 만들어서 사용한다.
  • 필요한 설정
    • hibernate.transaction.factory_class 속성을 org.hibernate.transaction.CMTTransactionFactory 로 설정.
    • hibernate.transaction.manager_lookup_class 속성을 사용하는 application server의 lockup 클래스로 설정.
top


데이터베이스와 시스템 트랜잭션

Hibernate/Chapter 10 : 2008.03.27 07:50


특징

  • 모든 SQL은 반드시 트랜잭션 안에서 수행된다.
  • 트랜잭션은 Commit 되거나 Rollback 된다.
  • Unit of work의 경계를 정해주어야 한다.
  • 특정 시점에 트랜잭션을 시작하고, 나중에 커밋하거나 예외가 발생했을 때 롤백하도록 해야 한다. 이것을 Transaction demarcation이라고 한다.

코딩을 통한 transaction demarcation

  • 일반적인 JDBC API를 사용할 때에는 Connection의 setAutoCommit(false)로 설정한 다음, 작업이 끝날 때, commit()을 호출하고, 예외가 발생하면, rollback()을 호출한다.
  • 여러 DB를 사용하는 작업을 하는 경우, JDBC만 가지고는 원자성을 처리 할 수 없다. 하나의 시스템 트랜잭션으로 여러 자원을 묶어서 처리하는 transaction manager가 필요하다.
  • Java Transaction API가 바로 그러한 트랜잭션 처리를 위한 용도이다. JTA의 UserTransaction 인터페이스에 begin()과 commit()을 사용하여 시스템 트랜잭션을 처리할 수 있다.
  • 하이버네이트는 자체에서 Transaction 인터페이스를 제공한다. 이 API를 쓰도록 강요하지는 않는다. JDBC의 트랜잭션 API를 사용할 수도 있다. 하지만 그러면 여러분의 코드가 JDBC에 종송적이게 된다.
  • 하이버네이트의 Transaction은 JTA 위에서도 동작한다.

선언적인 transactiondemarcation

  • EJB 컨테이너가 선언적인 트랜잭션 서비스를 제공하며, 이것을 CMT라고 부른다.
  • 스프링도 제공해 주는데, 이 책은 스프링 얘기가 하나도 없네.
top


트랜잭션 기초

Hibernate/Chapter 10 : 2008.03.27 07:49


  • Atomic: 원자성
  • Consistency: 비지니스 룰을 깨트리면 안 된다.
  • Isolation: 동시에 같은 데이터에 대한 여러 작업이 독립적으로 수행되어야 한다.
  • Durability: 트랜잭션의 결과는 영속적으로 유지되어야 한다.
top