blob: 05c82e6340f1cc66be5c7b9bfa61850b59788c5f [file]
<!DOCTYPE html>
<!--
| Generated by Apache Maven Doxia Site Renderer 2.0.0 from src/site/xdoc/docs/openapi-jpa-schema.xml at 2026-05-18
| Rendered using Apache Maven Fluido Skin 2.0.0-M11
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content="Apache Maven Doxia Site Renderer 2.0.0" />
<title>JPA/Hibernate Schema Generation for OpenAPI – Apache Axis2</title>
<link rel="stylesheet" href="../css/apache-maven-fluido-2.0.0-M11.min.css" />
<link rel="stylesheet" href="../css/site.css" />
<link rel="stylesheet" href="../css/print.css" media="print" />
<script src="../js/apache-maven-fluido-2.0.0-M11.min.js"></script>
</head>
<body>
<div class="container-fluid container-fluid-top">
<header>
<div id="banner">
<div class="pull-left"><div id="bannerLeft"><h1><a href="https://www.apache.org/"><img class="class java.lang.Object" src="https://www.apache.org/images/asf_logo_wide.png" /> Apache Axis2</a></h1></div></div>
<div class="pull-right"><div id="bannerRight"><h1><a href="https://axis.apache.org/axis2/java/core/"><img class="class java.lang.Object" src="https://axis.apache.org/axis2/java/core/images/axis.jpg" /></a></h1></div></div>
<div class="clear"><hr/></div>
</div>
<div id="breadcrumbs">
<ul class="breadcrumb">
<li id="publishDate">Last Published: 2026-05-17<span class="divider">|</span>
</li>
<li id="projectVersion">Version: 2.0.1<span class="divider">|</span></li>
<li><a href="https://www.apache.org" class="externalLink">Apache</a><span class="divider">/</span></li>
<li><a href="../index.html">Axis2/Java</a><span class="divider">/</span></li>
<li class="active">JPA/Hibernate Schema Generation for OpenAPI</li>
</ul>
</div>
</header>
<div class="row-fluid">
<header id="leftColumn" class="span2">
<nav class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Axis2/Java</li>
<li><a href="../index.html">Home</a></li>
<li><a href="../download.html">Downloads</a></li>
<li><a href="javascript:void(0)"><span class="icon-chevron-down"></span>Release Notes</a>
<ul class="nav nav-list">
<li><a href="../release-notes/1.6.1.html">1.6.1</a></li>
<li><a href="../release-notes/1.6.2.html">1.6.2</a></li>
<li><a href="../release-notes/1.6.3.html">1.6.3</a></li>
<li><a href="../release-notes/1.6.4.html">1.6.4</a></li>
<li><a href="../release-notes/1.7.0.html">1.7.0</a></li>
<li><a href="../release-notes/1.7.1.html">1.7.1</a></li>
<li><a href="../release-notes/1.7.2.html">1.7.2</a></li>
<li><a href="../release-notes/1.7.3.html">1.7.3</a></li>
<li><a href="../release-notes/1.7.4.html">1.7.4</a></li>
<li><a href="../release-notes/1.7.5.html">1.7.5</a></li>
<li><a href="../release-notes/1.7.6.html">1.7.6</a></li>
<li><a href="../release-notes/1.7.7.html">1.7.7</a></li>
<li><a href="../release-notes/1.7.8.html">1.7.8</a></li>
<li><a href="../release-notes/1.7.9.html">1.7.9</a></li>
<li><a href="../release-notes/1.8.0.html">1.8.0</a></li>
<li><a href="../release-notes/1.8.1.html">1.8.1</a></li>
<li><a href="../release-notes/1.8.2.html">1.8.2</a></li>
<li><a href="../release-notes/2.0.0.html">2.0.0</a></li>
<li><a href="../release-notes/2.0.1.html">2.0.1</a></li>
</ul></li>
<li><a href="../modules/index.html">Modules</a></li>
<li><a href="../tools/index.html">Tools</a></li>
<li class="nav-header">Documentation</li>
<li><a href="../docs/toc.html">Table of Contents</a></li>
<li><a href="../docs/installationguide.html">Installation Guide</a></li>
<li><a href="../docs/quickstartguide.html">QuickStart Guide</a></li>
<li><a href="../docs/userguide.html">User Guide</a></li>
<li><a href="../docs/jaxws-guide.html">JAXWS Guide</a></li>
<li><a href="../docs/pojoguide.html">POJO Guide</a></li>
<li><a href="../docs/spring.html">Spring Guide</a></li>
<li><a href="../docs/webadminguide.html">Web Administrator&apos;s Guide</a></li>
<li><a href="../docs/migration.html">Migration Guide (from Axis1)</a></li>
<li class="nav-header">Resources</li>
<li><a href="../faq.html">FAQ</a></li>
<li><a href="https://github.com/apache/axis-axis2-java-core" class="externalLink">Source Code</a></li>
<li class="nav-header">Get Involved</li>
<li><a href="../overview.html">Overview</a></li>
<li><a href="../mail-lists.html">Mailing Lists</a></li>
<li><a href="../release-process.html">Release Process</a></li>
<li><a href="../guidelines.html">Developer Guidelines</a></li>
<li><a href="../siteHowTo.html">Build the Site</a></li>
<li class="nav-header">Project Information</li>
<li><a href="https://github.com/apache/axis-axis2-java-core/graphs/contributors" class="externalLink">Contributors</a></li>
<li><a href="https://issues.apache.org/jira/projects/AXIS2/issues" class="externalLink">Issues</a></li>
<li class="nav-header">Apache</li>
<li><a href="https://www.apache.org/licenses/LICENSE-2.0.html" class="externalLink">License</a></li>
<li><a href="https://www.apache.org/foundation/sponsorship.html" class="externalLink">Sponsorship</a></li>
<li><a href="https://www.apache.org/foundation/thanks.html" class="externalLink">Thanks</a></li>
<li><a href="https://www.apache.org/security/" class="externalLink">Security</a></li>
</ul>
</nav>
<div class="well sidebar-nav">
<div id="poweredBy">
<div class="clear"></div>
<div class="clear"></div>
<a href="https://maven.apache.org/" class="builtBy" target="_blank"><img class="builtBy" alt="Built by Maven" src="../images/logos/maven-feather.png" /></a>
</div>
</div>
</header>
<main id="bodyColumn" class="span10">
<html xmlns="http://www.w3.org/1999/xhtml">
<section><a id="JPA.2FHibernate_Schema_Generation_for_OpenAPI"></a>
<h1>JPA/Hibernate Schema Generation for OpenAPI</h1>
<p>The <code>axis2-jpa-schema</code> module generates JSON Schema definitions
from JPA annotations or Hibernate XML mappings (<code>.hbm.xml</code>).
These schemas can be embedded in OpenAPI 3.0 specifications to document
request/response bodies that map directly to database entities.</p>
<p><strong>Module:</strong> <code>modules/jpa-schema</code><br />
<strong>Package:</strong> <code>org.apache.axis2.jpa.schema</code></p>
<section><a id="JPA_Annotation_Mode"></a>
<h2 id="annotation_mode">JPA Annotation Mode</h2>
<p>The <code>AnnotationIntrospector</code> uses Java reflection to read
Jakarta Persistence annotations from compiled <code>@Entity</code> classes.
Unlike the <a href="#hbm_xml_mode">HBM XML mode</a> (which reads raw XML
files and has a standalone CLI tool), annotation mode requires the entity
classes to be compiled and on the classpath. Typical integration points:</p>
<ul>
<li><strong>At startup</strong> &#x2014; a Spring bean or context listener scans
entity classes and generates schemas on first request</li>
<li><strong>At build time</strong> &#x2014; a Maven/Gradle task that runs after
compilation (e.g., in the <code>process-classes</code> phase)</li>
<li><strong>In unit tests</strong> &#x2014; call
<code>introspector.introspect(Product.class)</code> directly</li>
</ul>
<p>The introspector reads standard Jakarta Persistence annotations
(<code>@Entity</code>, <code>@Column</code>, <code>@Id</code>,
<code>@ManyToOne</code>, <code>@OneToMany</code>, etc.) and produces an
<code>EntitySchemaModel</code> that the <code>JpaSchemaGenerator</code>
converts to JSON Schema.</p>
<section><a id="Supported_Annotations"></a>
<h3>Supported Annotations</h3>
<table class="bodyTableBorder">
<tr class="a">
<th>Annotation</th>
<th>Schema Effect</th></tr>
<tr class="b">
<td><code>@Entity</code></td>
<td>Entity is eligible for introspection</td></tr>
<tr class="a">
<td><code>@Table(name=&quot;...&quot;)</code></td>
<td>Recorded in schema description</td></tr>
<tr class="b">
<td><code>@Id</code></td>
<td>Marked as required; <code>readOnly=true</code> in read schema</td></tr>
<tr class="a">
<td><code>@GeneratedValue</code></td>
<td>Excluded from write schema (server-managed)</td></tr>
<tr class="b">
<td><code>@Column(nullable, length)</code></td>
<td>Maps to <code>required</code> and <code>maxLength</code></td></tr>
<tr class="a">
<td><code>@Version</code></td>
<td>Excluded from write schema (server-managed)</td></tr>
<tr class="b">
<td><code>@Transient</code></td>
<td>Excluded from all schemas</td></tr>
<tr class="a">
<td><code>@Enumerated(STRING)</code></td>
<td>Emitted as <code>{&quot;type&quot;:&quot;string&quot;,&quot;enum&quot;:[...]}</code></td></tr>
<tr class="b">
<td><code>@ManyToOne</code></td>
<td>Emitted as <code>{&quot;$ref&quot;:&quot;#/components/schemas/Entity&quot;}</code></td></tr>
<tr class="a">
<td><code>@OneToMany</code></td>
<td>Emitted as <code>{&quot;type&quot;:&quot;array&quot;,&quot;items&quot;:{&quot;$ref&quot;:&quot;...&quot;}}</code></td></tr>
</table>
</section><section><a id="Example"></a>
<h3>Example</h3>
<pre>
@Entity
@Table(name = &quot;PRODUCT&quot;)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BigDecimal productID;
@Column(nullable = false, length = 200)
private String name;
@Enumerated(EnumType.STRING)
private ProductStatus status;
@Version
private Long objVersion;
@ManyToOne
private Department department;
@OneToMany
private List&lt;LineItem&gt; lineItems;
}
</pre>
<pre>
AnnotationIntrospector introspector = new AnnotationIntrospector();
EntitySchemaModel model = introspector.introspect(Product.class);
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);
</pre>
</section></section><section><a id="Read_vs_Write_Schema_Generation"></a>
<h2 id="read_write_schemas">Read vs Write Schema Generation</h2>
<p>The generator produces two schema variants per entity:</p>
<table class="bodyTableBorder">
<tr class="a">
<th>Variant</th>
<th>Includes</th>
<th>Use Case</th></tr>
<tr class="b">
<td><strong>Read schema</strong></td>
<td>All fields (IDs as <code>readOnly</code>)</td>
<td>GET response bodies</td></tr>
<tr class="a">
<td><strong>Write schema</strong></td>
<td>Excludes <code>@GeneratedValue</code> IDs, <code>@Version</code>, and custom audit fields</td>
<td>POST/PUT request bodies</td></tr>
</table>
<p>The <code>generateBothSchemas()</code> method returns both at once:</p>
<pre>
Map&lt;String, ObjectNode&gt; schemas = JpaSchemaGenerator.generateBothSchemas(model);
// &quot;Product&quot; &#x2192; read schema
// &quot;ProductWrite&quot; &#x2192; write schema
</pre>
<section><a id="What_Gets_Excluded_from_Write"></a>
<h3>What Gets Excluded from Write</h3>
<ul>
<li><code>@Id @GeneratedValue</code> &#x2014; server assigns the ID</li>
<li><code>@Version</code> &#x2014; server manages optimistic locking</li>
<li><code>@Transient</code> &#x2014; excluded from both read and write</li>
<li>Custom annotations (see below)</li>
</ul>
</section></section><section><a id="Custom_Audit_Annotation_Support"></a>
<h2 id="custom_annotations">Custom Audit Annotation Support</h2>
<p>Many enterprise codebases have project-specific annotations that mark
fields as server-managed (e.g., <code>@IgnoreChanges</code> for audit
timestamps, <code>@CreatedBy</code>, <code>@LastModifiedDate</code>).
Register these with the introspector to exclude them from write schemas:</p>
<pre>
AnnotationIntrospector introspector = new AnnotationIntrospector();
introspector.addWriteExcludeAnnotation(&quot;com.example.IgnoreChanges&quot;);
introspector.addWriteExcludeAnnotation(&quot;com.example.audit.CreatedBy&quot;);
EntitySchemaModel model = introspector.introspect(Product.class);
// Fields annotated with @IgnoreChanges or @CreatedBy are now
// excluded from the write schema but present in the read schema.
</pre>
</section><section><a id="Hibernate_XML_Mapping_Mode_.28.hbm.xml.29"></a>
<h2 id="hbm_xml_mode">Hibernate XML Mapping Mode (.hbm.xml)</h2>
<p>For codebases that use Hibernate XML mappings instead of (or alongside)
JPA annotations, the <code>HbmXmlIntrospector</code> parses <code>.hbm.xml</code>
files and produces the same <code>EntitySchemaModel</code>:</p>
<pre>
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
try (InputStream is = getClass().getResourceAsStream(&quot;/DepartmentBO.hbm.xml&quot;)) {
EntitySchemaModel model = introspector.introspect(is, &quot;DepartmentBO.hbm.xml&quot;);
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
}
</pre>
<section><a id="Supported_HBM_XML_Elements"></a>
<h3>Supported HBM XML Elements</h3>
<table class="bodyTableBorder">
<tr class="a">
<th>HBM XML Element</th>
<th>Schema Effect</th></tr>
<tr class="b">
<td><code>&lt;id&gt;</code> with <code>&lt;generator&gt;</code></td>
<td>Required, <code>readOnly</code>, excluded from write</td></tr>
<tr class="a">
<td><code>&lt;version&gt;</code></td>
<td>Excluded from write schema</td></tr>
<tr class="b">
<td><code>&lt;property&gt;</code></td>
<td>Mapped by Hibernate type &#x2192; JSON Schema type</td></tr>
<tr class="a">
<td><code>&lt;many-to-one&gt;</code></td>
<td><code>$ref</code> to referenced entity</td></tr>
<tr class="b">
<td><code>&lt;set&gt;</code> / <code>&lt;list&gt;</code></td>
<td>Array of <code>$ref</code></td></tr>
<tr class="a">
<td><code>&lt;component&gt;</code></td>
<td>Flattened with dot-notation prefix (e.g., <code>address.city</code>)</td></tr>
<tr class="b">
<td>Nested <code>&lt;column not-null=&quot;true&quot;&gt;</code></td>
<td>Maps to required</td></tr>
</table>
</section><section><a id="Type_Mapping"></a>
<h3>Type Mapping</h3>
<table class="bodyTableBorder">
<tr class="a">
<th>Hibernate / Java Type</th>
<th>JSON Schema</th></tr>
<tr class="b">
<td><code>string</code>, <code>text</code></td>
<td><code>{&quot;type&quot;:&quot;string&quot;}</code></td></tr>
<tr class="a">
<td><code>integer</code>, <code>int</code></td>
<td><code>{&quot;type&quot;:&quot;integer&quot;,&quot;format&quot;:&quot;int32&quot;}</code></td></tr>
<tr class="b">
<td><code>long</code>, <code>big_integer</code></td>
<td><code>{&quot;type&quot;:&quot;integer&quot;,&quot;format&quot;:&quot;int64&quot;}</code></td></tr>
<tr class="a">
<td><code>double</code>, <code>float</code>, <code>big_decimal</code></td>
<td><code>{&quot;type&quot;:&quot;number&quot;}</code></td></tr>
<tr class="b">
<td><code>boolean</code>, <code>yes_no</code></td>
<td><code>{&quot;type&quot;:&quot;boolean&quot;}</code></td></tr>
<tr class="a">
<td><code>timestamp</code>, <code>date</code></td>
<td><code>{&quot;type&quot;:&quot;string&quot;,&quot;format&quot;:&quot;date-time&quot;}</code></td></tr>
</table>
</section></section><section><a id="Test_Coverage"></a>
<h2>Test Coverage</h2>
<p>The <code>JpaSchemaGeneratorTest</code> class provides comprehensive tests:</p>
<ul>
<li><strong>Annotation introspection</strong> (12 tests): entity detection, ID fields,
column constraints, version, transient, enums, ManyToOne, OneToMany,
custom write-exclude annotations</li>
<li><strong>Schema generation</strong> (6 tests): read includes all fields, write excludes
server-managed fields, required array, relationship $refs, enum values,
generateBothSchemas</li>
<li><strong>HBM XML introspection</strong> (10 tests): parses CompanyBO and DepartmentBO
(70+ field production-grade entity), verifies type mapping, relationships,
collections, component flattening, nested column not-null</li>
</ul>
</section><section><a id="Relationship_.24ref_Convention"></a>
<h2>Relationship $ref Convention</h2>
<p>Relationships are emitted as <code>$ref</code> pointers using the OpenAPI
components convention:</p>
<pre>
// ManyToOne
{&quot;$ref&quot;: &quot;#/components/schemas/Department&quot;}
// OneToMany
{&quot;type&quot;: &quot;array&quot;, &quot;items&quot;: {&quot;$ref&quot;: &quot;#/components/schemas/LineItem&quot;}}
// Write schema uses &quot;Write&quot; suffix
{&quot;$ref&quot;: &quot;#/components/schemas/DepartmentWrite&quot;}
</pre>
<p>The caller is responsible for ensuring that referenced entity schemas
are also generated and added to the OpenAPI components section. The
batch generator (below) handles this automatically by processing all
HBM files in a directory at once.</p>
</section><section><a id="Batch_Generation_from_HBM_XML_Directory"></a>
<h2 id="batch_generation">Batch Generation from HBM XML Directory</h2>
<p><code>HbmBatchSchemaGenerator</code> is a standalone command-line tool
that scans a directory of <code>*.hbm.xml</code> files and produces a single
OpenAPI 3.0 JSON document containing read and write schemas for every
entity found. This is the recommended approach for projects with many
HBM-mapped entities.</p>
<section><a id="Command_Line"></a>
<h3>Command Line</h3>
<pre>
java -cp axis2-jpa-schema.jar:jackson-databind.jar:jackson-core.jar:jackson-annotations.jar:commons-logging.jar \
org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator \
src/main/resources \
resources-axis2/openapi-schemas.json
</pre>
</section><section><a id="Ant_Integration"></a>
<h3>Ant Integration</h3>
<p>The batch generator follows the same pattern as Hibernate Tools'
<code>hbm2java</code> and <code>hbm2ddl</code> tasks &#x2014; same HBM input
directory, different output artifact:</p>
<pre>
&lt;target name=&quot;openapi-schema&quot;
description=&quot;Generate OpenAPI schemas from HBM XML mappings&quot;&gt;
&lt;java classname=&quot;org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator&quot;
fork=&quot;true&quot; failonerror=&quot;true&quot;&gt;
&lt;classpath&gt;
&lt;fileset dir=&quot;lib&quot; includes=&quot;axis2-jpa-schema*.jar,jackson-*.jar,commons-logging*.jar&quot;/&gt;
&lt;/classpath&gt;
&lt;!-- Input: directory containing *.hbm.xml --&gt;
&lt;arg value=&quot;src/main/resources&quot;/&gt;
&lt;!-- Output: OpenAPI 3.0 JSON with all schemas --&gt;
&lt;arg value=&quot;build/openapi-schemas.json&quot;/&gt;
&lt;/java&gt;
&lt;/target&gt;
</pre>
</section><section><a id="Output"></a>
<h3>Output</h3>
<p>The tool produces a valid OpenAPI 3.0 document:</p>
<pre>
{
&quot;openapi&quot;: &quot;3.0.1&quot;,
&quot;info&quot;: {
&quot;title&quot;: &quot;Generated from 146 HBM XML mappings&quot;,
&quot;version&quot;: &quot;1.0.0&quot;
},
&quot;components&quot;: {
&quot;schemas&quot;: {
&quot;CompanyBO&quot;: { &quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: { ... } },
&quot;CompanyBOWrite&quot;: { &quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: { ... } },
&quot;DepartmentBO&quot;: { ... },
&quot;DepartmentBOWrite&quot;: { ... },
&quot;ProductBO&quot;: { ... },
&quot;ProductBOWrite&quot;: { ... }
}
}
}
</pre>
<p>Each entity produces two schemas:</p>
<ul>
<li><strong>EntityBO</strong> &#x2014; read schema (GET responses): all fields,
generated IDs marked <code>readOnly</code></li>
<li><strong>EntityBOWrite</strong> &#x2014; write schema (POST/PUT requests):
excludes generated IDs, version fields, and custom audit annotations</li>
</ul>
<p>Console output reports each entity processed:</p>
<pre>
OK CompanyBO.hbm.xml &#x2192; CompanyBO (12 fields, 2 relationships)
OK DepartmentBO.hbm.xml &#x2192; DepartmentBO (58 fields, 8 relationships)
OK ProductBO.hbm.xml &#x2192; ProductBO (8 fields, 1 relationships)
OK OrderBO.hbm.xml &#x2192; OrderBO (15 fields, 3 relationships)
SKIP HistoryData.hbm.xml (no entity found)
Generated 8 schemas (4 entities &#xd7; 2 [read + write]) from 5 HBM files
Output: /path/to/resources-axis2/openapi-schemas.json
</pre>
</section><section><a id="Build_Pipeline_Position"></a>
<h3>Build Pipeline Position</h3>
<p>The schema generator reads HBM XML files directly &#x2014; it does not
depend on compiled Java classes, a running database, or a Hibernate
<code>SessionFactory</code>. This means it can run:</p>
<ul>
<li><strong>Before <code>codegen</code></strong> &#x2014; HBM files are the
source of truth; the generator reads them before Java classes are
generated</li>
<li><strong>In CI</strong> &#x2014; no database connection required, so it
runs in any CI environment</li>
<li><strong>On schema change</strong> &#x2014; regenerate whenever an HBM
file changes; diff the output JSON to see exactly which fields or
relationships changed</li>
</ul>
<p>For projects that use Ant with Hibernate Tools, add
<code>openapi-schema</code> to your existing build pipeline after
code generation and before packaging. The schemas will reflect the
same entity definitions that the generated Java code and DDL use.</p>
</section></section>
</html> </main>
</div>
</div>
<hr/>
<footer>
<div class="container-fluid">
<div class="row-fluid">
<p>© 2004–2026
<a href="https://www.apache.org/">The Apache Software Foundation</a>
</p>
</div>
</div>
</footer>
</body>
</html>