简体   繁体   中英

How to obtain Oracle generated value from a sequence in Hibernate + JPA with @ID and @GeneratedValue

I have the following Oracle table definition.

CREATE TABLE "SIAS"."OPERATION_REG"
  (
    "ID"               NUMBER CONSTRAINT "CT_OPERATION_REG_ID" NOT NULL ENABLE,
    "OPERATION_NAME"   VARCHAR2(30 BYTE),
    "APPLICATION_NAME" VARCHAR2(30 BYTE),
    "EXECUTION_DATE" DATE,
    "EXECUTION_USER" VARCHAR2(80 BYTE),
    "RESULT"         VARCHAR2(20 BYTE),
    CONSTRAINT "PK_OPERATION_REG_ID" PRIMARY KEY ("ID") USING INDEX PCTFREE 10 
     INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS NOLOGGING STORAGE(INITIAL 65536 NEXT 
     1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST 
     GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) 
     TABLESPACE "SIAS_DAT" ENABLE
  )
  SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 
  NOCOMPRESS LOGGING STORAGE
  (
    INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 
    0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT 
    CELL_FLASH_CACHE DEFAULT
  )
  TABLESPACE "SIAS_DAT" ;
CREATE UNIQUE INDEX "SIAS"."IDX_OPERATION_REG_ID" ON "SIAS"."OPERATION_REG"
  (
    "ID"
  )
  PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS NOLOGGING STORAGE
  (
    INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 
    FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT 
    CELL_FLASH_CACHE DEFAULT
  )
  TABLESPACE "SIAS_DAT" ;
CREATE OR REPLACE TRIGGER "SIAS"."BI_OPERATION_REG" BEFORE
  INSERT ON OPERATION_REG REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW BEGIN 
    :NEW.ID := SEQ_OPERATION_REG.NEXTVAL;
EXCEPTION
WHEN OTHERS THEN
  RAISE_APPLICATION_ERROR
  (
    -20255, 'ERROR EN TRIGGER BI_OPERATION_REG'
  )
  ;
END;
/
ALTER TRIGGER "SIAS"."BI_OPERATION_REG" ENABLE;

I have this trigger enabled to auto generate the value of the ID column when a new row is being created.

create or replace
TRIGGER BI_OPERATION_REG BEFORE INSERT
   ON OPERATION_REG
   REFERENCING NEW AS NEW OLD AS OLD
   FOR EACH ROW
BEGIN
   :NEW.ID           := SEQ_OPERATION_REG.NEXTVAL;
EXCEPTION
   WHEN OTHERS
   THEN
      RAISE_APPLICATION_ERROR (-20255, 'ERROR EN TRIGGER BI_OPERATION_REG');
END;

This is the sequence definition to generate the values for ID

CREATE SEQUENCE "SIAS"."SEQ_OPERATION_REG" MINVALUE 1 MAXVALUE 
999999999999999999999999999 INCREMENT BY 1 START WITH 37 NOCACHE NOORDER NOCYCLE ;

I have no control over the database, because the DBA team is out of my scope, so I have to deal with those definitions. I have created a JPA Entity that maps OPERATION_REG table. This is the ID property method mapping for column ID.

@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "G1")
@SequenceGenerator(name = "G1", sequenceName = "SEQ_OPERATION_REG")
@Column(name = "ID")
public int getId() {
    return id;
}

This is the full code of my entity mapping

    import org.hibernate.annotations.GenericGenerator;

    import javax.persistence.*;
    import java.sql.Timestamp;
    import java.util.Collection;

    @Entity
    @Table(name = "OPERATION_REG")
    public class OperationRegEntity extends BaseEntity {
        private int id;
        private String operationName;
        private String applicationName;
        private Timestamp executionDate;
        private String executionUser;
        private String result;
        private Collection<TokenRegEntity> tokenRegsById;
        private Collection<TraceRegEntity> traceRegsById;

        @Id
        @GeneratedValue(generator="select-generator")
        @GenericGenerator(name="select-generator", strategy="select", parameters = @org.hibernate.annotations.Parameter(name="key", value="ID"))
    //    @GeneratedValue(strategy = GenerationType.AUTO, generator = "G1")
    //    @SequenceGenerator(name = "G1", sequenceName = "SEQ_OPERATION_REG")
        @Column(name = "ID")
        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        @Basic
        @Column(name = "OPERATION_NAME")
        public String getOperationName() {
            return operationName;
        }

        public void setOperationName(String operationName) {
            this.operationName = operationName;
        }

        @Basic
        @Column(name = "APPLICATION_NAME")
        public String getApplicationName() {
            return applicationName;
        }

        public void setApplicationName(String applicationName) {
            this.applicationName = applicationName;
        }

        @Basic
        @Column(name = "EXECUTION_DATE")
        public Timestamp getExecutionDate() {
            return executionDate;
        }

        public void setExecutionDate(Timestamp executionDate) {
            this.executionDate = executionDate;
        }

        @Basic
        @Column(name = "EXECUTION_USER")
        public String getExecutionUser() {
            return executionUser;
        }

        public void setExecutionUser(String executionUser) {
            this.executionUser = executionUser;
        }

        @Basic
        @Column(name = "RESULT")
        public String getResult() {
            return result;
        }

        public void setResult(String result) {
            this.result = result;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            OperationRegEntity that = (OperationRegEntity) o;

            if (id != that.id) return false;
            if (applicationName != null ? !applicationName.equals(that.applicationName) : that.applicationName != null)
                return false;
            if (executionDate != null ? !executionDate.equals(that.executionDate) : that.executionDate != null)
                return false;
            if (executionUser != null ? !executionUser.equals(that.executionUser) : that.executionUser != null)
                return false;
            if (operationName != null ? !operationName.equals(that.operationName) : that.operationName != null)
                return false;
            if (result != null ? !result.equals(that.result) : that.result != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result1 = id;
            result1 = 31 * result1 + (operationName != null ? operationName.hashCode() : 0);
            result1 = 31 * result1 + (applicationName != null ? applicationName.hashCode() : 0);
            result1 = 31 * result1 + (executionDate != null ? executionDate.hashCode() : 0);
            result1 = 31 * result1 + (executionUser != null ? executionUser.hashCode() : 0);
            result1 = 31 * result1 + (result != null ? result.hashCode() : 0);
            return result1;
        }

        @OneToMany(mappedBy = "operationRegByOperationRegId")
        public Collection<TokenRegEntity> getTokenRegsById() {
            return tokenRegsById;
        }

        public void setTokenRegsById(Collection<TokenRegEntity> tokenRegsById) {
            this.tokenRegsById = tokenRegsById;
        }

        @OneToMany(mappedBy = "operationRegByOperationRegId")
        public Collection<TraceRegEntity> getTraceRegsById() {
            return traceRegsById;
        }

        public void setTraceRegsById(Collection<TraceRegEntity> traceRegsById) {
            this.traceRegsById = traceRegsById;
        }
    }

I have a problem, because, when I create a new object and persist it on the database, I follow this strategy

@Autowired
OperationRegService operationregservice;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public OperationRegEntity createOperationReg(GenericRequestParameters parameters) {
    OperationRegEntity oper = new OperationRegEntity();
    oper.setApplicationName(parameters.getApplication());
    oper.setExecutionUser(parameters.getApplicationUser());
    oper.setOperationName(parameters.getSIASOperationName());
    oper.setExecutionDate(new Timestamp(Calendar.getInstance().getTime().getTime()));
    oper.setResult("INITIATED");
    operationregservice.persist(oper);
    return oper;
}

When I analyse the information of oper.getID() , the value is different from the actual value created in the database, in particular, always 1 point below. For example, the java entity has ID value of 34 and the table row entity has an ID value of 35 , as if the sequence is getting called twice. Any ideas?

You shouldn't use a @SequenceGenerator , because that's used when you want Hibernate to call the sequence upon persisting an entity.

In your use case the database does the call so you need to use the select identifier generator strategy :

@Id
@GeneratedValue(generator="select-generator")
@GenericGenerator(name="select-generator", 
     strategy="select", 
     parameters = @org.hibernate.annotations.Parameter(name="key", value="ID")
)
@Column(name = "ID")
public int getId() {
    return id;
}

Ok, I figure out the problem, and it was in the way the trigger generated the sequence. The key was to generate the sequence if no ID value was set already. That way, Hibernate will call the sequence, set the ID value, and the trigger will check if the value was set, if so, it will not call the sequence. If no value was set, then the trigger calls the sequence and sets the value

This is the valid trigger

create or replace
TRIGGER BI_OPERATION_REG BEFORE INSERT 
    ON OPERATION_REG 
    REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW 
BEGIN
    IF :NEW.ID IS NULL THEN SELECT SEQ_OPERATION_REG.NEXTVAL INTO :NEW.ID FROM dual; END IF;
EXCEPTION
   WHEN OTHERS
   THEN
      RAISE_APPLICATION_ERROR (-20255, 'ERROR EN TRIGGER BI_OPERATION_REG');
END;            

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM