我有一个JPA 2 Web应用程序(Struts 2,Hibernate 4仅作为JPA实现)。
当前的要求是向现有实体添加仅填充某些行的(非id)数字顺序字段。根据特定条件插入新行时,我需要将新字段设置为its highest value + 1或NULL。
its highest value + 1
NULL
例如:
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的JSR-000317持久性规范Eval不鼓励从其内部进行一些特定的操作:
3.5实体侦听器和回调方法 -生命周期回调可以调用JNDI,JDBC,JMS和企业Bean。 -通常,可移植应用程序的生命周期方法 不应调用EntityManager或Query操作,访问其他实体实例或在同一持久性上下文内修改关系 。[43]生命周期回调方法可以修改在其上被调用的实体的非关系状态。 [43] 这种操作的语义可能会在本规范的未来版本中标准化。
3.5实体侦听器和回调方法 -生命周期回调可以调用JNDI,JDBC,JMS和企业Bean。 -通常,可移植应用程序的生命周期方法 不应调用EntityManager或Query操作,访问其他实体实例或在同一持久性上下文内修改关系 。[43]生命周期回调方法可以修改在其上被调用的实体的非关系状态。
[43] 这种操作的语义可能会在本规范的未来版本中标准化。
总结一下,对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 */ }
道
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”不存在
原因: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怎么可能知道newField列(通过映射ImprovedNamingStrategy到new_field数据库上)通过获取value的财产FooSequence实体?
@Column
newField
new_field
value
FooSequence
拼图的某些部分丢失了。
编辑
如评论中所问,这是persistence.xml:
persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="MyService" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/myDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" /> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/> <property name="hibernate.query.substitutions" value="true 'Y', false 'N'"/> <property name="hibernate.show_sql" value="true" /> <property name="format_sql" value="true" /> <property name="use_sql_comments" value="true" /> </properties> </persistence-unit> </persistence>
一种可能的解决方案是使用一个单独的实体及其自己的表,该表将仅封装新字段并与该实体具有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 / main / java / 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