1
votes

Is there anyway to insert a new record into a PostgreSQL database with Jooq straight from a POJO which extends a general identity class that has an id field without including the id in the insert statement?

An example POJO:

@Data
public abstract class PersistenceIdentity {
    @Id
    @Column(name = "id", unique = true, nullable = false, precision = 7, insertable = false)
    private Integer id;

    @Column(name = "created_date")
    private LocalDateTime createdDate;

    public abstract Table<R> getJooqTable();
}

@Data
public class SocialNetwork extends PersistenceIdentity {

    @Column(name = "name")
    private String name;

    @Override
    public Table<SocialNetworkRecord> getJooqTable() {
        return Tables.SOCIAL_NETWORK;
    }

}

The PostgreSQL schema is:

CREATE TABLE "social_network" (
    id              SERIAL NOT NULL PRIMARY KEY,
    created_date    TIMESTAMP DEFAULT now(),
    name            TEXT NOT NULL
);

My code to persist the POJO:

public <T extends PersistenceIdentity> T insertRecord(T record) {
    Record newRecord = db.newRecord(record.getJooqTable(), record);
    if (newRecord instanceof UpdatableRecord) {
        ((UpdatableRecord) newRecord).store();
    }

    return newRecord.into(record);
}

I realize I'm probably doing what Jooq really wasn't meant for (i.e. using generic types), however that (appears) to work just fine.

The problem is, Jooq includes the id in the insert statement and I then, of course, get a null value constraint. I don't want it inserted when it's a new record, however I do want it included when it returns the record (after inserting), when updating and also in select statements.

I can't simply exclude the id because I need it later on to easily get around some of the @OneToMany / @ManyToOne limitations.

And I would rather not have to insert the specific values for each POJO (that's why we annotated with @Column).

Does Jooq not honor the @Id or the insertable = false parameter in @Column?

Can anyone shed some light on this?

EDIT 1 Per request, below is the relevant snippet from the jOOQ generated table object. I'm not sure if this is correct or not for what I'm trying to do (i.e. allow the database to generate the ID), but I would think nextval('social_network_id_seq'::regclass) would accomplish that.

@Generated(
    value = {
        "http://www.jooq.org",
        "jOOQ version:3.9.1"
    },
    comments = "This class is generated by jOOQ"
)
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class SocialNetwork extends TableImpl<SocialNetworkRecord> {

    /**
     * The column <code>public.social_network.id</code>.
     */
    public final TableField<SocialNetworkRecord, Integer> ID = createField("id", org.jooq.impl.SQLDataType.INTEGER.defaultValue(org.jooq.impl.DSL.field("nextval('social_network_id_seq'::regclass)", org.jooq.impl.SQLDataType.INTEGER)), this, "");

}

Also, we use the mvn jooq-codegen:generate -Djooq.generator.name=org.jooq.util.XMLGenerator to generate the XML schema and then generate the the jOOQ table objects from that XML config. The thinking is we can push the XML config to github and all builds can simply regenerate the table objects from that.

Here is the XML:

<column>
    <table_catalog></table_catalog>
    <table_schema>public</table_schema>
    <table_name>social_network</table_name>
    <column_name>id</column_name>
    <data_type>integer</data_type>
    <character_maximum_length>0</character_maximum_length>
    <numeric_precision>32</numeric_precision>
    <numeric_scale>0</numeric_scale>
    <ordinal_position>1</ordinal_position>
    <column_default>nextval('social_network_id_seq'::regclass)</column_default>
</column>
<table_constraint>
    <constraint_catalog></constraint_catalog>
    <constraint_schema>public</constraint_schema>
    <constraint_name>social_network_pkey</constraint_name>
    <constraint_type>PRIMARY KEY</constraint_type>
    <table_catalog></table_catalog>
    <table_schema>public</table_schema>
    <table_name>social_network</table_name>
</table_constraint>
<table_constraint>
    <constraint_catalog></constraint_catalog>
    <constraint_schema>public</constraint_schema>
    <constraint_name>2200_17431_1_not_null</constraint_name>
    <constraint_type>CHECK</constraint_type>
    <table_catalog></table_catalog>
    <table_schema>public</table_schema>
    <table_name>social_network</table_name>
</table_constraint>
<table_constraint>
    <constraint_catalog></constraint_catalog>
    <constraint_schema>public</constraint_schema>
    <constraint_name>2200_17431_3_not_null</constraint_name>
    <constraint_type>CHECK</constraint_type>
    <table_catalog></table_catalog>
    <table_schema>public</table_schema>
    <table_name>social_network</table_name>
</table_constraint>
<key_column_usage>
    <column_name>id</column_name>
    <constraint_catalog></constraint_catalog>
    <constraint_schema>public</constraint_schema>
    <constraint_name>social_network_pkey</constraint_name>
    <ordinal_position>0</ordinal_position>
    <table_catalog></table_catalog>
    <table_schema>public</table_schema>
    <table_name>social_network</table_name>
</key_column_usage>

EDIT 2 My SocialNetwork jOOQ-generated table object does not have a getIdentity() method, however it does have a getPrimaryKey() method and if it helps, my SocialNetworkRecord class has two Constructors:

public SocialNetworkRecord() {
    super(SocialNetwork.SOCIAL_NETWORK);
}

/**
 * Create a detached, initialised SocialNetworkRecord
 */
public SocialNetworkRecord(Integer id, Timestamp createdDate, String name) {
    super(SocialNetwork.SOCIAL_NETWORK);

    set(0, id);
    set(1, createdDate);
    set(2, name);
}
1

1 Answers

1
votes

The way jOOQ works, there are two elements worth explaining:

Step 1: Record.from(Object):

Record newRecord = db.newRecord(record.getJooqTable(), record);

This call is convenience for this:

Record newRecord = db.newRecord(record.getJooqTable());
newRecord.from(record);

And the Record.from(Object) will copy all values from the record to the newRecord by using Record.set(Field, Object), which again sets the record's internal Record.changed(Field) flag.

Step 2: UpdatableRecord.store()

Your call to:

((UpdatableRecord) newRecord).store();

Will take all changed() fields into consideration for the relevant INSERT or UPDATE statement that is executed. The rationale here is that people sometimes want to set the primary key value explicitly, and not let an identity generate the value for them. Even if an identity is present on the primary key, it may sometimes be desireable to override its value. SQL standard databases (e.g. Oracle 12c) thus support two ways of specifying an identity:

-- This can be overridden
GENERATED BY DEFAULT AS IDENTITY

-- This can never be overridden
GENERATED ALWAYS AS IDENTITY

(MySQL's AUTO_INCREMENT or PostgreSQL's SERIAL type work the same way)

jOOQ assumes GENERATED BY DEFAULT AS IDENTITY here. The only exception to the above behaviour is when the identity column is NOT NULL and the Record value for the identity is null and jOOQ's meta model is aware of both:

- `NOT NULL` constraint
- `GENERATED BY DEFAULT AS IDENTITY`

Then, jOOQ will omit considering the identity value for insertion / update.

Bug in 3.9.2 and less:

Note that up until jOOQ version 3.9.2, there was a bug / missing feature in the XMLGenerator that produces the XML file you're importing: https://github.com/jOOQ/jOOQ/issues/6141. This bug resulted in no identity information being generated.

Workaround 1: If you cannot influence the jOOQ meta model

If, for some reason, you cannot get the jOOQ meta model to reflect your NOT NULL constraint and your DEFAULT clause, you could work around this limitation by resetting the value of the identity right after your Record.from(Object) call using Record.reset(Field):

Record newRecord = db.newRecord(record.getJooqTable(), record);
newRecord.reset(identityColumn);
((UpdatableRecord) newRecord).store();

Workaround 2: Generate a synthetic identity

The code generator has a feature to generate synthetic identities. For instance, if all your identity columns are called ID, you could write this:

<!-- fully qualified -->
<syntheticIdentities>.*?\.ID</syntheticIdentities>

Or this:

<!-- unqualified -->
<syntheticIdentities>ID</syntheticIdentities>