From 69f4fa30660b57f580bbbc45eb884a76092de2ea Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 11 Mar 2022 11:04:14 +0100 Subject: [PATCH] [jOOQ/jOOQ#10756] Generate @Transactional on DAOImpl and generated DAOs query-methods --- .../org/jooq/codegen/AbstractGenerator.java | 11 + .../java/org/jooq/codegen/GenerationTool.java | 2 + .../main/java/org/jooq/codegen/Generator.java | 17 ++ .../java/org/jooq/codegen/JavaGenerator.java | 283 +++++++++++++++++- .../java/org/jooq/meta/jaxb/Generate.java | 42 +++ .../org/jooq/meta/xsd/jooq-codegen-3.17.0.xsd | 4 + 6 files changed, 358 insertions(+), 1 deletion(-) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java index a39be12283..a3d36d7ce3 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java @@ -112,6 +112,7 @@ abstract class AbstractGenerator implements Generator { String generateJPAVersion = ""; boolean generateValidationAnnotations = false; boolean generateSpringAnnotations = false; + boolean generateSpringDao = false; boolean generateKotlinSetterJvmNameAnnotationsOnIsPrefix = true; GeneratedSerialVersionUID generatedSerialVersionUID = GeneratedSerialVersionUID.CONSTANT; int maxMembersPerInitialiser = 500; @@ -733,6 +734,16 @@ abstract class AbstractGenerator implements Generator { this.generateSpringAnnotations = generateSpringAnnotations; } + @Override + public boolean generateSpringDao() { + return generateSpringDao; + } + + @Override + public void setGenerateSpringDao(boolean generateSpringDao) { + this.generateSpringDao = generateSpringDao; + } + @Override public boolean generateKotlinSetterJvmNameAnnotationsOnIsPrefix() { return generateKotlinSetterJvmNameAnnotationsOnIsPrefix; diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java index 8045784f22..164eb52292 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java @@ -791,6 +791,8 @@ public class GenerationTool { generator.setGenerateValidationAnnotations(g.getGenerate().isValidationAnnotations()); if (g.getGenerate().isSpringAnnotations() != null) generator.setGenerateSpringAnnotations(g.getGenerate().isSpringAnnotations()); + if (g.getGenerate().isSpringDao() != null) + generator.setGenerateSpringDao(g.getGenerate().isSpringDao()); if (g.getGenerate().isKotlinSetterJvmNameAnnotationsOnIsPrefix() != null) generator.setGenerateKotlinSetterJvmNameAnnotationsOnIsPrefix(g.getGenerate().isKotlinSetterJvmNameAnnotationsOnIsPrefix()); if (g.getGenerate().getGeneratedSerialVersionUID() != null) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java index 9731d5312c..f3226c3224 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java @@ -46,6 +46,7 @@ import org.jooq.JSON; import org.jooq.JSONB; import org.jooq.Spatial; import org.jooq.XML; +import org.jooq.impl.DAOImpl; import org.jooq.meta.Database; import org.jooq.meta.jaxb.GeneratedAnnotationType; import org.jooq.meta.jaxb.GeneratedSerialVersionUID; @@ -587,6 +588,22 @@ public interface Generator { */ void setGenerateSpringAnnotations(boolean generateSpringAnnotations); + /** + * Whether a Spring specific {@link DAOImpl} subclass should be generated, + * which may contain Spring specific stuff, such as the + * @Transactional annotation (if + * {@link #generateSpringAnnotations()} is set). + */ + boolean generateSpringDao(); + + /** + * Whether a Spring specific {@link DAOImpl} subclass should be generated, + * which may contain Spring specific stuff, such as the + * @Transactional annotation (if + * {@link #generateSpringAnnotations()} is set). + */ + void setGenerateSpringDao(boolean generateSpringDao); + /** * Whether kotlin mutable properties should be annotated with * set:JvmName as a workaround for problems occurring when diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java index 7c8418f0dc..21498e0c0c 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -87,6 +87,7 @@ import java.util.Optional; import java.util.Set; import java.util.TimeZone; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -129,6 +130,7 @@ import org.jooq.TableOptions; import org.jooq.UDT; import org.jooq.UDTField; import org.jooq.UniqueKey; +import org.jooq.UpdatableRecord; import org.jooq.codegen.GeneratorStrategy.Mode; import org.jooq.codegen.GeneratorWriter.CloseResult; import org.jooq.exception.SQLDialectNotSupportedException; @@ -571,6 +573,9 @@ public class JavaGenerator extends AbstractGenerator { generateCatalog(catalog); + if (generateSpringDao() && catalog.getSchemata().stream().anyMatch(s -> !s.getTables().isEmpty())) + generateSpringDao(catalog); + log.info("Generating schemata", "Total: " + catalog.getSchemata().size()); for (SchemaDefinition schema : catalog.getSchemata()) { try { @@ -4150,6 +4155,280 @@ public class JavaGenerator extends AbstractGenerator { watch.splitInfo("Table DAOs generated"); } + protected void generateSpringDao(CatalogDefinition catalog) { + // [#10756] Optionally, this class could be implemented in a new jooq-spring-extensions module + JavaWriter out = newJavaWriter(new File(getStrategy().getFile(catalog).getParentFile(), "AbstractSpringDAOImpl.java")); + log.info("Generating AbstractSpringDAOImpl", out.file().getName()); + generateSpringDao(catalog, out); + closeJavaWriter(out); + } + + protected void generateSpringDao(CatalogDefinition catalog, JavaWriter out) { + printPackage(out, catalog); + + printClassJavadoc(out, "Spring specific {@" + out.ref(DAOImpl.class) + "} override."); + printClassAnnotations(out, catalog, Mode.DEFAULT); + + String transactional = generateSpringAnnotations() + ? out.ref("org.springframework.transaction.annotation.Transactional") + : null; + String className = "AbstractSpringDAOImpl"; + + if (scala) { + if (generateSpringAnnotations()) + out.println("@%s(readOnly = true)", transactional); + + out.println("%sabstract class %s[R <: %s[R], P, T](table: %s[R], klass: java.lang.Class[P], configuration: %s) extends %s[R, P, T](table, klass, configuration) {", + visibility(), className, UpdatableRecord.class, Table.class, Configuration.class, DAOImpl.class); + + out.println(); + out.println("%sdef this(table: %s[R], klass: java.lang.Class[P]) = this(table, klass, null)", visibility(), Table.class); + } + else if (kotlin) { + if (generateSpringAnnotations()) + out.println("@%s(readOnly = true)", transactional); + + out.println("%sabstract class %s, P, T>(table: %s, type: %s

, configuration: %s?) : %s(table, type, configuration) {", + visibility(), className, UpdatableRecord.class, Table.class, Class.class, Configuration.class, DAOImpl.class); + + out.println(); + out.println("%sconstructor(table: %s, type: %s

) : this(table, type, null)", visibility(), Table.class, Class.class); + } + else { + if (generateSpringAnnotations()) + out.println("@%s(readOnly = true)", transactional); + + out.println("%sabstract class %s, P, T> extends %s {", visibility(), className, UpdatableRecord.class, DAOImpl.class); + out.println(); + out.println("protected %s(%s table, %s

type) {", className, Table.class, Class.class); + out.println("super(table, type);"); + out.println("}"); + out.println(); + out.println("protected %s(%s table, %s

type, %s configuration) {", className, Table.class, Class.class, Configuration.class); + out.println("super(table, type, configuration);"); + out.println("}"); + } + + Consumer printTransactionalHeader = readOnly -> { + out.println(); + + if (readOnly) + out.println("@%s(readOnly = true)", transactional); + else + out.println("@%s", transactional); + }; + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def count(): Long = super.count()"); + } + else if (kotlin) { + out.println("public override fun count(): Long = super.count()"); + } + else { + out.override(); + out.println("public long count() {"); + out.println("return super.count();"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def exists(obj: P): Boolean = super.exists(obj)"); + } + else if (kotlin) { + out.println("public override fun exists(obj: P): Boolean = super.exists(obj)"); + } + else { + out.override(); + out.println("public boolean exists(P object) {"); + out.println("return super.exists(object);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def existsById(id: T): Boolean = super.existsById(id)"); + } + else if (kotlin) { + out.println("public override fun existsById(id: T): Boolean = super.existsById(id)"); + } + else { + out.override(); + out.println("public boolean existsById(T id) {"); + out.println("return super.existsById(id);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def fetch[Z](field: %s[Z], values: %s[_ <: Z]): %s[P] = super.fetch(field, values)", Field.class, Collection.class, List.class); + } + else if (kotlin) { + out.println("public override fun fetch(field: %s, values: %s): %s

= super.fetch(field, values)", Field.class, out.ref("kotlin.collections.Collection"), out.ref("kotlin.collections.List")); + } + else { + out.override(); + out.println("public %s

fetch(%s field, %s values) {", List.class, Field.class, Collection.class); + out.println("return super.fetch(field, values);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def fetch[Z](field: %s[Z], values: Z*): %s[P] = super.fetch(field, values:_*)", Field.class, List.class); + } + else if (kotlin) { + out.println("public override fun fetch(field: %s, vararg values: Z): %s

= super.fetch(field, *values)", Field.class, out.ref("kotlin.collections.List")); + } + else { + out.override(); + out.println("public %s

fetch(%s field, Z... values) {", List.class, Field.class); + out.println("return super.fetch(field, values);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def fetchOne[Z](field: %s[Z], value: Z): P = super.fetchOne(field, value)", Field.class); + } + else if (kotlin) { + out.println("public override fun fetchOne(field: %s, value: Z): P? = super.fetchOne(field, value)", Field.class); + } + else { + out.override(); + out.println("public P fetchOne(%s field, Z value) {", Field.class); + out.println("return super.fetchOne(field, value);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def fetchOptional[Z](field: %s[Z], value: Z): %s[P] = super.fetchOptional(field, value)", Field.class, Optional.class); + } + else if (kotlin) { + out.println("public override fun fetchOptional(field: %s, value: Z): %s

= super.fetchOptional(field, value)", Field.class, Optional.class); + } + else { + out.override(); + out.println("public %s

fetchOptional(%s field, Z value) {", Optional.class, Field.class); + out.println("return super.fetchOptional(field, value);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def fetchRange[Z](field: %s[Z], lowerInclusive: Z, upperInclusive: Z): %s[P] = super.fetchRange(field, lowerInclusive, upperInclusive)", Field.class, List.class); + } + else if (kotlin) { + out.println("public override fun fetchRange(field: %s, lowerInclusive: Z, upperInclusive: Z): %s

= super.fetchRange(field, lowerInclusive, upperInclusive)", Field.class, out.ref("kotlin.collections.List")); + } + else { + out.override(); + out.println("public %s

fetchRange(%s field, Z lowerInclusive, Z upperInclusive) {", List.class, Field.class); + out.println("return super.fetchRange(field, lowerInclusive, upperInclusive);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def findAll(): %s[P] = super.findAll()", List.class); + } + else if (kotlin) { + out.println("public override fun findAll(): %s

= super.findAll()", out.ref("kotlin.collections.List")); + } + else { + out.override(); + out.println("public %s

findAll() {", List.class); + out.println("return super.findAll();"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def findById(id: T): P = super.findById(id)"); + } + else if (kotlin) { + out.println("public override fun findById(id: T): P? = super.findById(id)"); + } + else { + out.override(); + out.println("public P findById(T id) {"); + out.println("return super.findById(id);"); + out.println("}"); + } + + printTransactionalHeader.accept(true); + if (scala) { + out.println("override def findOptionalById(id: T): %s[P] = super.findOptionalById(id)", Optional.class); + } + else if (kotlin) { + out.println("public override fun findOptionalById(id: T): %s

= super.findOptionalById(id)", Optional.class); + } + else { + out.override(); + out.println("public %s

findOptionalById(T id) {", Optional.class); + out.println("return super.findOptionalById(id);"); + out.println("}"); + } + + for (String name : asList("insert", "update", "merge", "delete", "deleteById")) { + String argType = name.endsWith("ById") ? "T" : "P"; + String argName = name.endsWith("ById") ? "id" : "obj"; + + printTransactionalHeader.accept(false); + if (scala) { + out.println("override def %s(%s: %s): Unit = super.%s(%s)", name, argName, argType, name, argName); + } + else if (kotlin) { + out.println("public override fun %s(%s: %s): Unit = super.%s(%s)", name, argName, argType, name, argName); + } + else { + out.override(); + out.println("public void %s(%s<%s> %ss) {", name, Collection.class, argType, argName); + out.println("super.%s(%ss);", name, argName); + out.println("}"); + } + + printTransactionalHeader.accept(false); + if (scala) { + out.println("override def %s(%ss: %s*): Unit = super.%s(%ss:_*)", name, argName, argType, name, argName); + } + else if (kotlin) { + out.println("public override fun %s(vararg %ss: %s): Unit = super.%s(*%ss)", name, argName, argType, name, argName); + } + else { + out.override(); + out.println("public void %s(%s %s) {", name, argType, argName); + out.println("super.%s(%s);", name, argName); + out.println("}"); + } + + printTransactionalHeader.accept(false); + if (scala) { + out.println("override def %s(%ss: %s[%s]): Unit = super.%s(%ss)", name, argName, Collection.class, argType, name, argName); + } + else if (kotlin) { + out.println("public override fun %s(%ss: %s<%s>): Unit = super.%s(%ss)", name, argName, out.ref("kotlin.collections.Collection"), argType, name, argName); + } + else { + out.override(); + out.println("public void %s(%s... %ss) {", name, argType, argName); + out.println("super.%s(%ss);", name, argName); + out.println("}"); + } + } + + generateSpringDaoClassFooter(catalog, out); + out.println("}"); + } + + /** + * Subclasses may override this method to provide table references class footer code. + */ + @SuppressWarnings("unused") + protected void generateSpringDaoClassFooter(CatalogDefinition catalog, JavaWriter out) {} + protected void generateDao(TableDefinition table) { JavaWriter out = newJavaWriter(getFile(table, Mode.DAO)); log.info("Generating DAO", out.file().getName()); @@ -4167,7 +4446,9 @@ public class JavaGenerator extends AbstractGenerator { final String className = getStrategy().getJavaClassName(table, Mode.DAO); final List interfaces = out.ref(getStrategy().getJavaClassImplements(table, Mode.DAO)); final String tableRecord = out.ref(getStrategy().getFullJavaClassName(table, Mode.RECORD)); - final String daoImpl = out.ref(DAOImpl.class); + final String daoImpl = generateSpringDao() + ? out.ref(getStrategy().getJavaPackageName(table.getSchema(), Mode.DAO) + ".AbstractSpringDAOImpl") + : out.ref(DAOImpl.class); final String tableIdentifier = out.ref(getStrategy().getFullJavaIdentifier(table), 2); String tType = (scala || kotlin ? "Unit" : "Void"); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java index f7613fecb4..fc676d80cf 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java @@ -130,6 +130,8 @@ public class Generate implements Serializable, XMLAppendable protected Boolean validationAnnotations = false; @XmlElement(defaultValue = "false") protected Boolean springAnnotations = false; + @XmlElement(defaultValue = "false") + protected Boolean springDao = false; @XmlElement(defaultValue = "true") protected Boolean kotlinSetterJvmNameAnnotationsOnIsPrefix = true; @XmlElement(defaultValue = "true") @@ -1380,6 +1382,30 @@ public class Generate implements Serializable, XMLAppendable this.springAnnotations = value; } + /** + * Generate an AbstractSpringDAOImpl as a base class for other DAO classes, containing @Transactional annotations, etc. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isSpringDao() { + return springDao; + } + + /** + * Sets the value of the springDao property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setSpringDao(Boolean value) { + this.springDao = value; + } + /** * Workaround for Kotlin generating setX() setters instead of setIsX() in byte code for mutable properties called isX. * @@ -2756,6 +2782,11 @@ public class Generate implements Serializable, XMLAppendable return this; } + public Generate withSpringDao(Boolean value) { + setSpringDao(value); + return this; + } + public Generate withKotlinSetterJvmNameAnnotationsOnIsPrefix(Boolean value) { setKotlinSetterJvmNameAnnotationsOnIsPrefix(value); return this; @@ -3070,6 +3101,7 @@ public class Generate implements Serializable, XMLAppendable builder.append("jpaVersion", jpaVersion); builder.append("validationAnnotations", validationAnnotations); builder.append("springAnnotations", springAnnotations); + builder.append("springDao", springDao); builder.append("kotlinSetterJvmNameAnnotationsOnIsPrefix", kotlinSetterJvmNameAnnotationsOnIsPrefix); builder.append("globalObjectReferences", globalObjectReferences); builder.append("globalCatalogReferences", globalCatalogReferences); @@ -3579,6 +3611,15 @@ public class Generate implements Serializable, XMLAppendable return false; } } + if (springDao == null) { + if (other.springDao!= null) { + return false; + } + } else { + if (!springDao.equals(other.springDao)) { + return false; + } + } if (kotlinSetterJvmNameAnnotationsOnIsPrefix == null) { if (other.kotlinSetterJvmNameAnnotationsOnIsPrefix!= null) { return false; @@ -4058,6 +4099,7 @@ public class Generate implements Serializable, XMLAppendable result = ((prime*result)+((jpaVersion == null)? 0 :jpaVersion.hashCode())); result = ((prime*result)+((validationAnnotations == null)? 0 :validationAnnotations.hashCode())); result = ((prime*result)+((springAnnotations == null)? 0 :springAnnotations.hashCode())); + result = ((prime*result)+((springDao == null)? 0 :springDao.hashCode())); result = ((prime*result)+((kotlinSetterJvmNameAnnotationsOnIsPrefix == null)? 0 :kotlinSetterJvmNameAnnotationsOnIsPrefix.hashCode())); result = ((prime*result)+((globalObjectReferences == null)? 0 :globalObjectReferences.hashCode())); result = ((prime*result)+((globalCatalogReferences == null)? 0 :globalCatalogReferences.hashCode())); diff --git a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.17.0.xsd b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.17.0.xsd index f5601c517a..62f981161f 100644 --- a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.17.0.xsd +++ b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.17.0.xsd @@ -1793,6 +1793,10 @@ jOOQ version used for source code.]]> + + + + setX() setters instead of setIsX() in byte code for mutable properties called isX.]]>