Meskipun ini adalah utas lama, saya ingin membagikan solusi saya dan semoga mendapatkan umpan balik tentang ini. Berhati-hatilah karena saya hanya menguji solusi ini dengan database lokal saya di beberapa kasus pengujian JUnit. Jadi ini bukan fitur produktif sejauh ini.
Saya memecahkan masalah itu untuk saya dengan memperkenalkan anotasi khusus yang disebut Urutan tanpa properti. Itu hanya penanda untuk bidang yang harus diberi nilai dari urutan yang bertambah.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Menggunakan anotasi ini saya menandai entitas saya.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Untuk menjaga agar database tetap independen, saya memperkenalkan entitas yang disebut SequenceNumber yang memegang urutan nilai saat ini dan ukuran kenaikan. Saya memilih className sebagai kunci unik sehingga setiap kelas entitas akan mendapatkan urutannya sendiri.
@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 ....
}
Langkah terakhir dan yang paling sulit adalah PreInsertListener yang menangani penetapan nomor urut. Perhatikan bahwa saya menggunakan pegas sebagai wadah kacang.
@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()
{
final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
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);
if ((current == null) || current.isEmpty())
{
boolean insert = false;
StatelessSession session = sessionFactoryImpl.openStatelessSession();
session.beginTransaction();
SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);
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;
}
}
}
Seperti yang Anda lihat dari kode di atas, pendengar menggunakan satu instance SequenceNumber per kelas entitas dan menyimpan beberapa nomor urut yang ditentukan oleh incrementValue dari entitas SequenceNumber. Jika kehabisan nomor urut, ia memuat entitas SequenceNumber untuk kelas target dan mencadangkan nilai incrementValue untuk panggilan berikutnya. Dengan cara ini saya tidak perlu meminta database setiap kali nilai urutan diperlukan. Perhatikan StatelessSession yang sedang dibuka untuk memesan rangkaian nomor urut berikutnya. Anda tidak dapat menggunakan sesi yang sama dengan entitas target yang saat ini dipertahankan karena ini akan mengarah ke ConcurrentModificationException di EntityPersister.
Semoga ini bisa membantu seseorang.