7

I'm using Oracle database and I have sequence and trigger for generating and storing ID before insert.

CREATE SEQUENCE CASE_SEQ START WITH 1001 INCREMENT BY 1 NOMAXVALUE; 

CREATE OR REPLACE TRIGGER CASE_TR_SEQ
BEFORE INSERT ON CASE FOR EACH ROW
BEGIN
  SELECT CASE_SEQ.NEXTVAL INTO :NEW.CASE_ID FROM DUAL;
END;
/

Then I have simple entity with property:

@Id
@Column(name = "CASE_ID", insertable = false, updatable = false)
private Long caseId;

...when I try build project I'm getting:

Exception [EclipseLink-46] (Eclipse Persistence Services - 2.3.2.v20111125-r10461):
org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the
primary key field [CASE.CASE_ID].

When I remove either insertable or updatable keyword, then it works. I know that there is a lot of solutions how generate ID using JPA, also JPA can use (call) oracle sequence to set (generated) ID. But I try understand why is my solutions wrong. Why I can't use both keywords together with the @Id annotation? My thought is: I want to prohibit insert or update caseId by JPA.

1) What is the proper soulution? Should I use only @Id:

@Id
@Column(name = "CASE_ID")
private Long caseId;

or is better (safer) define insertable=false also:

@Id
@Column(name = "CASE_ID", insertable = false)
private Long caseId;

2) I understand that updatable=false for @Id does not have meaning (update Primary Key does not have meaning but it is possible by raw sql), but what does it mean (do you have some example when it is beneficial):

@Id
@Column(name = "CASE_ID", updatable = false)
private Long caseId;

EDIT 2012-04-13

I made some tests:

Entity

@Id
@Column(name = "CASE_ID")
private Long caseId;

JPA log

INSERT INTO CASE (CASE_ID, CASE_DATE, INFO) VALUES (?, ?, ?)
bind => [3 parameters bound]|#]

So this is not safe, because JPA tries store CASE_ID (which is then replaced by ID from Oracle sequence by trigger).

Entity

@Id
@Column(name = "CASE_ID", insertable = false)
private Long caseId;

Create method

public void createCase(final Case caseData) {
    caseData.setCaseId(-1001L);
    em.persist(caseData);
}

JPA log

INSERT INTO CASE (CASE_DATE, INFO) VALUES (?, ?)
bind => [2 parameters bound]|#]

It is good, because the CASE_ID is not part of insert command.

And Update of CASE_ID is not possible because annotation ID:

public void update() {
    Case update = em.find(Case.class, 1023L);
    update.setCaseId(1028L);
}

Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.3.2.v20111125-r10461):
org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [caseId] of class
[com.wordpress.kamiluv.jsfprototype.model.entity.Case] is mapped to a primary
key column in the database. Updates are not allowed.

So now the last version looks as the most safe, right?

sasynkamil
  • 859
  • 2
  • 12
  • 23
  • 1
    Take a look at http://stackoverflow.com/questions/3669883/hibernate-where-do-insertable-false-updatable-false-belong-in-composite-pr and http://stackoverflow.com/questions/3805584/please-explain-about-insertable-false-updatable-false and see if it helps you. –  Apr 12 '12 at 16:57
  • @tech_learner unfortunately not much – sasynkamil Apr 12 '12 at 17:30
  • 1
    I am not sure, if it helps but are you looking for immutable class? If then, take a look at this: http://www.mkyong.com/hibernate/hibernate-mutable-example-class-and-collection/ –  Apr 12 '12 at 18:01

1 Answers1

5

You are currently saying with your JPA annotations that you have an @Id column that cannot be inserted or updated in any way. You must be able to set the id before inserting or updating, but JPA does not know how to do so. You will need to use the @GeneratedValue annotation on the @Id to tell JPA what strategy to use (one of: TABLE,SEQUENCE,IDENTITY,AUTO) if you do not want the id to be set in your code.

bobz32
  • 684
  • 5
  • 3
  • Ok, but I don't want use any of '@GeneratedValue' possibilities, I'd like to use Oracle trigger to set ID in database columns. And removing '@Id' is not good solutions because there is some functionality connected wiht '@Id' in JPA e.g.: EntityManager find(Case.class, caseId). – sasynkamil Apr 12 '12 at 17:10
  • If you want to just use an Oracle trigger, try @GeneratorValue(strategy=GenerationType.AUTO). Not sure about Oracle, but I've had success with AUTO on a Sybase table that handled the id generation itself. – bobz32 Apr 12 '12 at 17:17
  • it works but I think it is not good solutions, bacuase ATUTO strategy is defined for different meaning... DOC: Specifying a strategy of AUTO allows EclipseLink to select the strategy to use. Typically, EclipseLink picks TABLE as the strategy, since it is the most portable strategy. However, when AUTO is specified, schema generation must be used at least once in order for the default table to be created in the database. – sasynkamil Apr 12 '12 at 17:26
  • 3
    The only other solution I could think of would require a modification to your trigger. If you made your trigger conditional (so if the id is not specified, THEN use and increment the SEQUENCE), you could then use the SEQUENCE generator type in JPA. This way, your code is using the SEQUENCE properly, and your trigger will handle any legacy SQL inserts at the same time. – bobz32 Apr 12 '12 at 17:43
  • Yes, good solution, but there might be a problem that some developer set caseId and then invoke method em.persist(). The new case will be stored in database with "non" sequnce ID. I will try make some tests which solution is the most safe. – sasynkamil Apr 13 '12 at 05:03
  • Back to the AUTO strategy. If the GeneratedValue is not used, the AUTO is default value. JPA implementation then pick an appropriate strategy for a particular database but only if you use DLL generation defined in persistence.xml: . So it works regardless if I use only Id annotation or GeneratedValue.AUTO also. But I'm still not sure, if I can use Id anotation with oracle Trigger & Sequence. All I know is, that Entity must have some Id anotation (some primary key). – sasynkamil Apr 13 '12 at 16:49
  • Postgres also uses a sequence to generate a value for the PK. However it uses a column default to apply the sequence instead of a trigger. In any case to get this to work with Postgres I successfully use: GenerationType.IDENTITY on the Id field. Since they both use sequences and some DB mechanism to apply the sequence, this might also work for your use case. – Bill Rosmus Jul 20 '12 at 15:15