11

I am using Swagger codegen to create Java models to be used in a Spring REST server, and would like to know how to get Swagger to declare each model as a JPA entity.

I generate the code with the swagger-codegen-maven-plugin as follows:

<plugin>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>2.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/openApi/Rack.json</inputSpec>
                <language>spring</language>
                <groupId>com.me</groupId>
                <artifactId>rest-server</artifactId>
                <apiPackage>com.me.rest.api</apiPackage>
                <modelPackage>com.me.rest.model</modelPackage>
                <invokerPackage>com.me.rest.invoker</invokerPackage>
                <configOptions>
                    <sourceFolder>src/gen/java/main</sourceFolder>
                    <java8>true</java8>
                    <dateLibrary>java8</dateLibrary>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

As I have it now, this is the abbreviated java code that gets generated:

@Validated
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "...")

public class Rack   {
  @JsonProperty("id")
  private Long id = null;

  @JsonProperty("name")
  private String name = null;

  ...
}

How do I get Swagger to add the @Entity and @Id JPA annotations, as follows?

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
@Validated
@javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "...")

public class Rack   {
  @Id
  @JsonProperty("id")
  private Long id = null;

  @JsonProperty("name")
  private String name = null;

  ...
}

This way, all I would have to do to get Spring to automatically expose these generated classes as REST APIs, would be to add the following to my pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

Then I could create the JPA repositories with Spring-Data, as follows:

public interface RackRepository extends CrudRepository<Rack, Long> {
}
Brady
  • 10,207
  • 2
  • 20
  • 59
  • I was looking for exactly this this morning, I don't believe it's done because it's regarded as a security risk by exposing your entire model on the frontend. The insistance on a DTO Layer forces you to consider what parts of your model are for user consumption, Name, DOB that kinda thing versus internal systems stuff such as roles and whotnot. Still... I can't believe that this hasn't been done and some form of exposure mechanism included in swagger contract just to say, don't expose this field to the frontend? – Mark Gargan May 07 '19 at 10:37
  • I ended up having to modify the Swagger code-gen code. It turned out to be more complicated than originally though since you have to define at least OneToMany, Embedded, and Embedable annotations, which is not trivial, if not impossible to do in a generic manner. I have a branch created with specific model class names. – Brady May 13 '19 at 10:46

3 Answers3

9

A PR has recently been merged fixing your issue : https://github.com/OpenAPITools/openapi-generator/pull/11775

You need to upgrade your Maven plugin to use the latest version (currently unreleased, only snapshot is available)

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.0.0-SNAPSHOT</version>
    ...
</plugin>

The configuration might be slightly different.

Then you need to add x-class-extra-annotation and x-field-extra-annotation in your spec.

For instance for the Pet Clinic:

  schemas:
    Pet:
      type: object
      x-class-extra-annotation: "@javax.persistence.Entity"
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
          x-field-extra-annotation: "@javax.persistence.Id"
        name:
          type: string
        tag:
          type: string
Mathieu D
  • 136
  • 1
  • 2
  • @[Mathieu D] That's great, exactly wha I was looking for, however I see that the version 6 is still in beta, even though you posted this commend in Mar 18? Need a released version of 6 :/ – user2441441 May 25 '22 at 20:51
  • 1
    https://github.com/OpenAPITools/openapi-generator/releases/tag/v6.0.0 Good things come to those who wait :) – Mathieu D Jun 03 '22 at 15:02
4

While the right way to solve this surely is an extension of swagger-codegen (probably with the introduction of some kind of include/exclude config), I got away with a fairly simply post-processing of the generated files.

In contrast to the OP I use Gradle instead of Maven and leveraged its extended filtering functionality. For Maven it is probably necessary to run a Groovy-script by way of the Groovy-Maven-Plugin, since Maven only supports placeholder substitution (as does Ant, so using the AntRun-Plugin would also not work).

I used a simple heuristic to only include entities with an id - the logic is as follows:

  • for all Java-files containing an ID-field
    • include import statement for javax.persistence.* after the package declaration
    • add the @Entity-annotation before the class definition
    • for the ID-field, add the annotations @Id and @GeneratedValue
    • (based on field names, other annotations - @OneToMany etc. - may be added as well)

Gradle-users may find the following task useful as a start:

task generateJpaAnnotations(type: Copy) {
    from "${swaggerSources.<modelName>.code.outputDir}/src/main/java"
    into "<output dir>
    include '**/*.java'

    eachFile {
        if (it.file.text.contains("private Long id")) {
            filter { line -> line.contains('package') ? "$line\nimport javax.persistence.*;" : line }
            filter { line -> line.contains('public class') ? "@Entity\n$line" : line }
            filter { line -> line.contains('private Long id') ? "@Id\n@GeneratedValue(strategy=GenerationType.AUTO)\n$line" : line }        }
    }
}
mthomas
  • 624
  • 2
  • 9
  • 20
2

So I'm actually asking myself the same question. I found an example but the guy is simply re-defining his POJOs and providing a way to adapt the generated ones to the handwritten ones. Tedious and not evolutive.

Globally this could be hard because I'm not sure there is a way in your swagger to decide which POJO will be JPA enabled and maybe you don't want them all in your DB (?) Also, how to you tag the id in swagger? If you know of such a way, you can always modify the mustache (pojo.mustache I guess) to give you the annotations you're missing.

Kiskit
  • 121
  • 2