이것은 오래된 스레드이지만 솔루션을 공유하고 이에 대한 의견을 얻고 싶습니다. 일부 JUnit 테스트 케이스에서 로컬 데이터베이스로만이 솔루션을 테스트했음을 경고합니다. 따라서 이것은 지금까지 생산적인 기능이 아닙니다.
속성이없는 Sequence라는 사용자 지정 주석을 도입 하여이 문제를 해결했습니다. 증분 시퀀스에서 값을 할당해야하는 필드의 마커 일뿐입니다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
이 주석을 사용하여 엔티티를 표시했습니다.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
데이터베이스를 독립적으로 유지하기 위해 시퀀스 현재 값과 증분 크기를 보유하는 SequenceNumber라는 엔티티를 소개했습니다. className을 고유 키로 선택하여 각 엔티티 클래스가 자체 시퀀스를 얻습니다.
@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
@Id
@Column(name = "className", updatable = false)
private String className;
@Column(name = "nextValue")
private Integer nextValue = 1;
@Column(name = "incrementValue")
private Integer incrementValue = 10;
... some getters and setters ....
}
마지막 단계와 가장 어려운 단계는 시퀀스 번호 할당을 처리하는 PreInsertListener입니다. 스프링을 빈 컨테이너로 사용했습니다.
@Component
public class SequenceListener implements PreInsertEventListener
{
private static final long serialVersionUID = 7946581162328559098L;
private final static Logger log = Logger.getLogger(SequenceListener.class);
@Autowired
private SessionFactoryImplementor sessionFactoryImpl;
private final Map<String, CacheEntry> cache = new HashMap<>();
@PostConstruct
public void selfRegister()
{
// As you might expect, an EventListenerRegistry is the place with which event listeners are registered
// It is a service so we look it up using the service registry
final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
// add the listener to the end of the listener chain
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
}
@Override
public boolean onPreInsert(PreInsertEvent p_event)
{
updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());
return false;
}
private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
{
try
{
List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);
if (!fields.isEmpty())
{
if (log.isDebugEnabled())
{
log.debug("Intercepted custom sequence entity.");
}
for (Field field : fields)
{
Integer value = getSequenceNumber(p_entity.getClass().getName());
field.setAccessible(true);
field.set(p_entity, value);
setPropertyState(p_state, p_propertyNames, field.getName(), value);
if (log.isDebugEnabled())
{
LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
}
}
}
}
catch (Exception e)
{
log.error("Failed to set sequence property.", e);
}
}
private Integer getSequenceNumber(String p_className)
{
synchronized (cache)
{
CacheEntry current = cache.get(p_className);
// not in cache yet => load from database
if ((current == null) || current.isEmpty())
{
boolean insert = false;
StatelessSession session = sessionFactoryImpl.openStatelessSession();
session.beginTransaction();
SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);
// not in database yet => create new sequence
if (sequenceNumber == null)
{
sequenceNumber = new SequenceNumber();
sequenceNumber.setClassName(p_className);
insert = true;
}
current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
cache.put(p_className, current);
sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());
if (insert)
{
session.insert(sequenceNumber);
}
else
{
session.update(sequenceNumber);
}
session.getTransaction().commit();
session.close();
}
return current.next();
}
}
private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
{
for (int i = 0; i < propertyNames.length; i++)
{
if (propertyName.equals(propertyNames[i]))
{
propertyStates[i] = propertyState;
return;
}
}
}
private static class CacheEntry
{
private int current;
private final int limit;
public CacheEntry(final int p_limit, final int p_current)
{
current = p_current;
limit = p_limit;
}
public Integer next()
{
return current++;
}
public boolean isEmpty()
{
return current >= limit;
}
}
}
위 코드에서 알 수 있듯이 리스너는 엔티티 클래스 당 하나의 SequenceNumber 인스턴스를 사용했으며 SequenceNumber 엔티티의 incrementValue로 정의 된 두 개의 시퀀스 번호를 예약합니다. 시퀀스 번호가 부족하면 대상 클래스의 SequenceNumber 엔터티를로드하고 다음 호출에 대해 incrementalValue 값을 예약합니다. 이렇게하면 시퀀스 값이 필요할 때마다 데이터베이스를 쿼리 할 필요가 없습니다. 다음 시퀀스 번호 세트를 예약하기 위해 열려있는 StatelessSession에 유의하십시오. EntityPersister에서 ConcurrentModificationException이 발생하므로 대상 엔티티가 현재 지속되는 동일한 세션을 사용할 수 없습니다.
이것이 누군가를 돕기를 바랍니다.