Skip to content

Setting up an Entity Persistence

Gal Koren edited this page May 3, 2021 · 5 revisions

We shall implement an Ad entity starting with a single table with few fields.

Enable MySQL Batching

Add this &rewriteBatchedStatements=true to the connection string or else the JDBC driver will split our bulks to separate commands and the performance penalty for that is amazingly high.

Define the PLContext

You don’t need to repeat it for all entities but it is rather a one-time setup.
PLContext is a wrapper around DSLContext of JOOQ. This is where you tell JOOQ about your DB DataSource.

public DSLContext defineJooq() {
    DSLContext jooq = DSL.using(new DefaultConfiguration()
        .set(SQLDialect.MYSQL)
        .set(new ThreadLocalTransactionProvider(/* and your JDBC connection provider */)));

    return new PLContext.Builder(jooq).build();
}

Define a JOOQ Table

PL is based on JOOQ. If you are already familiar with JOOQ, this step should be a piece of cake. If not, let’s show you how to do it.
We shall have a table with 4 columns:

  • creative_id (the ID as a primary key)
  • status
  • display_url
  • headline
public class AdCreative extends AbstractDataTable<AdCreative> {

  public static final AdCreative TABLE = new AdCreative("ad_creatives");
  private AdCreative(String name) { super(name); }

  public final TableField<Record, Integer> creative_id = createPKField("creative_id", SQLDataType.INTEGER.identity(true));

  public final TableField<Record, String>  status      = createField("status", SQLDataType.VARCHAR.length(50));

  public final TableField<Record, String>  display_url = createField("display_url", SQLDataType.VARCHAR.length(255));

  public final TableField<Record, String>  headline    = createField("headline", SQLDataType.VARCHAR.length(255));
}
  • The creative_id field is defined as a PrimaryKey. At least one fields must be flagged as PK.
  • Note the identity(true) expression. It tells JOOQ that this column is auto-incrementing and we would like the generated IDs to be retrieved back to us so that we can use them.
  • This class doesn't create a table. We assume the table already exists. This step just defines a static definition of the table.
  • AbstractDataTable is our infra class extending the JOOQ TableImpl class. Don’t use JOOQ directly without it (it won’t work).

Define the EntityType

You may feel some deja-vu as you look at it as it looks pretty much like the previous code block.
Every JOOQ field is now wrapped by a PL field. It may look redundant, but this allows more advanced features such as combining multiple tables into a single entity.

public class AdEntity extends AbstractEntityType<AdEntity> {

    public static final AdEntity INSTANCE = new AdEntity();
    private AdEntity() { super("ad"); }
    @Override public DataTable getPrimaryTable() { return AdCreative.TABLE; }
    
    @Immutable
    public static final EntityField<AdEntity, Integer>    ID = INSTANCE.field(AdCreative.TABLE.creative_id);

    public static final EntityField<AdEntity, SyncStatus> SYNC_STATUS = INSTANCE.field(AdCreative.TABLE.status);

    public static final EntityField<AdEntity, String>     HEADLINE = INSTANCE.field(AdCreative.TABLE.headline);

    public static final EntityField<AdEntity, String>     URL = INSTANCE.field(AdCreative.TABLE.display_url);
}

Define Commands

For update

import static com.kenshoo.pl.entity.IdentifierType.uniqueKey;

public class UpdateAdCommand extends UpdateEntityCommand<AdEntity, Identifier<AdEntity>> {

    public UpdateAdCommand(int idValue) {
        super(AdEntity.INSTANCE, uniqueKey(AdEntity.Id).createIdentifier(idValue));
    }
}

For create

public class CreateAdCommand extends CreateEntityCommand<AdEntity> {

    public CreateAdCommand() {
        super(AdEntity.INSTANCE);
    }
}

The Persistence class

public class AdPersistence {

    private final PersistenceLayer persistenceLayer;
    private final PLContext settings;

    public AdPersistence(PLContext settings) {
        this.settings = settings;
        this.persistenceLayer = new PersistenceLayer(settings);
    }

    public CreateResult<AdEntity, Identifier<AdEntity>> create(Collection<CreateAdCommand> commands) {
        return persistenceLayer.create(commands, flowBuilder().build());
    }

    public <ID extends Identifier<AdEntity>>
    UpdateResult<AdEntity, ID> update(Collection<? extends UpdateEntityCommand<AdEntity, ID>> commands) {
        return persistenceLayer.update(commands, flowBuilder().build());
    }

    public <ID extends Identifier<AdEntity>>
    InsertOnDuplicateUpdateResult<KeywordEntity, ID> upsert(Collection<InsertOnDuplicateUpdateCommand<AdEntity, ID>> commands) {
        return persistenceLayer.upsert(commands, flowBuilder().build());
    }

    public <ID extends Identifier<AdEntity>>
    DeleteResult<KeywordEntity, ID> delete(Collection<DeleteEntityCommand<AdEntity, ID>> commands) {
        return persistenceLayer.delete(commands, flowBuilder().build());
    }


    private ChangeFlowConfig.Builder<AdEntity> flowBuilder() {
        //
        // This is where we later add business rules to our flow.
        //
        return ChangeFlowConfigBuilderFactory.newInstance(settings, AdEntity.INSTANCE);
    }
}

And the hard wiring is finally done!
We can start executing commands, define validators and have fun.