8. Adding Paging Extensions to DAO

Posted by Adthegreat on Wed, 04 Mar 2020 02:30:17 +0100

Why extend DAO?In the previous section, I mentioned that the DAO generated by jOOQ has some basic CRID methods, as well as some query methods for each field.However, these methods are relatively simple and based on a single field to query, which is not enough to face complex business scenarios.

For example, paging queries, multi-conditional queries, and so on, which are commonly used in our business, are not encapsulated in DAO and can only be completed using the API of DSLContext.In fact, these methods are more common, we can use some encapsulation to make these operations easier.

How to proceed

The DAO code generated by jOOQ shows that all DAOs inherit DAOImpl Abstract classes, so we only need to create a class to inherit DAOImpl and let all DAO classes inherit the classes we created, then we can extend some public methods within the classes we created to use for each DAO

Suppose we create a class named AbstractExtendDAOImpl, with S1UserDao as an example, with the following inheritance relationships:

  • S1UserDao [extends] -> AbstractExtendDAOImpl [extends] -> DAOImpl [implement] -> DAO

However, the code generated by jOOQ is generated in full every time according to the configuration, it will delete the original target path and rebuild, so it is not feasible to modify the generated code directly after the DAO is generated.Then we can only modify the final build target code during the build phase

The jOOQ generator is configured with specific build logic that can be configured with configuration -> generator -> name, using org.jooq.codegen.JavaGenerator by default

<generator>
    <!-- ... -->
    <name>org.jooq.codegen.JavaGenerator</name>
    <!-- ... -->
</generator>

 

Our goal is to create a class that inherits JavaGenerator and override the generation of some DAO classes to achieve the desired effect. JavaGenerator classes can override the following methods.We're mainly going to extend DAO, so the way we're going to override this is generateDao

generateArray(SchemaDefinition, ArrayDefinition)           // Generates an Oracle array class
generateArrayClassFooter(ArrayDefinition, JavaWriter)      // Callback for an Oracle array class footer
generateArrayClassJavadoc(ArrayDefinition, JavaWriter)     // Callback for an Oracle array class Javadoc

generateDao(TableDefinition)                               // Generates a DAO class
generateDaoClassFooter(TableDefinition, JavaWriter)        // Callback for a DAO class footer
generateDaoClassJavadoc(TableDefinition, JavaWriter)       // Callback for a DAO class Javadoc

generateEnum(EnumDefinition)                               // Generates an enum
generateEnumClassFooter(EnumDefinition, JavaWriter)        // Callback for an enum footer
generateEnumClassJavadoc(EnumDefinition, JavaWriter)       // Callback for an enum Javadoc

generateInterface(TableDefinition)                         // Generates an interface
generateInterfaceClassFooter(TableDefinition, JavaWriter)  // Callback for an interface footer
generateInterfaceClassJavadoc(TableDefinition, JavaWriter) // Callback for an interface Javadoc

generatePackage(SchemaDefinition, PackageDefinition)       // Generates an Oracle package class
generatePackageClassFooter(PackageDefinition, JavaWriter)  // Callback for an Oracle package class footer
generatePackageClassJavadoc(PackageDefinition, JavaWriter) // Callback for an Oracle package class Javadoc

generatePojo(TableDefinition)                              // Generates a POJO class
generatePojoClassFooter(TableDefinition, JavaWriter)       // Callback for a POJO class footer
generatePojoClassJavadoc(TableDefinition, JavaWriter)      // Callback for a POJO class Javadoc

generateRecord(TableDefinition)                            // Generates a Record class
generateRecordClassFooter(TableDefinition, JavaWriter)     // Callback for a Record class footer
generateRecordClassJavadoc(TableDefinition, JavaWriter)    // Callback for a Record class Javadoc

generateRoutine(SchemaDefinition, RoutineDefinition)       // Generates a Routine class
generateRoutineClassFooter(RoutineDefinition, JavaWriter)  // Callback for a Routine class footer
generateRoutineClassJavadoc(RoutineDefinition, JavaWriter) // Callback for a Routine class Javadoc

generateSchema(SchemaDefinition)                           // Generates a Schema class
generateSchemaClassFooter(SchemaDefinition, JavaWriter)    // Callback for a Schema class footer
generateSchemaClassJavadoc(SchemaDefinition, JavaWriter)   // Callback for a Schema class Javadoc

generateTable(SchemaDefinition, TableDefinition)           // Generates a Table class
generateTableClassFooter(TableDefinition, JavaWriter)      // Callback for a Table class footer
generateTableClassJavadoc(TableDefinition, JavaWriter)     // Callback for a Table class Javadoc

generateUDT(SchemaDefinition, UDTDefinition)               // Generates a UDT class
generateUDTClassFooter(UDTDefinition, JavaWriter)          // Callback for a UDT class footer
generateUDTClassJavadoc(UDTDefinition, JavaWriter)         // Callback for a UDT class Javadoc

generateUDTRecord(UDTDefinition)                           // Generates a UDT Record class
generateUDTRecordClassFooter(UDTDefinition, JavaWriter)    // Callback for a UDT Record class footer
generateUDTRecordClassJavadoc(UDTDefinition, JavaWriter)   // Callback for a UDT Record class Javadoc

 

Custom Code Block

First, let's define the AbstractExtendDAOImpl we mentioned earlier

public abstract class AbstractExtendDAOImpl<R extends UpdatableRecord<R>, P, T> extends DAOImpl<R, P, T>
        implements ExtendDao<R, P, T> {
    // ...
}

With S1UserDao as an example, our ultimate goal is to directly inherit the AbstractExtendDAOImpl class when this class is generated

  • Default Build

    import org.jooq.impl.DAOImpl;
    public class S1UserDao extends DAOImpl<S1UserRecord, S1UserPojo, Integer> {
        // ...
    }
  • Custom Build

    import com.diamondfsd.jooq.learn.extend.AbstractExtendDAOImpl;
    public class S1UserDao extends AbstractExtendDAOImpl<S1UserRecord, S1UserPojo, Integer> {
        // ...
    }

The small change we need to make is to replace its inherited parent, DAOImpl -> AbstractExtendDAOImpl

Override Generator

By inheriting JavaGenerator, you can override the generation logic of the specified code block, so let's create a CustomJavaGenerator class to inherit JavaGenerator and override generateDao to do what we want

Let's take a look at the official JavaGenerator.generateDAO source code, which has two overloads: one is the control process responsible for file creation, the other is the filling content, and the other is responsible for file content construction and output

protected void generateDao(TableDefinition table) {
    JavaWriter out = newJavaWriter(getFile(table, Mode.DAO));
    log.info("Generating DAO", out.file().getName());
    generateDao(table, out);
    closeJavaWriter(out);
}

protected void generateDao(TableDefinition table, JavaWriter out) {
    // ...too much code to show
}

 

We have two options to achieve what we want

  • Scenario one, copy both methods completely into the CustomJavaGenerator class, then modify the specified keyword string to achieve the desired effect.The advantage is simple, clear and controllable, but the disadvantage is that there is too much specific generated code. When we modify, we may have some problems if we encounter a version update of jOQ

  • Scenario 2, we want to achieve the desired effect by calling the parent method to complete the code content filling, and then encoding to replace the specified string. The advantage of this method is that the changes are small and will not affect the original logic. The disadvantage is that in case the jOOQ class name is modified (this generally does not occur), there will be compatibility problems and the string content we replace needs to be updated.

Here I use the second scheme, which is relatively simple and the specific implementation logic is also simple.Override generateDao method

  1. Add import com.diamondfsd.jooq.learn.extend.AbstractExtendDAOImpl; code block
  2. Call parent method for code generation
  3. Get the content of the generated file
  4. Delete import org.jooq.impl.DAOImpl;
  5. Replace extends DAOImpl with extends AbstractExtendDAOImpl;
  6. Rewrite to target file
public class CustomJavaGenerator extends JavaGenerator {
    private static final JooqLogger log = JooqLogger.getLogger(CustomJavaGenerator.class);


    /**
     * Override generateDao, specifically generate logic or call parent's method, just get the file content after the build is complete,
     * Then replace the contents specified in the file
     *
     * @param table
     */
    @Override
    protected void generateDao(TableDefinition table) {
        super.generateDao(table);
        File file = getFile(table, GeneratorStrategy.Mode.DAO);
        if (file.exists()) {
            try {
                String fileContent = new String(FileCopyUtils.copyToByteArray(file));
                String oldExtends = " extends DAOImpl";
                String newExtends = " extends AbstractExtendDAOImpl";
                fileContent = fileContent.replace("import org.jooq.impl.DAOImpl;\n", "");
                fileContent = fileContent.replace(oldExtends, newExtends);
                FileCopyUtils.copy(fileContent.getBytes(), file);
            } catch (IOException e) {
                log.error("generateDao error: {}", file.getAbsolutePath(), e);
            }
        }
    }

    @Override
    protected void generateDao(TableDefinition table, JavaWriter out) {
        // Used to generate import com.diamondfsd.jooq.learn.extend.AbstractExtendDAOImpl content
        out.ref(AbstractExtendDAOImpl.class);
        super.generateDao(table, out);

    }
}

After the custom code generator is written, modify the code generator target of the jOOQ plug-in configuration to CustomJavaGenerator

<generator>
    <name>com.diamondfsd.jooq.learn.CustomJavaGenerator</name>
    <!-- ... -->
</generator>

With this configuration, when the code generator is invoked, code generation occurs through logic in our custom class

Paging Query Encapsulation

The AbstractExtendDAOImpl class is created by us, and all generated DAOs inherit it, so it is easy to extend the public methods by writing some extended public methods directly in this class.

Here I define an interface, ExtendDAO, to define some common methods

public interface ExtendDAO<R extends UpdatableRecord<R>, P, T> extends DAO<R, P, T> {

    /**
     * Get DSLContext
     *
     * @return DSLContext
     */
    DSLContext create();

    /**
     * Conditional Query Single Record
     *
     * @param condition constraint condition
     * @return <p>
     */
    P fetchOne(Condition condition);

    /**
     * Conditional Query Single Record
     *
     * @param condition constraint condition
     * @return Optional<P>
     */
    Optional<P> fetchOneOptional(Condition condition);

    /**
     * Conditional Query Multiple and Sort
     *
     * @param condition  constraint condition
     * @param sortFields sort field
     * @return POJO aggregate
     */
    List<P> fetch(Condition condition, SortField<?>... sortFields);

    /**
     * Read Paging Data
     *
     * @param pageResult Paging parameters
     * @param condition  constraint condition
     * @param sortFields sort field
     * @return Paging Result Set
     */
    PageResult<P> fetchPage(PageResult<P> pageResult, Condition condition, SortField<?>... sortFields);

    /**
     * Read Paging Data
     *
     * @param pageResult      Paging parameters
     * @param selectLimitStep Query Statement
     * @return Paging Result Set
     */
    PageResult<P> fetchPage(PageResult<P> pageResult, SelectLimitStep<?> selectLimitStep);

    /**
     * Read paging data of any type
     *
     * @param pageResult      Paging parameters
     * @param selectLimitStep Query Statement
     * @param mapper          Result mapping
     * @param <O>             Generics of return types
     * @return Paging Result Set
     */
    <O> PageResult<O> fetchPage(PageResult<O> pageResult, SelectLimitStep<?> selectLimitStep,
                                RecordMapper<? super Record, O> mapper);

    /**
     * Read paging data of any type
     *
     * @param pageResult      Paging parameters
     * @param selectLimitStep Query Statement
     * @param pojoType        POJO type
     * @param <O>             Generics of return types
     * @return Paging Result Set
     */
    <O> PageResult<O> fetchPage(PageResult<O> pageResult, SelectLimitStep<?> selectLimitStep,
                                Class<O> pojoType);

}

 

AbstractExtendDAOImpl implements the ExtendDAO interface. Other implementations are not shown here. You can see the source code. Here we will focus on the implementation of paging.

Paging here uses MySQL's SQL_CALC_FOUND_ROWS keyword and SELECT FOUND_ROWS() statement.Before querying, get the original SQL statement, and stitch together the SQL_CALC_FOUND_ROWS keyword after selecting to query the total number of rows by select FOUND_ROWS () after executing the SQL

public abstract class AbstractExtendDAOImpl<R extends UpdatableRecord<R>, P, T> extends DAOImpl<R, P, T>
        implements ExtendDAO<R, P, T> {


    @Override
    public DSLContext create() {
        return DSL.using(configuration());
    }

    @Override
    public <O> PageResult<O> fetchPage(PageResult<O> pageResult, SelectLimitStep<?> selectLimitStep,
                                       RecordMapper<? super Record, O> mapper) {
        int size = pageResult.getPageSize();
        int start = (pageResult.getCurrentPage() - 1) * size;
        // Small optimization with zero pages, returning a paging wrapper class with empty data directly without querying the database
        if (size == 0) {
            return new PageResult<>(Collections.emptyList(), start, 0, 0);
        }
        String pageSql = selectLimitStep.getSQL(ParamType.INLINED);
        String SELECT = "select";

        pageSql = SELECT + " SQL_CALC_FOUND_ROWS " +
                pageSql.substring(pageSql.indexOf(SELECT) + SELECT.length())
                + " limit ?, ? ";

        List<O> resultList = create().fetch(pageSql, start, size).map(mapper);
        Long total = create().fetchOne("SELECT FOUND_ROWS()").into(Long.class);
        PageResult<O> result = pageResult.into(new PageResult<>());
        result.setData(resultList);
        result.setTotal(total);
        return result;
    }
}

Content Summary

Source code for this chapter: https://github.com/k55k32/learn-jooq/tree/master/section-9

This chapter completes the extension of DAO by using a custom code generator.Currently, only the basic DAO has been expanded, with some conditional query methods and the encapsulation of paging queries added.I hope you can learn some ways to use the custom code generator and override it.Mastering these customizations of jOQs functionality can be very helpful

0 original articles published, 0 praised, 53 visits
Private letter follow

Topics: Oracle SQL encoding MySQL