Hibernate -- Increment 와 Hilo 메 인 키 생 성 전략 원리

최근 프로젝트 에서 클 러 스 터 문제 가 발생 했 습 니 다. 예 를 들 어 우 리 는 두 개의 클 러 스 터 노드 가 있 습 니 다. 정상 적 인 상황 에서 한 노드 만 작업 (A) 하고 이상 이 발생 하면 다른 클 러 스 터 노드 (B) 로 전환 합 니 다.프로젝트 에 서 는 데이터베이스 메 인 키 생 성 정책 으로 Hibernate 의 increment 를 사용 합 니 다.그 원 리 는 다음 과 같 습 니 다. Hibernate 초기 화가 완료 되면 홈 키 를 가 져 올 때 데이터 베 이 스 를 조회 하여 가장 큰 Id 를 조회 합 니 다. 그 다음 작업 은 모두 메모리 에서 홈 키 의 자체 증 가 를 유지 하고 저장 할 때 데이터 베 이 스 를 업데이트 합 니 다. 그 소스 코드 는 다음 과 같 습 니 다.
package org.hibernate.id;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.jdbc.Batcher;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.hibernate.util.StringHelper;

public class IncrementGenerator
  implements IdentifierGenerator, Configurable
{
  private static final Log log = LogFactory.getLog(IncrementGenerator.class);
  private long next;
  private String sql;
  private Class returnClass;

  public synchronized Serializable generate(SessionImplementor session, Object object)
    throws HibernateException
  {
    if (this.sql != null) {
      getNext(session);
    }
    return IdentifierGeneratorFactory.createNumber(this.next++, this.returnClass);
  }

  public void configure(Type type, Properties params, Dialect dialect)
    throws MappingException
  {
    String tableList = params.getProperty("tables");
    if (tableList == null) tableList = params.getProperty("identity_tables");
    String[] tables = StringHelper.split(", ", tableList);
    String column = params.getProperty("column");
    if (column == null) column = params.getProperty("target_column");
    String schema = params.getProperty("schema");
    String catalog = params.getProperty("catalog");
    this.returnClass = type.getReturnedClass();

    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < tables.length; ++i) {
      if (tables.length > 1) {
        buf.append("select ").append(column).append(" from ");
      }
      buf.append(Table.qualify(catalog, schema, tables[i]));
      if (i >= tables.length - 1) continue; buf.append(" union ");
    }
    if (tables.length > 1) {
      buf.insert(0, "( ").append(" ) ids_");
      column = "ids_." + column;
    }

    this.sql = "select max(" + column + ") from " + buf.toString();
  }

  private void getNext(SessionImplementor session)
  {
    log.debug("fetching initial value: " + this.sql);
    try
    {
      PreparedStatement st = session.getBatcher().prepareSelectStatement(this.sql);
      try {
        ResultSet rs = st.executeQuery();
        try {
          if (rs.next()) {
            this.next = (rs.getLong(1) + 1L);
            if (rs.wasNull()) this.next = 1L;
          }
          else {
            this.next = 1L;
          }
          this.sql = null;
          log.debug("first free id: " + this.next);
        }
        finally {
          rs.close();
        }
      }
      finally {
         session.getBatcher().closeStatement(st);
      }
    }                                                  
    catch (SQLException sqle)
    {
       throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle, "could not fetch initial value for increment generator", this.sql);
    }
  }
}

/* Location:           D:\Workspace\HibernateTest\bin\lib\hibernate3.jar
 * Qualified Name:     org.hibernate.id.IncrementGenerator
 * Java Class Version: 1.4 (48.0)
 * JD-Core Version:    0.5.3
 */

            generate 방법 을 보십시오. sql 문장 이 Null 과 같 지 않 을 때 다음 버 전 을 가 져 옵 니 다. 즉, 데이터베이스 에서 가장 큰 id 를 가 져 온 다음 에 sql 을 null 로 설정 합 니 다.이렇게 하면 다음 에 id 를 가 져 올 때 데이터베이스 에서 가 져 오지 않 고 메모리 + 에 있 습 니 다.그러면 문제 가 생 길 수 있 습 니 다.예 를 들 어 시스템 이 현재 A 노드 에서 일 하고 있 는데 next 가 10 까지 갔 을 때 갑자기 네트워크 가 끊 겨 서 시스템 이 B 노드 로 전환 되 었 다. B 노드 가 id 를 얻 을 때 데이터 베 이 스 를 조회 하여 최대 치 를 얻 었 고 next 를 순조롭게 실행 하여 20 까지 갔다. 이때 A 노드 의 next 는 10 이 고 sql 문 구 는 null 이다.따라서 A 는 데이터 베 이 스 를 조회 하지 않 고 next 의 값 을 직접 가 져 오 면 10 부터 20 까지 의 id 가 메 인 키 충돌 이 발생 하고 A 노드 를 다시 시작 해 야 문 제 를 해결 할 수 있 습 니 다.
따라서 hibenate 를 사용 하 는 클 러 스 터 와 관련 이 있다 면 increment 를 메 인 키 생 성 전략 으로 사용 할 수 없습니다.위 에서 분석 한 바 와 같이 이 문 제 를 해결 하려 면 두 프로 세 스 간 의 메모리 공 유 를 해결 해 야 한다. 즉, next 변 수 를 공유 하 는 것 이지 만 실현 하기 에는 매우 복잡 하 다.마지막 으로 Hilo 의 메 인 키 생 성 전략 으로 해결 합 니 다.그 부분의 원본 코드 는 다음 과 같다.
 
   public Serializable doWorkInCurrentTransaction(Connection conn, String sql)
     throws SQLException
   {
     int result;
     int rows;
     do
     {
       sql = this.query;
       SQL.debug(this.query);
       PreparedStatement qps = conn.prepareStatement(this.query);
       try {
         ResultSet rs = qps.executeQuery();
         if (!(rs.next())) {
           String err = "could not read a hi value - you need to populate the table: " + this.tableName;
           log.error(err);
           throw new IdentifierGenerationException(err);
         }
         int result = rs.getInt(1);
         rs.close();
       }
       catch (SQLException sqle)
       {
         throw sqle;
       }
       finally {
         qps.close(); }
 
       sql = this.update;
       SQL.debug(this.update);
       PreparedStatement ups = conn.prepareStatement(this.update);
       int rows;
       try {
         ups.setInt(1, result + 1);
         ups.setInt(2, result);
         rows = ups.executeUpdate();
       }
       catch (SQLException sqle)
       {
         throw sqle;
       }
       finally {
         ups.close();
       }
     }
     while (rows == 0);
     return new Integer(result);
   }
 }

 위의 코드 는 메 인 키 를 가 져 올 때 hibenate 는 같은 사무 에서 데이터베이스 에서 메 인 키 를 꺼 내 메 인 키 를 업데이트 합 니 다.이렇게 하면 다른 프로 세 스 가 데이터베이스 에서 값 을 추출 할 때 최대 값 을 얻 을 수 있 도록 합 니 다.
관건 은 다음 코드 입 니 다.
 public class TableHiLoGenerator extends TableGenerator
 {
   public static final String MAX_LO = "max_lo";
   private long hi;
   private int lo;
   private int maxLo;
   private Class returnClass;
   private static final Log log = LogFactory.getLog(TableHiLoGenerator.class);
 
   public void configure(Type type, Properties params, Dialect d) {
     super.configure(type, params, d);
     this.maxLo = PropertiesHelper.getInt("max_lo", params, 32767);
     this.lo = (this.maxLo + 1);
     this.returnClass = type.getReturnedClass();
   }
 
   public synchronized Serializable generate(SessionImplementor session, Object obj) throws HibernateException
   {
     if (this.maxLo < 1)
     {
       int val = ((Integer)super.generate(session, obj)).intValue();
       return IdentifierGeneratorFactory.createNumber(val, this.returnClass);
     }
     if (this.lo > this.maxLo) {
       int hival = ((Integer)super.generate(session, obj)).intValue();
       this.lo = ((hival == 0) ? 1 : 0);
       this.hi = (hival * (this.maxLo + 1));
       log.debug("new hi value: " + hival);
     }
 
     return IdentifierGeneratorFactory.createNumber(this.hi + this.lo++, this.returnClass);
   }
 }

 
우 리 는 볼 수 있다. ,홈 키 를 조회 할 때 데이터베이스 의 홈 키 + 1 을 저장 합 니 다. 만약 에 이 사물 이 성공 하지 못 하면 this. lo 는 this. max Lo 보다 영원히 클 것 입 니 다. 이때 B 로 전환 되 더 라 도 다시 A 로 돌아 갈 때 먼저 데이터 베 이 스 를 조회 하여 this. hi 가 영원히 같 지 않도록 합 니 다.성공 하면 B 에서 A 로 전환 할 때 B 의 hi 값 이 A 의 hi 값 보다 적어도 this. max Lo + 1 크기 때문에 A 가 안에서 낮은 위 치 를 유지 하 더 라 도 B 와 같 지 않 습 니 다. this. lo 가 this. max Lo 보다 클 때 데이터 베 이 스 를 조회 하기 때 문 입 니 다.이 알고리즘 은 너무 아름답다.
그 상세 한 원 리 는 소스 코드 와 아래 의 링크 를 참조 하여 비교 할 수 있다.
http://hi.baidu.com/sai5d/blog/item/88e5f4db09e90277d0164e30.html

좋은 웹페이지 즐겨찾기