Chapter 14. Image Replacement

Fahrner Image Replacement (FIR)

  • CSS를 적용해서 이미지로 교체할 텍스트를 표시한다.
<h1 id="fir">Fahrner Image Replacement</h1>
  • 텍스트를 제거해야 하니까 <span>으로 감싸서 제거한다.
<h1 id="fir"><span>Fahrner Image Replacement</span></h1>

#fir span {

display: none;

}
  • fir css를 사용해서 백그라운드에 이미지로 만든 글자를 적용한다.
#fir {

width: 287px;

height: 29px;

background: url(fir.gif) no-repeat;

}
  • 장점: css를 지원하지 않는 브라우저에서는 그냥 보통 텍스트라도 보여줄 것이다.
  • 단점: display가 비주얼하게 글자를 숨기는 것 뿐만 아니라, 완전히 없애드릴 수도 있다. <span>이라는 별의미없는 태그가 필요하다. 이미지를 꺼버리는 사용자들에게는 난감하다.

Leahy/Langridge Image Replacement (LIR)

  • 별의미 없는 <span> 엘리먼트 없이 텍스트를 이미지로 바꾸기
  • 엘리먼트의 높이를 0으로 만들고 교체할 이미즤 크기만큼 padding-top을 설정한다.
<h1 id="lir">Leahy/Langridge Image Replacement</h1>


#lir {

padding: 90px 0 0 0;

overflow: hidden;

background: url(lir.gif) no-repeat;

height: 0px !important; /* for most browsers */

height /**/:90px; /* for IE5/Win */

}
  • Screen-reader 애플리케이션이 텍스트를 읽을 수 있다.(display 속성을 사용하지 않아서.)
  • 이미지를 꺼두고 CSS만 켜두는 사람에게는 아무것도 안보인다.
  • IE5/Windows 브라우저를 위해서 Box Model 핵을 사용해야 한다.

The Phark Method

  • 글자 들여쓰기를 엄청많이 해서 페이지에 존재하고 있지만, 보이지는 않도록 한다.
<h1 id="phark">The Phark Method</h1>

#phark {

height: 26px;

text-indent: -5000px;

background: url(phark.gif) no-repeat;

}
  • 간단해서 좋아보이지만 여전히 이미지를 꺼두면 안 보인다.

Chpater 13. Styling Text

Hypertext를 좀 더 멋지게 보이게 하는 방법

Times They are a-Changing

  • Times(Times new roman) 글자체는 여러 브라우저의 기본 글자체다. however, this
    could easily be changed by users to whatever they fancy, and of course
    shouldn’t be relied upon.

줄 높이 설정하기

body {

line-height: 1.5em;

}
  • 줄 높이를 설정하여 가독성을 높일 수 있다.

글자체 설정하기

body {

font-family: "Lucida Grande", "Trebuchet MS", sans-serif;

line-height: 1.5em;

}
  • 사용자의 컴퓨터에 설치되어 있는 폰트를 고려하여 여러 개의 폰트를 설정한다.
  • 맨 앞에 것이 없으면 그 다음 것을 적용한다.
  • 글자체가 여러 단어로 구성되어 있다면, 따옴표로 묶는다.

Kerning (a.k.a. Letter-Spacing)

h1 {

letter-spacing: -2px;

}
  • 글자들의 간격을 조정할 수 있다.

Drop Caps

<p><span class="drop">I</span>f you're painting with latex paints, and
  • 문단을 멋지게 표현하기 위한 스타일 훅이 필요하다.
  • CSS2를 적용하는 브라우저들은 이런 훅이 필요없이 :first-letter 사용하면 된다.
.drop {

float: left;

font-size: 400%;

line-height: 1em;

margin: 4px 10px 10px 0;

padding: 4px 10px;

border: 2px solid #ccc;

background: #eee;

}

Text alignment

body {

font-family: Georgia, Times, serif;

line-height: 1.5em;

text-align: justify;

}
  • text-align: justify를 사용하면 양쪽으로 정렬된다.

Transforming Text

h1 {

letter-spacing: 4px;

font-style: italic;

text-align: center;

text-transform: uppercase;

}
  • text-transform: uppercase;를 사용해서 글자를 모두 대문자로 바꿀 수 있다.

Small Caps

h1 {

letter-spacing: 4px;

text-align: center;

font-variant: small-caps;

}
  • font-variant: small-caps;를 사용해서 전부 대문자로 바꿔주고, 단어의 첫 글자 이외의 글자들을 조금 작게 보여준다.

문단 들여쓰기

p {

text-indent: 3em;

}

Isolation 단계 더 높이기

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로 보장해야
    하는 경우에 유용하게 사용할 수 있을 것 같다.
  • 결국 테스트 코드를 많이 만들어서 테스트해봐야겠다.

낙천적인 동시접근 제어

특징

  • 모든 게 다 잘 될거라고 가정을 하는 접근법이다.
  • 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로 설정해야 줘야 한다.

IT 신조어가 필요한 시대

트랜잭션을 공부하던 중 Isolation을 한글로 뭐라고 할지 고민해봤습니다.(삼천포..) 가장 먼저 떠오른건 ‘분리’, 그 다음은 ‘격리’ 마지막은 ‘독립’.

트랜잭션 Isolation의 의미는 이전 글에서도 정리했지만, 마치 트랜잭션 입장에서 보면 현재 진행중인 트랜잭션이 자기 밖에 없는 것으로 보이지만, 실제로는 동시에 여러 트랜잭션들이 활개치고 있는 상황을 뜻합니다.

즉 물속에 물고기는 여러 마리인데 마치 물고기 입장에서 세상을 보면 자기 밖에 없는 것 같아 보이도록 하는 것입니다. 그 수준을 조정할 수 있어서 완전히 정말 자기밖에 없는 것 처럼 보이게(Serializable) 수준(level)을 조정할 수도 있지만 성능 저하가 심하기 때문에(바다에 물고기 한 마리만 넣은 상태와 거의 비슷), 어느 정도 수준을 낮춰서 남들도 있긴 있는데 서로 심각한 피해를 주지 않는 수준(Read Commit-다른 물고기가 먹고 있는 먹이는 쳐다보지도 않음, Repeatable Read-자기가 찜해둔 먹이를 고이 간직하고 있어서 언제든 그 먹이를 쳐다볼 수 있음)에서 여러 마리의 물고기(성능 좀 나아짐)를 풀어놓습니다.

대체 이걸 한글로 뭐라고 표현해야 할까요..

1. 분리: 그런 의미에서 다른 트랜잭션들과 자기 자신 트랜잭션을 별도로 띠어 놨다는 뜻에서 분리라는 단어가 떠올랐습니다. 그런데 분리 한다고 해서 꼭 개별적으로 분리하지는 않으니까 의미가 좀 안어울리는 감이 있습니다.

2. 격리: 따라서 단 하나로 분리한다는 의미가 느껴지는 격리가 좀 더 마음에 들었습니다. 그런데 격리라는 단어로부터 떠오르는 이미지가 감방, 독방, 죄 와 같은 굉장히 부정적인 이미지 입니다. 트랜잭션을 분리시키는 의도는 문제를 해결하기 위한 좋은 의도인데 어감이 부정적이여서 참 느낌이 불편했습니다.

3. 독립: 그런 의미에서 문제로 부터 해방되는 해방감의 느낌이 나는 독립이 떠올랐습니다. 다른 것들과 독립적으로 일을 수행하는 트랜잭션.. 그럴 듯 합니다. 일단 어감이 긍정적이고 독고다이의 ‘독’자가 한 개의 의미를 나타내 주기 때문에 셋 중에서 가장 좋은 단어라고 생각이 됩니다. 그러나 이 생각이 들었을 때는 이미 ‘격리’를 사용하여 정리를 끝낸 상태였습니다.

이렇게 세 가지 단어에 대해 생각하고 나니 드는 생각은 왜 전부 한자어일까. 라는 것이었습니다. 순 우리말은 없을까? 순 우리말은 누가 만들어 내는건가? ‘지못미’, ‘이뭥미’ 이런 단어들은 계속해서 만들어 지는데 왜 ‘트랜잭션’, ‘아이솔레이션’ 이런 의미를 표현할 수 있는 단어는 만들어지지 않는 것일까? 그래서 저는 IT에도 신조가 계속 생겨나야 되지 않을까 생각해봤습니다. 그런면에서 저는 북한의 한글이 부러워졌습니다. 예전에는 참 고집이 쎄다. 라고 생각했었는데 이제는 숙연함까지 느끼게 됩니다. 저는 빨갱이가 아닙니다. 그냥 북한의 그런 고집(?)을 이어받아 순수 한글로 된 IT 신조어들을 만들어 내면 좋겠다고 생각해봤을 뿐입니다.

사용자 삽입 이미지
참 어려운 일인것 같습니다. 어문학과도 아니고 책을 많이 읽어보지도 않았고 공부는 해야겠고 새로운 기술 용어는 계속 등장하고.. 아니 대체 하이버네이트의 Conversation은 지금 있는 한국어 중에서 어떤 단어로 대체 할 수 있겠습니까? 뭐 틀리진 않지만(‘의사소통’이 가장 근접한 것 같지만.. 단일 요청/응답도 의사소통이고 여러개의 요청/응답도 의사소통이라고 할 수 있는데,
여러개의 요청/응답을 하나의 묶음으로 처리하려는 Conversation보다 ‘의사소통’이라는 단어가 좀 더 포괄적이라고
느껴집니다.) 완전히 새로운 단어가 필요한 시대가 온 것 같습니다. 한글에도 큰~~ 개혁과 변화 그리고 발전이 있기를…

사용자 삽입 이미지그림출처: 링크 에 가시면 세종대왕이 훈민정음을 창제한 이유가 적혀있습니다.