Mig*_*ing 121 java hibernate jpa sequence
对于某些不是标识符/不是复合标识符一部分的列,是否可以使用DB序列?
我正在使用hibernate作为jpa提供程序,并且我有一个表有一些生成值的列(使用序列),尽管它们不是标识符的一部分.
我想要的是使用序列为实体创建一个新值,其中序列的列不是主键的一部分:
@Entity
@Table(name = "MyTable")
public class MyEntity {
//...
@Id //... etc
public Long getId() {
return id;
}
//note NO @Id here! but this doesn't work...
@GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
@SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
@Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
public Long getMySequencedValue(){
return myVal;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,当我这样做:
em.persist(new MyEntity());
Run Code Online (Sandbox Code Playgroud)
将生成id,但该mySequenceVal属性也将由我的JPA提供程序生成.
只是为了说清楚:我希望Hibernate为mySequencedValue属性生成值.我知道Hibernate可以处理数据库生成的值,但是我不想使用触发器或除Hibernate之外的任何其他东西来为我的属性生成值.如果Hibernate可以为主键生成值,为什么它不能为简单属性生成?
Mor*_*erg 66
寻找这个问题的答案,我偶然发现了这个链接
似乎Hibernate/JPA无法为您的非id属性自动创建值.该@GeneratedValue注释只有配合使用@Id,以创建自动编号.
该@GeneratedValue注释只是告诉Hibernate数据库已生成该值本身.
该论坛中建议的解决方案(或解决方法)是使用生成的Id创建一个单独的实体,如下所示:
@Entity
public class GeneralSequenceNumber {
@Id
@GeneratedValue(...)
private Long number;
}
@Entity
public class MyEntity {
@Id ..
private Long id;
@OneToOne(...)
private GeneralSequnceNumber myVal;
}
Ser*_*kov 37
我发现这种方法很@Column(columnDefinition="serial")完美,但仅适用于PostgreSQL.对我来说这是完美的解决方案,因为第二个实体是"丑陋"的选择.
Rum*_*mal 18
我知道这是一个非常古老的问题,但它首先显示了结果,jpa自问题以来发生了很大的变化.
现在正确的方法是使用@Generated注释.您可以定义序列,将列中的默认值设置为该序列,然后将列映射为:
@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
Run Code Online (Sandbox Code Playgroud)
ala*_*irg 14
Hibernate绝对支持这一点.来自文档:
"生成的属性是由数据库生成其值的属性.通常,Hibernate应用程序需要刷新包含数据库生成值的任何属性的对象.但是,将属性标记为生成,可以让应用程序将此职责委托给Hibernate.实质上,每当Hibernate为已定义生成属性的实体发出SQL INSERT或UPDATE时,它会立即发出一个select来检索生成的值."
对于仅在insert上生成的属性,属性映射(.hbm.xml)将如下所示:
<property name="foo" generated="insert"/>
Run Code Online (Sandbox Code Playgroud)
对于在插入和更新时生成的属性,属性映射(.hbm.xml)将如下所示:
<property name="foo" generated="always"/>
Run Code Online (Sandbox Code Playgroud)
不幸的是,我不知道JPA,所以我不知道这个功能是否通过JPA公开(我怀疑可能没有)
或者,您应该能够从插入和更新中排除该属性,然后"手动"调用session.refresh(obj); 插入/更新后,从数据库加载生成的值.
这是在排除和更新语句中使用的排除属性的方法:
<property name="foo" update="false" insert="false"/>
Run Code Online (Sandbox Code Playgroud)
同样,我不知道JPA是否公开了这些Hibernate功能,但Hibernate确实支持它们.
看起来线程很旧,我只是想在这里添加我的解决方案(在 Spring 中使用 AspectJ - AOP)。
解决方案是创建自定义注释,@InjectSequenceValue如下所示。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
String sequencename();
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以注释实体中的任何字段,以便在运行时使用序列的下一个值注入基础字段(长整型/整数)值。
像这样注释。
//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
@InjectSequenceValue(sequencename = "serialnum_sequence")
Long serialNumber;
Run Code Online (Sandbox Code Playgroud)
到目前为止,我们已经标记了需要注入序列值的字段。因此,我们将了解如何将序列值注入到标记的字段中,这是通过在 AspectJ 中创建切入点来完成的。
save/persist我们将在方法执行之前触发注入。这是在下面的类中完成的。
@Aspect
@Configuration
public class AspectDefinition {
@Autowired
JdbcTemplate jdbcTemplate;
//@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
@Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
public void generateSequence(JoinPoint joinPoint){
Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
for (Object arg :aragumentList ) {
if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields
field.setAccessible(true);
try {
if (field.get(arg) == null){ // Setting the next value
String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
long nextval=getNextValue(sequenceName);
System.out.println("Next value :"+nextval); //TODO remove sout.
field.set(arg, nextval);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* This method fetches the next value from sequence
* @param sequence
* @return
*/
public long getNextValue(String sequence){
long sequenceNextVal=0L;
SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
while (sqlRowSet.next()){
sequenceNextVal=sqlRowSet.getLong("value");
}
return sequenceNextVal;
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以按如下方式注释任何实体。
@Entity
@Table(name = "T_USER")
public class UserEntity {
@Id
@SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
Long id;
String userName;
String password;
@InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
Long serialNumber;
String name;
}
Run Code Online (Sandbox Code Playgroud)
作为后续跟进,我是如何让它工作的:
@Override public Long getNextExternalId() {
BigDecimal seq =
(BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
return seq.longValue();
}
Run Code Online (Sandbox Code Playgroud)
虽然这是一个旧线程,但我想分享我的解决方案,并希望得到一些反馈。请注意,我仅在某些 JUnit 测试用例中使用本地数据库测试了此解决方案。因此,到目前为止,这还不是一个富有成效的功能。
我通过引入一个没有属性的名为 Sequence 的自定义注释为我解决了这个问题。它只是应该从递增序列中分配值的字段的标记。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Run Code Online (Sandbox Code Playgroud)
使用此注释我标记了我的实体。
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Run Code Online (Sandbox Code Playgroud)
为了保持数据库独立,我引入了一个名为 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 ....
}
Run Code Online (Sandbox Code Playgroud)
最后一步也是最困难的一步是处理序列号分配的 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
从上面的代码中可以看出,侦听器为每个实体类使用一个 SequenceNumber 实例,并保留了由 SequenceNumber 实体的 incrementValue 定义的一对序列号。如果序列号用完,它会为目标类加载 SequenceNumber 实体,并为下一次调用保留 incrementValue 值。这样我就不需要在每次需要序列值时查询数据库。请注意为保留下一组序列号而打开的 StatelessSession。您不能使用目标实体当前持久化的同一会话,因为这会导致 EntityPersister 中的 ConcurrentModificationException。
希望这可以帮助某人。
我使用@PrePersist注释通过Hibernate修复了UUID(或序列)的生成:
@PrePersist
public void initializeUUID() {
if (uuid == null) {
uuid = UUID.randomUUID().toString();
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
如果您使用的是 postgresql
而我使用的是 spring boot 1.5.6
@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
110031 次 |
| 最近记录: |