尽管这是一个老话题,但我还是想分享我的解决方案,并希望能得到一些反馈。请注意,我仅在某些JUnit测试用例中使用本地数据库测试了此解决方案。因此,到目前为止,这还不是生产性功能。
我通过引入一个没有属性的自定义注释(称为“序列”)为我解决了这个问题。它只是应该为递增序列中的值分配字段的标记。
@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。请注意,我使用spring作为bean容器。
@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实体的递增值定义的序列号。如果序列号用完了,它将为目标类加载SequenceNumber实体,并为下一次调用保留增量值。这样,我不需要每次需要序列值时都查询数据库。请注意,正在打开以保留下一组序列号的StatelessSession。您不能使用目标实体当前存在的同一会话,因为这会导致EntityPersister中的ConcurrentModificationException。
希望这对某人有帮助。