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
- Add import com.diamondfsd.jooq.learn.extend.AbstractExtendDAOImpl; code block
- Call parent method for code generation
- Get the content of the generated file
- Delete import org.jooq.impl.DAOImpl;
- Replace extends DAOImpl with extends AbstractExtendDAOImpl;
- 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