Creating Java JPA entities with an XML field

by Liger Learn
1. Introduction
In this article we explore how you can store an XML field in your JPA entities.
We use a number of technologies:
- Spring Data
- Hibernate ORM
- JAXB
- PostgreSQL
As always, all code used within the article is available on GitHub: here.
If you would like to see how you can store a JSON field in a JPA entity check out the tutorial: here.
2. Our Goal
Our goal is to be able to perform create, read, update and delete (CRUD) operations on a JPA entity that contains a field which uses PostgreSQL’s native XML column type. To illustrate how to achieve this we will create:
- a simple domain model with an XML field in an entity
- a spring data JPA repository for our entity
- a Spring MVC controller which exposes endpoints illustrating CRUD operations
Let’s get started!
3. Our domain model
For our domain model we have a Person
and an Address
:
Person
-
- A person contains an ID, first name, surname and an
Address
.
- A person contains an ID, first name, surname and an
Address
- An address contains a door number, road name, and postal code.
- The address is stored in a column in the database which has been defined to use the native
xml
column type.
3.1 The Address
class
We define the Address
class as an XML complex type:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * A simple representation of an "address" as an xml complex type. */ @Setter @Getter @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "Address") public class Address implements Serializable { private final static long serialVersionUID = 7702L; @XmlElement(name = "DoorNumber", required = true) protected int doorNumber; @XmlElement(name = "RoadName", required = true) protected String roadName; @XmlElement(name = "PostalCode", required = true) protected String postalCode; } |
3.2 The Person
JPA entity
We define a person as a JPA entity. The entity definition is somewhat complex, so let’s go through it piece by piece. First, lets take a look at the field definitions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Entity public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) @Getter @Setter private long id; @Column(name = "first_name") @Getter @Setter private String firstName; @Column(name = "surname") @Getter @Setter private String surname; @Getter transient private Address address; @Type(type = "com.ligerlearn.example.jpa.hibernate_types.SQLXMLType") @Column(name = "address", columnDefinition = "xml") private String addressAsXmlString; // .... } |
We have an ID which is auto-generated by the database, and we also have the person’s first_name
, surname
and address
:
- The
id
,firstName
, andsurname
are simple String fields. - The
Address
is where it gets interesting:- We have a
transient
field which is of theAddress
type we saw earlier (with XML annotations).- Transient here means it is not persisted to the database.
- We also have another field called
addressAsXmlString
which is of typeString
:- This field represents the XML string version of the
Address
POJO.
- This field represents the XML string version of the
- Since we are using PostgreSQL’s native XML type as the type of the column we add
columnDefinition = "xml".
- We also use the
@org.hibernate.annotations.Type
annotation and provide a fully qualified
class name as the type.- You can see the full source code for the
SQLXMLType
class here. - We won’t get into the details about the
SQLXMLType
class’ code in this article but you can think of it as providing the necessary functionality for Hibernate to work with the XML field.
- You can see the full source code for the
- We have a
3.2.1 XML Infrastructure Prerequisites
The addressAsXmlString
field in the Person
entity does not have a setter method and is private
so its state cannot be mutated outside of this class (unless you are using reflection trickery):
- It is our responsibility to make sure that it always accurately represents the contents of the
Address
POJO as an XML String. - With that in mind we need to define the setter for the
Address
POJO so that it also sets theaddressAsXmlString
value. - But before doing that we need to have the XML infrastructure in place to be able to marshal/unmarshall between an XML string/POJO.
- For this we define a class,
ModelJaxBContext
which is the XML JaxB context for our XML model classes (which in this case is just theAddress
). - The full definition of the class is available here but for the purpose of this article will suffice to simply show the public interface:
- For this we define a class,
1 2 3 4 5 6 |
// Returns the singleton instance of the ModelJaxBContext. public static ModelJaxBContext getInstance(); // Marshalls a POJO of type T to an XML String. public <T> String marshallPojoToXmlString(Class<T> pojoClass, T pojo); // Unmarshalls an XML String to a POJO of type T. public <T> T unmarshallXmlDocumentToPojo(Class<T> clazz, String xmlString); |
3.2.2 Person entity continued…
Now we define our setter in the Person
entity class for the Address
as the following:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void setAddress(Address address) { // Retrieve the model's JAXB context. ModelJaxBContext jaxbContext = ModelJaxBContext.getInstance(); // We need to convert the address to an XML document string and set the string representation. this.addressAsXmlString = jaxbContext.marshallPojoToXmlString(Address.class, address); log.info("Set the address xml string representation to: {}", this.addressAsXmlString); // Set the POJO version, too. this.address = address; } |
This way when we set the address POJO value, we are always updating the string representation as well.
We now have one final problem to solve – since the Address
POJO is defined as transient (transient private Address address;
) when the entity is loaded, then the address
field will be set to null
:
- In order to avoid this we add a
@PostLoad
annotated method which sets the value of theAddress
when the entity is loaded:
1 2 3 4 5 6 7 8 9 10 |
@PostLoad private void postLoadFunction(){ log.info("PostLoad method called for JPA entity"); log.info("Attempting to unmarshall XML string to POJO: {}", addressAsXmlString); // The Address POJO is not persisted so when the entity is loaded it will // be initialised with null. However, the XML string is persisted - so we load // the POJO from the string (and avoid future NPEs). this.address = ModelJaxBContext.getInstance().unmarshallXmlDocumentToPojo(Address.class, addressAsXmlString); } |
4. CRUD operations
We now want to illustrate how we can perform CRUD operations on the entity and have the address persisted as an XML field in the PostgreSQL database. We do this in two steps:
- Creating a Spring Data JPA repository for the entity.
- Creating a
@RestController
exposing CRUD operations on the entity as HTTP endpoints.
4.1 The @Repository
Spring Data JPA repositories allow you to define a type signature and have a repository proxy which implements common CRUD operations on your entity injectable in other your components.
Let’s define our @Repository
:
1 2 |
@Repository public interface PersonRepository extends JpaRepository<Person, Long> {} |
Here we are stating our repository operates on the Person
entity and ID field is of type Long
.
4.2 The @RestController
4.2.1 Ensure the repository is injected…
Now let’s create a @RestController
which exposes CRUD functionality on our entity through HTTP Rest endpoints. We’ll slowly build up the code for the class as we work through more functionality (the full code can be found here):
1 2 3 4 5 6 7 8 9 10 11 12 |
@RestController public class LigerLearnJpaWithXmlFieldExampleController { private final PersonRepository personRepository; @Autowired public LigerLearnJpaWithXmlFieldExampleController(PersonRepository personRepository) { this.personRepository = personRepository; } // ... } |
Here we are using constructor injection to ensure that the controller will have an instance of the repository we defined in the previous subsection.
4.2.2 Create our PersonDTO
We want the CRUD functionality to return a Person
representation so we define a PersonDTO
class which we will return as a response to requests:
1 2 3 4 5 6 7 8 9 10 |
/** * Data transfer object for a Person entity. */ @Data @Builder public class PersonDTO { private Long id; private String firstName; private String surname; private Address address; } |
There are a number of reasons why we use a data transfer object (DTO) rather than the Person
@Entity
directly:
- It allows flexibility in that the representation can differ from the actual entity if need be (in this case it does not).
- We know definitively what object will be serialised in the HTTP response.
- If we are responding with the
Person
@Entity
rather than thePersonDTO
the response serialiser may fail trying to serialise an object which is actually a Hibernate proxy (or something else) under the hood.- By using a DTO – what you see is what you get.
- If we are responding with the
4.2.3 Create our rest endpoints
With the PersonDTO
defined let’s create our REST endpoints:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/** * Convenient method to convert a person to a person DTO. */ private PersonDTO personToPersonDTO(Person person) { return PersonDTO.builder() .id(person.getId()) .firstName(person.getFirstName()) .surname(person.getSurname()) .address(person.getAddress()) .build(); } @GetMapping("/{id}") public PersonDTO getPerson(@PathVariable("id") Long id) { // Retrieve an existing Person. Person person = personRepository.getOne(id); // Return the DTO instance. return personToPersonDTO(person); } @PostMapping("/") public PersonDTO createPerson(@RequestBody PersonDTO personDTO) { // Create a new Person. Person person = new Person(); person.setFirstName(personDTO.getFirstName()); person.setSurname(personDTO.getSurname()); person.setAddress(personDTO.getAddress()); Person savedPerson = personRepository.save(person); return personToPersonDTO(savedPerson); } @PutMapping("/{id}") public PersonDTO updatePerson( @PathVariable("id") Long id, @RequestBody PersonDTO personDTO) { // Update an existing Person. Person person = personRepository.getOne(id); person.setFirstName(personDTO.getFirstName()); person.setSurname(personDTO.getSurname()); person.setAddress(personDTO.getAddress()); Person updatedPerson = personRepository.save(person); return personToPersonDTO(updatedPerson); } @DeleteMapping("/{id}") public void deletePerson(@PathVariable("id") Long id) { log.info("Deleting: {}", id); // Delete an existing person. personRepository.deleteById(id); } |
Our rest endpoints are somewhat self-explanatory:
- the
@GetMapping
retrieves an existing entity - the
@PostMapping
creates a new entity - the
@PutMapping
updates an existing entity - the
@DeleteMapping
deletes the entity with the given ID.
5. Testing the @RestController
If you run the associated code (available on github here) as a spring boot application (run the :bootRun
Gradle task) you can navigate to http://localhost:8080/swagger-ui.html
and have access to a Swagger UI GUI to make mock requests to the controller and see how everything works out:
For the purposes of this article I will use CURL rather than the UI to test the controller endpoints in the following sections.
5.1 Creating an entity
We send a POST request with the following request body (-d = data flag):
1 2 |
curl -X POST "http://localhost:8080/" -H "accept: */*" -H "Content-Type: application/json" \ -d '{"firstName":"John","surname":"Smith","address":{"doorNumber":1500,"roadName":"Liger Learn Road","postalCode":"AW4POC"}}' |
And receive a response:
1 |
{"id":6,"firstName":"John","surname":"Smith","address":{"doorNumber":1500,"roadName":"Liger Learn Road","postalCode":"AW4POC"}} |
From the response we can see that the newly created Person has been given an ID of 6. Let’s inspect the PostgreSQL database using PgAdmin to see how this is being stored there:
We can see from the screenshot of the query result that the address has successfully been stored as XML within the database.
5.2 Updating an entity
Let’s update our newly created entry’s address:
1 2 |
curl -X PUT "http://localhost:8080/6" -H "accept: */*" -H "Content-Type: application/json" \ -d '{"firstName":"John","surname":"Smith","address":{"doorNumber":123,"roadName":"Updated Liger Learn Road","postalCode":"BE290Q"}}' |
If we take a look at the database again we can see the XML for the row with ID 6 has been updated:
6. Conclusion
In this article we explored how we can store an XML field in a JPA entity. We covered a lot of ground including creating the necessary JAXB classes to allow for you to marshal/unmarshall between XML strings and POJOs. Finally, we created a @RestController
which exposed CRUD endpoints for the Person
resource and we saw how the data is persisted as XML in the underlying database.
Recommended Posts

Creating Java JPA entities with a JSON field
June 7, 2020
Church-Rosser theorems and the lambda (λ) calculus
October 19, 2023
Lambda calculus (λ) reduction orders and normal form
October 19, 2023