Mặc dù đây là một chủ đề cũ tôi muốn chia sẻ giải pháp của mình và hy vọng nhận được một số phản hồi về điều này. Được cảnh báo rằng tôi chỉ thử nghiệm giải pháp này với cơ sở dữ liệu cục bộ của mình trong một số testcase JUnit. Vì vậy, đây không phải là một tính năng sản xuất cho đến nay.
Tôi đã giải quyết vấn đề đó cho tôi bằng cách giới thiệu một chú thích tùy chỉnh có tên Sequence không có thuộc tính. Nó chỉ là một điểm đánh dấu cho các trường nên được gán một giá trị từ một chuỗi tăng dần.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Sử dụng chú thích này tôi đã đánh dấu các thực thể của tôi.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Để giữ cho cơ sở dữ liệu độc lập, tôi đã giới thiệu một thực thể có tên SequenceNumber chứa giá trị hiện tại của chuỗi và kích thước gia tăng. Tôi đã chọn className làm khóa duy nhất để mỗi lớp thực thể sẽ có chuỗi riêng.
@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 ....
}
Bước cuối cùng và khó khăn nhất là PreInsertListener xử lý việc gán số thứ tự. Lưu ý rằng tôi đã sử dụng mùa xuân như container đậu.
@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;
}
}
}
Như bạn có thể thấy từ đoạn mã trên, người nghe đã sử dụng một thể hiện SequenceNumber cho mỗi lớp thực thể và bảo lưu một vài số thứ tự được xác định bởi gia sốValue của thực thể SequenceNumber. Nếu nó hết số thứ tự, nó sẽ tải thực thể SequenceNumber cho lớp đích và dự trữ các giá trị gia tăng giá trị cho các cuộc gọi tiếp theo. Bằng cách này, tôi không cần truy vấn cơ sở dữ liệu mỗi khi cần một giá trị chuỗi. Lưu ý StatlessSession đang được mở để đặt bộ số thứ tự tiếp theo. Bạn không thể sử dụng cùng một phiên mà thực thể đích hiện đang tồn tại vì điều này sẽ dẫn đến một concExModificationException trong EntityPersister.
Hy vọng điều này sẽ giúp được ai đó.