Bu eski bir iş parçacığı olmasına rağmen benim çözüm paylaşmak ve umarım bu konuda bazı geribildirim almak istiyorum. Bu çözümü yalnızca bazı JUnit test çantasında yerel veritabanımla test ettiğim konusunda uyarıda bulunun. Yani bu şimdiye kadar üretken bir özellik değil.
Ben bu özelliği benim için hiçbir özellik ile Sequence adlı özel bir açıklama getirerek çözdü. Bu, artırılmış bir diziden bir değer atanması gereken alanlar için yalnızca bir işaretleyicidir.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Bu ek açıklamayı kullanarak varlıklarımı işaretledim.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Şeyler veritabanını bağımsız tutmak için dizi geçerli değeri ve artış boyutunu tutan SequenceNumber adlı bir varlık tanıttı. ClassName benzersiz anahtar olarak seçtim, böylece her varlık sınıfı kendi dizisini alacak.
@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 ....
}
Son adım ve en zoru, sıra numarası atamasını işleyen bir PreInsertListener'dır. Fasulye kabı olarak yay kullandığımı unutmayın.
@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;
}
}
}
Yukarıdaki koddan da görebileceğiniz gibi, dinleyici varlık sınıfı başına bir SequenceNumber örneği kullandı ve SequenceNumber varlığının incrementValue tarafından tanımlanan birkaç sıra numarası ayırır. Sıra numaraları biterse, hedef sınıf için SequenceNumber varlığını yükler ve sonraki çağrılar için incrementValue değerlerini saklar. Bu şekilde bir dizi değeri gerektiğinde veritabanını sorgulamak gerekmez. Sonraki sıra numarası kümesini ayırmak için açılan StatelessSession'a dikkat edin. EntityPersister'da bir ConcurrentModificationException özelliğine yol açacağından, hedef varlığın devam etmekte olduğu oturumu kullanamazsınız.
Umarım bu birine yardımcı olur.