Хотя это старая ветка, я хочу поделиться своим решением и, надеюсь, получить некоторую обратную связь по этому вопросу. Имейте в виду, что я тестировал это решение только с моей локальной базой данных в некотором тестовом примере 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 для каждого класса сущности и резервирует пару порядковых номеров, определяемых incrementValue объекта SequenceNumber. Если у него заканчиваются порядковые номера, он загружает сущность SequenceNumber для целевого класса и резервирует значения incrementValue для следующих вызовов. Таким образом, мне не нужно запрашивать базу данных каждый раз, когда требуется значение последовательности.
Обратите внимание на StatelessSession, который открывается для резервирования следующего набора порядковых номеров. Вы не можете использовать тот же сеанс, целевая сущность которого в настоящее время сохраняется, так как это приведет к исключению ConcurrentModificationException в EntityPersister.
Надеюсь, это кому-нибудь поможет.