일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 티스토리챌린지
- @EntityListeners
- pooled-lo
- sendFractionalSeconds
- spring boot
- 오블완
- MYSQL
- MSSQL
- yml
- mysql =
- createEntityGraph
- https
- apatch poi
- 운동해서 광명찾자
- Protecle
- mysql equal null
- @CreateDate
- +9:00
- deserializer
- getEntityGraph
- fractional seconds
- AuditingEntityListener
- 1*1000
- 버전 문자열 비교
- NamedEntityGraph
- load order
- RootGraph
- mysql not equal null
- getDateCellValue
- EmbeddedId
- Today
- Total
Hello
hibernate @TableGenerator 사용기와 Optimizer 변경하기 본문
TABLE 전략 사용 테스트
기술 스택
java 8
hibernate 5
이 방법을 사용해본적이 없어서 위 과정을 알아보다 어떻게 동작하는지 궁금해져서 테스트해보았습니다.
table을 생성해 sequence처럼 동작한다고 설명되어 있어 시작 값 : 1, 증가 값 : 50 을 가질 때,
A server : db update 50 using value [1~50]
B server : db update 100 using value [51-100]
처럼 동작할 것으로 기대 했는데 예상과 다르게 동작 했다.
test case 1)
A 호출 -> B 호출 -> A 호출 -> B호출
pk 관리 테이블
데이터 테이블
- date_end를 보면 3번이 7보다 늦게 생성되었다.
- a서버가 1-5을 b서버가 6-10을 사용할 것을 예상했는데 다른결과가 나타났다.
test case 2)
A 호출 -> A호출 -> B호출 ->B호출
pk 관리 테이블
데이터 테이블
- b서버 시작 id가6으로 등록될 것이라고 예상했는데 다르게 7로 등록되었다.
@Entity
@Table(name = "tb_test1")
@TableGenerator(name = "tbGenerator",
table = "tb_increment_id",
pkColumnName = "name",
valueColumnName = "val",
allocationSize=5)
public class DateEntity {
private Integer id;
private LocalDateTime dateEnd;
private String serverName;
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "tbGenerator")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public LocalDateTime getDateEnd() {
return dateStart;
}
public void setDateEnd(LocalDateTime dateEnd) {
this.dateStart = dateEnd;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
}
위와 같이 동작하는 이유는 TableGenerator Hibernate에서 PooledOptimizer를 기본적으로 사용하기 때문입니다.
PooledOptimizer를 알아보기에 앞서 @TableGenerator에 대해 알아보겠습니다.
@TableGenerator 코드 일부
initialValue 의 default 0 인 것을 알 수 있습니다.
위 자바 코드에서 initialValue를 지정하지 않았기 때문에 default 0이 들어가게 됩니다.
근데, 데이터 테이블 이미지를 보면 디비에서 등록된 id 값 시작 번호는 1번으로 시작합니다.
이상하지 않나요?? 이 마법은 어노테이션 바인딩 할 때 일어납니다!
AnnotationBinder.buildIdGenerator
@TableGenerator일 때, getIdGenerationTypeInterpreter().interpretTableGenerator()를 호출합니다.
이전에 작성한 JPA 기본키 생성 전략 글 에서 FallbackInterpreter를 사용해 id 전략을 선택하는 것을 작성했습니다.
FallbackInterpreter.interpretTableGenerator 내부를 보면 여기에 비밀이 숨어 있었습니다.
initialValue + 1 로 addParam을 하기 때문에 id 시작값이 1로 등록 된 것이었습니다.
주석에 HHH-4884 를 hibernate github에서 찾아보았지만 찾을 수없었다. 어디에 가야 있나~
그럼 initalValue가 뭔지 알게 되었으니, hibernate optimizer 에 대해 알아보겠습니다. hibernate는 기본적으로 다음과 같은 내장 최적화 프로그램이 함께 제공됩니다.
none
최적화가 수행되지 않습니다. 생성기에서 식별자 값이 필요할 때마다 데이터베이스와 통신합니다.
pooled-lo
pooled-lo 최적화 프로그램은 증분 값이 데이터베이스 table/sequence 구조로 인코딩된다는 원칙에 따라 작동합니다.
poole
pooled-lo와 비슷하지만 여기서 테이블/시퀀스의 값이 값 풀의 상위 끝으로 해석된다는 점만 다릅니다.
hilo; legacy-hilo
table or sequence 의 단일 값을 기반으로 값 풀을 생성하기 위한 사용자 정의 알고리즘을 정의합니다.
이 optimizer는 사용하지 않는 것이 좋습니다. 이전에 이러한 전략을 사용했던 레거시 애플리케이션에서 사용하기 위해 여기에 유지 관리 및 언급됩니다.
애플리케이션은 계약에 정의된 대로 자체 최적화 전략을 구현하고 사용할 수도 있습니다
org.hibernate.id.enhanced.Optimizer
TableGenerator과 SequenceGenerator은 기본 적으로 pooled optimizer를 사용합니다. OptimizerFactory.determineImplicitOptimizerName는 어떤 optimizer를 사용할지 선택합니다.
위 코드의 return을 보면, AvailableSettings.PREFER_POOLED_VALUES_LO 옵션이 true 경우에만 pooled_lo를 사용하고 나머지는 pooled를 사용하는 것을 알 수 있습니다.
위에서 찾은 optimizer name으로 실제 옵티마이저를 결정하게 됩니다.
우리는 pk 시작값 : 1, optimizer : pooled를 사용하는 것을 알게 되었습니다. 그럼, pooled가 어떻게 동작하는지 한번 알아보겠습니다.
PooledOptimizer.generate
기본키 생성을위해 hibernate.Optimizer.generate가 호출 됩니다.
처음 호출
1) hiValue == null => true
1-1) callback.getNextValue를 통해 다음 값을 읽음 hiValue = 1
1-2) value를 넣는 조건문에서 hiValue == initialValue => true 통과 된다.
1-3) value = 1
return value.makeValueThenIncrement ( value++;)
두번째 호출
1) hiValue == null => false
1-1) value >hiValue => true (2 > 1)
1-2) callback.getNextValue 호출해 hiValue 갱신 hiValue = 5
1-3) value = hiValue.subtract(5-1) ->value -= 4; value = 2
return value.makeValueThenIncrement ( value++;)
hiValue가 5가 아니라 1이 리턴되는 것에 의문이 들것입니다. 분명 증가 값으 5로 작성했는데 왜 시작값인 1이 리턴이 되었는지..!
이유는 TableGenerator.generate에 있습니다.!!
각각의 Generator는 AccessCallback 를 구현해 optimizer에서 호출할 수 있도록 합니다.
아래 이미지에서 표시된 부분을 보면 select, insert update 작업이 일어납니다.
차례대로 설명하면,
select 시 조회된 값이 없으면 insert 후 update
select 시 조회된 값이 있으면 update
작업을 진행합니다.
select 과정을 보면 조회된 값이 있을 때도 없을 때도 value.initalize로 값을 넣어주게 됩니다.
이떄, 최초 호출 시 value는 0이 담기게 됩니다. (initialValue -1)
update는 value를 변경하지 않고 db 값만 변경하게 됩니다.
return을 보면 value.increment()를 호출해 리턴하는데 이때 value++; 를 동작 하고나서 자신을 리턴해 1이 리턴됩니다.
pooled는 최초 호출 시 init value를 hiValue로 갖게되어 두번 째 호출이 발생했을 때 db에서 새로 값을 받아 hiValue를 변경하게 됩니다.
글이 좀 뒤죽 박죽으로 작성되어 있는데, 잘 정리해 놓은 블로그가 있어 가져 왔습니다.
위 블로그에 작성된 pooled optimizer following diagram을 가져와 봤습니다. 제가 위에서 장황하게 설명한것을 그림으로 깔끔하게 정리가 되어있습니다.
내용을 공부하면서 내 의도대로 key를 업데이트 하기위해서는 pooled-lo를 사용해야 한다는 것을 알게 되었습니다.
pooled-lo optimizer
이 방법은 pooled 와 다르게 db에서 읽은 값, limit 값, pk value 3개의 필드가 있으며 db에서 읽은 값과 limit값을 분리해 관리하고 있는점이 다릅니다.
1) lastSourceValue가 null 이거나 value>upperLimitValue 일 때 callback.getNextValue()를 호출
1-1) lastSourceValue = 1;
1-2) upperLimitValue = 1+5;
1-3) value =1;
return value ++
pooled-lo 적용 방법
1) properties를 통해 전역으로 설정
1-1) spring.jpa.properties.hibernate.id.optimizer.pooled.preferred=pooled-lo
1-2) spring.jpa.properties.hibernate.id.optimizer.pooled.prefer_lo=true
1-2 방법은 deprecated 되었습니다 1-1방법을 사용해야합니다.
/**
* When using pooled {@link org.hibernate.id.enhanced.Optimizer optimizers}, prefer interpreting the
* database value as the lower (lo) boundary. The default is to interpret it as the high boundary.
*
* @deprecated Use {@link #PREFERRED_POOLED_OPTIMIZER} instead
*/
@Deprecated
String PREFER_POOLED_VALUES_LO = "hibernate.id.optimizer.pooled.prefer_lo";
/**
* When a generator specified an increment-size and an optimizer was not explicitly specified, which of
* the "pooled" optimizers should be preferred? Can specify an optimizer short name or an Optimizer
* impl FQN.
*/
String PREFERRED_POOLED_OPTIMIZER = "hibernate.id.optimizer.pooled.preferred";
2) genericGenerator를 통해 개별 설정
@TableGenerator에 작성하던 값을 TableGenerator.class에 매핑되년 필드에 값 넣을 수 있도록 설정
@TableGenerator에는 optimizer를 변경할 수 없지만 아래와 같이 작성하면 OPT_PARAM을 통해 사용하고 싶은 optimizer를 적용할 수 있다.!
@Id
@GenericGenerator(name = "tbGenerator1", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = {
@org.hibernate.annotations.Parameter(name = org.hibernate.id.enhanced.TableGenerator.OPT_PARAM,value = "pooled-lo"),
@org.hibernate.annotations.Parameter(name = org.hibernate.id.enhanced.TableGenerator.TABLE_PARAM,value = "tb_increment_id"),
@org.hibernate.annotations.Parameter(name = org.hibernate.id.enhanced.TableGenerator.VALUE_COLUMN_PARAM,value = "val"),
@org.hibernate.annotations.Parameter(name = org.hibernate.id.enhanced.TableGenerator.INITIAL_PARAM, value = "1"),
@org.hibernate.annotations.Parameter(name = org.hibernate.id.enhanced.TableGenerator.INCREMENT_PARAM,value = "50")
})
@GeneratedValue(generator = "tbGenerator1")
public Integer getId() {
return id;
}
참고 : https://vladmihalcea.com/hibernate-hidden-gem-the-pooled-lo-optimizer/
https://lumme.dev/2022/04/03/hibernate-table-generator-optimizers.html
https://stackoverflow.com/questions/25204019/how-to-use-the-pooled-lo-optimizer-with-hibernate