I have a JPA 2 web application (Struts 2, Hibernate 4 as JPA implementation only).
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.
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:
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.
I know I can define callbacks methods, but JSR-000317 Persistence Specification for Eval 2.0 Eval discourages some specific operations from inside it:
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.
Summarizing, yes to JDBC (!) and EJB, no to EntityManager and other Entities.
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.
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
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.
I guess I need to put the @Column definition somewhere: how could JPA possibly know that the newField column (mapped through ImprovedNamingStrategy to new_field on the database) is retrieved through the value property of the FooSequence entity ?
Some pieces of the puzzle are missing.
EDIT
As asked in comments, this is the 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>
@JoinColumn(name="INCR_FIELD")to yourprivate FooSequence newField;, and add theFooSequenceclass to your persistence.xml (if needed). Afterwards, in order to get the value of NEW_FIELD usegetFooSequence().getId()@JoinColumn(name="new_field")toprivate FooSequence newField;, that was the only change I made). Please, post it as an answer with some more details, I'll assign the bounty as soon as it will work, no matter the ending period, and I'll upvote every helping answer immediately.new_field(to be sure it is not caused by that column), that's why I used a newINCR_FIELDcolumn.NEW_FIELDin your code, you should callgetFooSequence().getId()(as I mentioned in one of my comments above).