Java JPA - 以编程方式通过序列增加数字字段
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22486446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JPA - Increment a numeric field through a sequence programmatically
提问by Andrea Ligios
I have a JPA 2 web application (Struts 2, Hibernate 4 as JPA implementation only).
我有一个 JPA 2 Web 应用程序(Struts 2、Hibernate 4 仅作为 JPA 实现)。
The current requirement is to add a (non-id) numeric sequential field, filled for certain rows only, to an existing entity. When inserting a new row, based on a certain condition, I need to set the new field to its highest value + 1
or to NULL
.
当前的要求是向现有实体添加一个(非 ID)数字顺序字段,仅填充某些行。插入新行时,根据特定条件,我需要将新字段设置为its highest value + 1
或NULL
。
For example:
例如:
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
In the good old SQL, it would be something like:
在旧的 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....'
)
But I've no clue on how to achieve it with JPA 2.
但我不知道如何使用 JPA 2 实现它。
I know I can define callbacks methods, but JSR-000317 Persistence Specification for Eval 2.0 Evaldiscourages some specific operations from inside it:
我知道我可以定义回调方法,但JSR-000317 Eval 2.0 的持久性规范 Eval不鼓励在其中进行一些特定的操作:
3.5 Entity Listeners and Callback Methods
- Lifecycle callbacks can invoke JNDI, JDBC, JMS, and enterprise beans.
- In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.[43]A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.[43]The semantics of such operations may be standardized in a future release of this specification.
3.5 实体侦听器和回调方法
- 生命周期回调可以调用 JNDI、JDBC、JMS 和企业 bean。
- 一般而言,可移植应用程序的生命周期方法不应调用 EntityManager 或 Query 操作、访问其他实体实例或修改同一持久性上下文中的关系。[43]生命周期回调方法可以修改调用它的实体的非关系状态。[43]此类操作的语义可能会在本规范的未来版本中标准化。
Summarizing, yes to JDBC (!) and EJB, no to EntityManager and other Entities.
总而言之,对 JDBC (!) 和 EJB 是,对 EntityManager 和其他实体不是。
EDIT
编辑
I'm trying to achieve the solution described in the answer from @anttix, but I'm encoutering some problem, so please correct me where I'm wrong.
我正在尝试实现@anttix 的答案中描述的解决方案,但我遇到了一些问题,所以请纠正我的错误。
Table
桌子
MyTable
-------------------------
ID number (PK)
NEW_FIELD number
DESCRIPTION text
Main Entity
主要实体
@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 */
}
Sub entity
子实体
@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);
Exception
例外
Caused by: javax.transaction.RollbackException: ARJUNA016053: Could not commit transaction.
[...]
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.postgresql.util.PSQLException: ERROR: relation "new_field" does not exist
引起: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”不存在
What am I doing wrong ? I'm pretty new to JPA 2 and I've never used an entity not associated to a physical table... this approach is totally new to me.
我究竟做错了什么 ?我对 JPA 2 很陌生,我从未使用过与物理表无关的实体……这种方法对我来说是全新的。
I guess I need to put the @Column
definition somewhere: how could JPA possibly know that the newField
column (mapped through ImprovedNamingStrategyto new_field
on the database) is retrieved through the value
property of the FooSequence
entity ?
我想我需要把@Column
定义的地方:JPA怎么可能知道newField
列(通过映射ImprovedNamingStrategy到new_field
数据库上)通过获取value
的财产FooSequence
实体?
Some pieces of the puzzle are missing.
拼图的某些部分丢失了。
EDIT
编辑
As asked in comments, this is the 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>
采纳答案by anttix
One possible solution is to use a separate entity with its own table that will encapsulate only the new field and have an OneToOne mapping with that entity. You will then instantiate the new entity only when you encounter an object that needs the additional sequence number. You can then use any generator strategy to populate it.
一种可能的解决方案是使用单独的实体及其自己的表,该实体将仅封装新字段并与该实体具有 OneToOne 映射。只有当您遇到需要附加序列号的对象时,您才会实例化新实体。然后您可以使用任何生成器策略来填充它。
@Entity
public class FooSequence {
@Id
@GeneratedValue(...)
private Long value;
}
@Entity
public class Whatever {
@OneToOne(...)
private FooSequnce newColumn;
}
See:
看:
A gradle 1.11 runnable SSCCE (using Spring Boot):
一个 gradle 1.11 可运行的 SSCCE(使用 Spring Boot):
src/main/java/JpaMultikeyDemo.java
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
构建.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
回答by Angular University
This looks like it could be a good case for some AOP. First start by creating a custom field annotation @CustomSequenceGeneratedValue
, and then annotate the field on the entity with it:
这看起来对于某些 AOP 来说可能是一个很好的例子。首先创建一个自定义字段注释@CustomSequenceGeneratedValue
,然后用它注释实体上的字段:
public class MyEntity {
...
@CustomSequenceGeneratedValue
private Long generatedValue;
public void setGeneratedValue(long generatedValue) {
}
}
Then an aspect is created to increment generated values:
然后创建一个方面来增加生成的值:
@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");
}
}
}
}
}
Then the aspect is scanned with <aop:aspectj-autoproxy />
, and applied to any Spring DAO saving entities of this type. The aspect would populate the sequence generated values based on a sequence, in a transparent way for the user.
然后使用 扫描方面<aop:aspectj-autoproxy />
,并将其应用于任何这种类型的 Spring DAO 保存实体。该方面将以对用户透明的方式基于序列填充序列生成的值。
回答by SergeyB
You mentioned being open to using JDBC. Here is how you can you use Entity Callback with JdbcTemplate, the example uses Postgres's syntax for selecting next value in a sequence, just update it to use the right syntax for your DB.
您提到对使用 JDBC 持开放态度。以下是如何将实体回调与 JdbcTemplate 一起使用,该示例使用 Postgres 的语法来选择序列中的下一个值,只需更新它以使用适用于您的数据库的正确语法。
Add this to your entity class:
将此添加到您的实体类:
@javax.persistence.EntityListeners(com.example.MyEntityListener.class)
And here is listener implementation (@Qualifier
and required = true
are necessary for it to work):
这是侦听器实现(@Qualifier
并且required = 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;
}
}
回答by Andrea Ligios
The hacky solution I used to keep it simple is the following:
我用来保持简单的hacky解决方案如下:
MyEntity myEntity = new MyEntity();
myEntity.setDescription("blabla");
em.persist(myEntity);
em.flush(myEntity);
myEntity.setNewField(getFooSequence());
The complete code ("pseudo-code", I've written it directly on SO so it could have typos) with transaction handling would be like :
带有事务处理的完整代码(“伪代码”,我直接在 SO 上编写,因此可能有拼写错误)类似于:
Entity
实体
@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 */
}
Main EJB:
主要 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
道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();
}
}
This will guarantee there will be no gaps in the FooSequence generation.
这将保证在 FooSequence 生成中没有间隙。
The only drawback, that I don't care at all in my use case, is that FooSequence and the @Id sequence are not synchronized, so two concurrent inserts may have "inverted" FooSequence values, respecto to their order of arrive, eg.
在我的用例中我根本不关心的唯一缺点是 FooSequence 和 @Id 序列不同步,因此两个并发插入可能具有“反转” FooSequence 值,相对于它们的到达顺序,例如。
ID NEW FIELD
-------------
1 2
2 1