Java2JPA: automatically create JPA Mappings for a Java domain model
A project I worked on involved creating JPA mappings for different Java POJO domain models.
Since I could not find a tool to automatically create JPA mappings for these models, I created a Java2JPA tool myself.
This post introduces this tool and describes how to use it.
Introduction
The existing Java POJO domain models I needed to create jpa mappings for contained about 20-50 classes each.
Creating these mappings manually would have been a lot of work, so I looked into creating these automatically.
I did not expect to find a complete out-of-the-box solution, but I expected to find something that would enable me to avoid all the repetitive work.
However, I did not find any Java2JPA tool, so I wrote a simple mapping generator myself.
The generator I made only generates JPA Xml mappings and does not annotate existing classes with JPA annotations.
However, someone could implement another JpaMappingRenderer implementation that writes out JPA annotated java files instead of JPA mapping xml files.
Generation of the mappings is not entirely automatable
There is probably no known Java2JPA tool because the mapping process is not entirely automatable.
Whereas popular ddl2jpa and jpa2java tools exist for most JPA Providers(Hibernate Tools for Hibernate, Eclipselink JPA Extensions,..), that provide complete “generate with the press of a button” output, java2jpa tools don’t exist probably because using them would usually require some manual work.
Human decisions need to be made while mapping.
For example, it is not possible to determine the field or combination of fields that represent the database id of a class by plain introspection, and it is also not possible to determine whether an abstract class should be mapped as a “mapped superclass” or as an entity that already has its own table.
The java2jpa tool code
The code is hosted at Github(java2jpa). The code can be downloaded as a zip. You can build the code with Maven.
The code is build around a few core classes: Java2JpaMappingGenerator, RenderJpaMappingForClassStrategy and JpaMappingRenderer.
The Java2JpaMappingGenerator is the class responsible for generating the mappings and has two important fields: a JpaMappingRenderer and a RenderJpaMappingForClassStrategy.
The RenderJpaMappingForClassStrategy is responsible for making the rendering decisions – e.g. which field will be mapped as id, how class inheritance will be mapped,.. – and the JpaMappingRenderer is responsible for rendering the actual files based on the rendering decisions made.
To use the tool efficiently more often than not a custom implementation of RenderJpaMappingForClassStrategy is required.
Basic usage of the code
In this basic usage example we will generate a jpa mapping for the following two pojo classes in the com.test.model.simple package: Student and Course.
In the unit tests of the source code an example with a more extended sample model is available.
public class Course { private Long id; private String title; private List<Student> students = new ArrayList<Student>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; } } public class Student { private int studentId; private String name; private Date birthDate; private Course course; public int getStudentId() { return studentId; } public void setStudentId(int studentId) { this.studentId = studentId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public Course getCourse() { return course; } public void setCourse(Course course) { this.course = course; } }
Make sure the java2jpa jar is available on the Java classpath.
Then execute the following code:
Java2JpaMappingGenerator java2JpaMappingGenerator = new Java2JpaMappingGenerator(); java2JpaMappingGenerator.setRenderJpaMappingForClassStrategy( new RenderJpaMappingForClassStrategyDefaultImpl()); JpaMappingRendererDefaultImpl jpaMappingRenderer = new JpaMappingRendererDefaultImpl("target/META-INF/orm.xml"); java2JpaMappingGenerator.setJpaMappingRenderer(jpaMappingRenderer); java2JpaMappingGenerator.generateJpaMappingsForPackages("com.test.model.simple"); jpaMappingRenderer.createMappedFiles();
This will generate the following orm.xml in the target/META-INF folder:
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings version="2.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"> <persistence-unit-metadata> <xml-mapping-metadata-complete/> <persistence-unit-defaults> <access>PROPERTY</access> </persistence-unit-defaults> </persistence-unit-metadata> <entity class="com.test.model.simple.Course"> <table name="COURSE"/> <attributes> <id name="id"> <generated-value strategy="AUTO"/> </id> <one-to-many mapped-by="course" name="students"/> </attributes> </entity> <entity class="com.test.model.simple.Student"> <table name="STUDENT"/> <attributes> <id name="studentId"> <generated-value strategy="AUTO"/> </id> <many-to-one fetch="LAZY" name="course"> <join-column name="COURSE_ID"/> </many-to-one> </attributes> </entity> </entity-mappings>
In order to run a JpaMappingTesterOnHsqlImpl test, you will need to have a valid persistence.xml on your classpath(pointing to the generated orm.xml on the classpath – by default META-INF/orm.xml – to test).
JpaMappingTester jpaMappingTester=new JpaMappingTesterOnHsqlImpl("TESTDB"); jpaMappingTester.test();
For a working example of using this Tester, check the unit test JpaMappingTesterOnHsqlImplTest in the source code.
Implementing your own RenderJpaMappingForClassStrategy
For some models, RenderJpaMappingForClassStrategyDefaultImpl will work out of the box.
However, for most models a different implementation will be necessary.
The following table lists the assumptions this default implementation makes and mentions some override scenario’s.
Method | Default implementation |
---|---|
public boolean classNeedsMapping(Class clazz) Returns whether a class needs a jpa mapping. |
Class needs mapping if clazz is a top level class, not an interface and not an enum. Typical override scenario: In some models, more classes need to be excluded. The default implementation assumes all the classes in the packages to map need jpa mappings. |
public ClassRenderType getRenderTypeFor(Class clazz) Returns how a class should be mapped: MAPPEDSUPERCLASS, ENTITY or EMBEDDABLE. |
Abstract classes are mapped as MAPPEDSUPERCLASS. Classes for which the getIdFieldForClass method returns a value are mapped as ENTITY. Other classes are mapped as EMBEDDABLE. Typical override scenario: If you want to map the subclasses of an abstract class to one table, you want this method to return ENTITY for the abstract superclass. |
public InheritanceMappingType getInheritanceMappingTypeForClass(Class clazz, Collection<Class> allClassesToMap) Returns how the inheritance of the class should be mapped. |
Maps ENTITY classes that are part of an inheritance hierarchy to a single table. Typical override scenario: If you want to use another inheritance mapping type, you will need to override the default implementation. However, in order to do so, the code of the tool itself will need to be adapted too. |
public CollectionRenderType getCollectionRenderTypeForField(Field field) Returns how a collection field should be mapped. |
Returns null if ParameterizedType of Collection(=”other class”) is not known. Returns ONETOMANY if field of type “class being rendered” is present on “other class”. Returns SIMPLE if field of type “class being rendered” is not present on “other class” and other class is either a simple class or an enum. Returns MANYTOMANY if field of type “class being rendered” is not present on “other class” and other class is not a simple class and not an enum. Typical override scenario: Sometimes you want to add a MANYTOMANY mapping instead of a ONETOMANY mapping, even if field of type “class being rendered” is present on “other class”. |
public Field getIdFieldForClass(Class clazz) Returns the field that represents the id for the given class. |
If clazz has a field with name “id” return that field. Otherwise, if class has field with name “<simpleClassName>+Id” return that field. Typical override scenario: If the field that corresponds with the database id does not match the “id” or “<simpleClassName>+Id” naming, you will need to override this method. |
public boolean fieldNeedsManyToOneMapping(Field field) Returns whether a certain field requires a many-to-one mapping. |
Field needs mapping if type of field is a top level class, not a simple class, not a standard class, not an array, enum, and field does not have a transient or static modifier. Typical override scenario: Sometimes, you will need to exclude more fields, such as log instance variables that do not need to be persisted. |
Limitations of the tool
The xml mappings generated by the current implementation are far from complete.
Column level details are almost never specified in the generated mappings, only one inheritance mapping strategy is considered and collection cascades need to be added manually.
The tool only creates an initial mapping for each class, avoiding most of the repetitive work involved.
Stack Overflow
A while ago I asked on Stackoverflow whether a java2jpa tool already existed.
I answered my own answer now by pointing people to my own tool.
If the tool is of use to you, upvotes on that answer are appreciated.
Would be a nice community reward to get some reputation there out of contributing this.