JPA – 以编程方式通过序列递增数字字段

我有一个JPA 2 Web应用程序(Struts 2,Hibernate 4仅作为JPA实现)。

当前的要求是将(非id)数字顺序字段(仅为某些行填充)添加到现有实体。 根据特定条件插入新行时,我需要将新字段设置为its highest value + 1NULL

例如:

 ID NEW_FIELD DESCRIPTION -------------------------------- 1 1 bla bla 2 bla bla <--- unmatched: not needed here 3 bla bla <--- unmatched: not needed here 4 2 bla bla 5 3 bla bla 6 4 bla bla 7 bla bla <--- unmatched: not needed here 8 5 bla bla 9 bla bla <--- unmatched: not needed here 10 6 bla bla 

在旧的SQL中,它会是这样的:

 INSERT INTO myTable ( id, new_field, description ) VALUES ( myIdSequence.nextVal, (CASE myCondition WHEN true THEN myNewFieldSequence.nextVal ELSE NULL END), 'Lorem Ipsum and so on....' ) 

但我不知道如何用JPA 2来实现它。

我知道我可以定义回调方法,但是Eval 2.0 Eval的JSR-000317持久性规范不鼓励它内部的一些特定操作:

3.5实体监听器和回调方法
– 生命周期回调可以调用JNDI,JDBC,JMS和企业bean。
– 通常,可移植应用程序的生命周期方法不应调用EntityManager或Query操作,访问其他实体实例或修改同一持久性上下文中的关系 生命周期回调方法可以修改调用它的实体的非关系状态。

可以在本说明书的未来版本中标准化这些操作的语义。

总结一下,是JDBC(!)和EJB,不是EntityManager和其他实体。


编辑

我正在努力实现@anttix答案中描述的解决方案,但我遇到了一些问题,所以请纠正我错在哪里。

 MyTable ------------------------- ID number (PK) NEW_FIELD number DESCRIPTION text 

主要实体

 @Entity @Table(name="MyTable") public class MyEntity implements Serializable { @Id @SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id") private Long id; @OneToOne(cascade= CascadeType.PERSIST) private FooSequence newField; private String description /* Getters and Setters */ } 

子实体

 @Entity public class FooSequence { @Id @SequenceGenerator(name="seq_foo", sequenceName="seq_foo", allocationSize=1) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_foo") private Long value; /* Getter and Setter */ } 

DAO

 myEntity.setNewField(new FooSequence()); entityManager.persist(myEntity); 

例外

引起:javax.transaction.RollbackException:ARJUNA016053:无法提交事务。

[…]

引起:javax.persistence.PersistenceException:org.hibernate.exception.SQLGrammarException:错误:关系“new_field”不存在

[…]

引起:org.hibernate.exception.SQLGrammarException:错误:关系“new_field”不存在

[…]

引起:org.postgresql.util.PSQLException:错误:关系“new_field”不存在

我究竟做错了什么 ? 我对JPA 2很新,我从未使用过与物理表无关的实体……这种方法对我来说是全新的。

我想我需要把@Column定义放在某处:JPA怎么可能知道new_field列(通过ImprovedNamingStrategy映射到数据库上的new_field )是通过FooSequence实体的value属性检索的?

缺少一些拼图。


编辑

正如评论中所述,这是persistence.xml

    java:jboss/datasources/myDS           

一种可能的解决方案是使用具有其自己的表的单独实体,该表将仅封装新字段并且具有与该实体的OneToOne映射。 然后,只有在遇到需要附加序列号的对象时,才会实例化新实体。 然后,您可以使用任何生成器策略来填充它。

 @Entity public class FooSequence { @Id @GeneratedValue(...) private Long value; } @Entity public class Whatever { @OneToOne(...) private FooSequnce newColumn; } 

看到:

一个gradle 1.11可运行的SSCCE(使用Spring Boot):

的src /主/爪哇/ JpaMultikeyDemo.java

 import java.util.List; import javax.persistence.*; import lombok.Data; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; @Configuration @EnableTransactionManagement @EnableAutoConfiguration public class JpaMultikeyDemo { @Entity @Data public static class FooSequence { @Id @GeneratedValue private Long value; } @Entity @Data public static class FooEntity { @Id @GeneratedValue private Long id; @OneToOne private FooSequence sequence; } @PersistenceContext EntityManager em; @Transactional public void runInserts() { // Create ten objects, half with a sequence value for(int i = 0; i < 10; i++) { FooEntity e1 = new FooEntity(); if(i % 2 == 0) { FooSequence s1 = new FooSequence(); em.persist(s1); e1.setSequence(s1); } em.persist(e1); } } public void showAll() { String q = "SELECT e FROM JpaMultikeyDemo$FooEntity e"; for(FooEntity e: em.createQuery(q, FooEntity.class).getResultList()) System.out.println(e); } public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(JpaMultikeyDemo.class); context.getBean(JpaMultikeyDemo.class).runInserts(); context.getBean(JpaMultikeyDemo.class).showAll(); context.close(); } } 

的build.gradle

 apply plugin: 'java' defaultTasks 'execute' repositories { mavenCentral() maven { url "http://repo.spring.io/libs-milestone" } } dependencies { compile "org.springframework.boot:spring-boot-starter-data-jpa:1.0.0.RC5" compile "org.projectlombok:lombok:1.12.6" compile "com.h2database:h2:1.3.175" } task execute(type:JavaExec) { main = "JpaMultikeyDemo" classpath = sourceSets.main.runtimeClasspath } 

另见: http : //docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-configure-datasource

对于某些AOP来说,这似乎是一个很好的例子。 首先创建自定义字段注释@CustomSequenceGeneratedValue ,然后使用它注释实体上的字段:

 public class MyEntity { ... @CustomSequenceGeneratedValue private Long generatedValue; public void setGeneratedValue(long generatedValue) { } } 

然后创建一个方面来增加生成的值:

 @Aspect public class CustomSequenceGeneratedValueAspect { @PersistenceContext private EntityManager em; @Before("execution(* com.yourpackage.dao.SomeDao.*.*(..))") public void beforeSaving(JoinPoint jp) throws Throwable { Object[] args = jp.getArgs(); MethodSignature ms = (MethodSignature) jp.getSignature(); Method m = ms.getMethod(); Annotation[][] parameterAnnotations = m.getParameterAnnotations(); for (int i = 0; i < parameterAnnotations.length; i++) { Annotation[] annotations = parameterAnnotations[i]; for (Annotation annotation : annotations) { if (annotation.annotationType() == CustomSequenceGeneratedEntity.class) { ... find generated properties run query and call setter ... ... Query query = em.createNativeQuery("select MY_SEQUENCE.NEXTVAL from dual"); } } } } } 

然后使用扫描方面,并将其应用于此类型的任何Spring DAO保存实体。 该方面将以对用户透明的方式基于序列填充序列生成的值。

你提到过开放使用JDBC。 以下是如何使用实体回调和JdbcTemplate,该示例使用Postgres的语法来选择序列中的下一个值,只需更新它以使用正确的数据库语法。

将其添加到您的实体类:

 @javax.persistence.EntityListeners(com.example.MyEntityListener.class) 

这里是监听器实现( @Qualifierrequired = true是它工作所必需的):

 package com.example; import javax.persistence.PostPersist; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @Component public class MyEntityListener { private static JdbcTemplate jdbcTemplate; @Autowired(required = true) @Qualifier("jdbcTemplate") public void setJdbcTemplate(JdbcTemplate bean) { jdbcTemplate = bean; } @PostPersist @Transactional public void postPersis(MyEntity entity) { if(isUpdateNeeded(entity)) { entity.setMyField(jdbcTemplate.queryForObject("select nextval('not_hibernate_sequence')", Long.class)); } } private boolean isUpdateNeeded(MyEntity entity) { // TODO - implement logic to determine whether to do an update return false; } } 

我过去简单易懂的hacky解决方案如下:

 MyEntity myEntity = new MyEntity(); myEntity.setDescription("blabla"); em.persist(myEntity); em.flush(myEntity); myEntity.setNewField(getFooSequence()); 

事务处理的完整代码(“伪代码”,我直接在SO上编写,因此它可能有拼写错误)就像:

实体

 @Entity @Table(name="MyTable") public class MyEntity implements Serializable { @Id @SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id") private Long id; private Long newField; // the optional sequence private String description /* Getters and Setters */ } 

主EJB:

 @Stateless @TransactionManagement(TransactionManagementType.CONTAINER) // default public class MainEjb implements MainEjbLocalInterface { @Inject DaoEjbLocalInterface dao; // Create new session, no OSIV here @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Long insertMyEntity(boolean myCondition) throws Exception { try { MyEntity myEntity = dao.insertMyEntity(); // if this break, no FooSequence will be generated doOtherStuff(); // Do other non-database stuff that can break here. // If they break, no FooSequence will be generated, // and no myEntity will be persisted. if (myCondition) { myEntity.setNewField(dao.getFooSequence()); // This can't break (it would have break before). // But even if it breaks, no FooSequence will be generated, // and no myEntity will be persisted. } } catch (Exception e){ getContext().setRollbackOnly(); log.error(e.getMessage(),e); throw new MyException(e); } } } 

DAO EJB

 @Stateless @TransactionManagement(TransactionManagementType.CONTAINER) // default public class DaoEjb implements DaoEjbLocalInterface { @PersistenceContext( unitName="myPersistenceUnit") EntityManager em; // default, use caller (MainEJB) session @TransactionAttribute(TransactionAttributeType.REQUIRED) public MyEntity insertMyEntity() throws Exception{ MyEntity myEntity = new MyEntity(); myEntity.setDescription("blabla"); em.persist(myEntity); em.flush(); // here it will break in case of database errors, // eg. description value too long for the column. // Not yet committed, but already "tested". return myEntity; } // default, use caller (MainEJB) session @TransactionAttribute(TransactionAttributeType.REQUIRED) public Long getFooSequence() throws Exception { Query query = em.createNativeQuery("SELECT nextval('seq_foo')"); return ((BigInteger) query.getResultList().get(0)).longValue(); } } 

这将保证FooSequence代中不会有任何空白。

在我的用例中我唯一不关心的唯一缺点是FooSequence和@Id序列不同步,因此两个并发插入可能具有“反向”FooSequence值,例如,它们的到达顺序。

 ID NEW FIELD ------------- 1 2 2 1