In this chapter, you are going to learn how to reduce the amount of code you have to write by using Java's bean capability.
Writing Less Code With JAXB
So far, you have persisted object properties by writing code for each property that you want to serialize (store) or deserialize (load). Each property has to be defined in the object, and then you have to write an extra bit of code in the persister:
This is routine work, time-consuming for large systems, and prone to error. For instance, what if you mistype a key name? Use, say, supplier
when storing and Supplier
when loading? Java is case-sensitive, and it fails to find Supplier
in a properties map that has a supplier
key in it, and so leaves the component with a blank supplier field. 😱
What if you want to add, remove, or change a class property?
Then you also have to change the persister. For example, if you want to add quantity to the RobotPart, then you need to also add suitable lines to the persister:
What can we do to reduce all this extra code?
Wouldn't it be great if you could mark which properties needed to be stored, and then have some program do all this for you? There are indeed such programs! In this chapter, we will use a built-in library called JAXB (pronounced"Jaxby") to persist objects.
It, too, serializes and deserializes objects to store them as a file on a disk, or to share objects as messages sent between systems. However, it works out automatically how to store objects, based on the way you describe the properties in your object class.
How does it do that? 🤔
JAXB uses Java's ability to inspect itself. This is called reflection or introspection. In many languages, when the code is compiled, it loses the human information such as variable and method names. On the other hand, Java usually preserves this information so that one application can inspect the code of another application and discover what properties its objects have, what the methods are, what the data types are, etc.
All you need to do is mark up or annotate the properties you want to store so that the JAXB library can tell which properties to serialize, and which to leave.
Let's go through using the JAXB library to store a customer details person object. You could annotate the person class as follows, which tells JAXB to store the complete person object as a root, or top-level XML element (you can see what this XML file looks like later):
@XmlRootElement
public class Person {
@XmlElement(required=true)
public String name;
public String address;
public String email;
public String telephone;
}
Now you can pass this object to the JAXB library to store, and it automatically finds each property and stores it, without having to write the code for each property:
public class PersonJaxbPersister {
public void persistPerson(Person person) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
File file = new File("Store.xml");
jaxbMarshaller.marshal(person, file);
jaxbMarshaller.marshal(person, System.out);
}
}
This creates a JAXB context for the class you want to persist (person) and examines the class to work out what needs to be stored and how. The marshaller is created from this context; it does the persistence step by reading the object properties and writing them out to the local file Store.xml. In this case, it has its property JAXB_FORMATTED_OUTPUT set to true so that it formats the file contents nicely for humans to read.
If you add a main method like this to PersonJaxbPerister, and run it as an application:
public static void main(String[] args) throws IOException, JAXBException {
Person samjones = new Person();
samjones.name = "Sam Jones";
samjones.address = "12 Letsbe Avenue, Royston Vasey, MK2 3AU";
samjones.telephone = "+44 7700 900081";
samjones.email = "sam.jones@mymail.not.fr";
PersonJaxbPersister persister = new PersonJaxbPersister();
persister.persistPerson(samjones);
}
The JAXB code above creates a key-value paired XML file like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
<name>Sam Jones</name>
<Address>12 Letsbe Avenue, Royston Vasey, MK2 3AU</Address>
<email>sam.jones@mymail.not.fr</email>
<telephone>+44 7700 900081</telephone>
</person>
The keys (such as name and address) have been automatically read by JAXB from the person class definition - these are the class field names as given in the class. The values are read from the object instance property value (Sam Jones or 12 Letsbe Avenue).
If you add a new field to the person class, then the JAXB will automatically find it and marshall/unmarshall it, and you don't need to change anything else in the code.
Try It Out for Yourself: Use JAXB!
Using the above examples, see if you can annotate your RobotPart class, and create a JAXB persister class that will write a robot component to disk file and read it back in again.
Ready? Check out one solution in this screencast:
As you saw in the screencast, here is an example annotated simple RobotPart:
package com.openclassrooms.persistence.jaxb;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class RobotPart {
public String name;
public String description;
public String supplier;
@Override
public String toString() {
return "RobotComponent [name=" + name + ", description=" + description + ", supplier=" + supplier + "]";
}
}
Now, here is a RobotPart persister class:
public class RobotPartJaxbPersister {
private JAXBContext jaxbContext = null;
private Marshaller marshaller = null;
private Unmarshaller unmarshaller = null;
private File storefile = null;
public RobotPartJaxbPersister(String filename) throws JAXBException {
jaxbContext = JAXBContext.newInstance(RobotPart.class);
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
unmarshaller = jaxbContext.createUnmarshaller();
storefile = new File(filename);
}
public void serialisePart(RobotPart part2store) throws JAXBException {
marshaller.marshal(part2store, storefile);
}
public RobotPart deserialisePart() throws JAXBException {
return (RobotPart) unmarshaller.unmarshal(storefile);
}
public static void main(String[] args) throws IOException, JAXBException {
RobotPart sharklaseradapter = new RobotPart();
sharklaseradapter.name = "Shark Laser Adapter";
sharklaseradapter.description = "Collar and fittings for shark-mounted laser. Shark not included";
sharklaseradapter.supplier = "Dr NoNo";
RobotPartJaxbPersister persister = new RobotPartJaxbPersister("Store.jaxb");
persister.serialisePart(sharklaseradapter);
RobotPart storedadapter = persister.deserialisePart();
if (!sharklaseradapter.toString().equals(storedadapter.toString())) throw new RuntimeException("Store failed! "+sharklaseradapter+" vs "+storedadapter);
}
}
Let's break this down:
The constructor sets up the JAXB context.
It used that to create a marshaller and an unmarshaller to persist objects in a file in the local directory with the given filename.
This means that they only needed to be instantiated once when the persister is constructed, instead of every time you store or load a RobotPart.
You can see that the serialize and deserialize methods are very short, essentially just calling the JAXB library to do its work.
If you run this as an application, you would get a XML file like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<robotPart>
<name>Shark Laser Adapter</name>
<description>Collar and fittings for shark-mounted laser. Shark not included</description>
<supplier>Dr NoNo</supplier>
</robotPart>
There are many options that can be added to the JAXB annotations to help store things effectively. You might want to experiment with storing different data types such as numbers and dates.
Try It Out for Yourself: Storing Lists!
JAXB can store lists as well as single objects, and so you could use it to store the lists of customers or the catalog of robot parts. Try this out! As an exercise, try creating a collection of RobotPart objects, annotate it suitably, and send it to the JAXB persister and see what files you get.
An example implementation is below.
To store a list of parts, you can create a catalog object like this:
@XmlRootElement
public class RobotPartCatalogue {
public List<RobotPart> parts = new ArrayList<RobotPart>();
}
This is just a wrapper class that contains a list of parts. The persister for it looks the same as for persisting a single RobotPart, but taking RobotPartCatalogue
instead:
public class RobotPartCatalogueJaxbPersister {
private JAXBContext jaxbContext = null;
private Marshaller marshaller = null;
private Unmarshaller unmarshaller = null;
private File storefile = null;
public RobotPartCatalogueJaxbPersister(String filename) throws JAXBException {
jaxbContext = JAXBContext.newInstance(RobotPartCatalogue.class);
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
unmarshaller = jaxbContext.createUnmarshaller();
storefile = new File(filename);
}
public void serialiseCatalogue(RobotPartCatalogue catalogue) throws JAXBException {
marshaller.marshal(catalogue, storefile);
marshaller.marshal(catalogue, System.out);
}
public RobotPartCatalogue deserialiseCatalogue() throws JAXBException {
return (RobotPartCatalogue) unmarshaller.unmarshal(storefile);
}
public static void main(String[] args) throws IOException, JAXBException {
RobotPart sharklaseradapter = new RobotPart();
sharklaseradapter.name = "Shark Laser Adapter";
sharklaseradapter.description = "Collar and fittings for shark-mounted laser. Shark not included";
sharklaseradapter.supplier = "Dr NoNo";
RobotPart buttonCover = new RobotPart();
buttonCover.name = "Detonator Button Cover";
buttonCover.description = "Safety cover to prevent accidentally pressing the self-destruct button";
buttonCover.supplier = "Glass Tumbler Co";
RobotPartCatalogue catalogue = new RobotPartCatalogue();
catalogue.parts.add(sharklaseradapter);
catalogue.parts.add(buttonCover);
RobotPartCatalogueJaxbPersister persister = new RobotPartCatalogueJaxbPersister("Catalogue.jaxb");
persister.serialiseCatalogue(catalogue);
}
}
If you have used Generics, you might see that you can reduce the number of persister classes being written by using Java's Generic feature.
Identifying Advantages and Disadvantages with JAXB
In this chapter, we used the built-in JAXB library to persist objects and lists of objects for the robot parts shop. We annotated the classes we wanted to store, and then wrote some persister classes that handled setting up the JAXB library. This sharply reduced the amount of boilerplate code - that is, code that follows a routine, fixed format, but still has to be written by hand to have the capability. All that code to copy properties from our data object - the person or RobotPart - to a persisting object has gone.
However, we have broken a principle of programming by contaminating our person class with details about how it will be stored. That is if we use a different mechanism to store our person class - one that has different annotations - we need to add those two and hope they are not incompatible. Therefore, we might not be able to reuse our person class in two different applications that each use different persisters. 🙁
What else do we need our persisters to be able to do?
We have stored only single objects in files; even the list is one root object that contains a single type of object, and we read and write the whole set as one operation. When you are storing many in large datasets, you need ways to find individual entries (Sam Jones’s telephone number) and ways to search for entries (All the people I know in Berlin), and you want to do this quickly in large datasets. You need to control who has access to which data when sharing it. You must cope with what happens when several people want to update an entry at the same time; if you make a change and save it, and I do the same, then my changes overwrite yours, and yours are lost next time you load the data.
That sounds like a lot of work to do! Is there a technology that has already solved much of this?
Yes, yes there is: connecting our application to a relational database. And that's exactly what we'll do in the next part!
Let's Recap!
Use the built-in JAXB library to persist objects - and even lists of objects - without having to write lots of boilerplate code.
Use annotations and reflection or inspection to examine the persisted objects and work out how to marshall and unmarshall them.
In the next part, we'll look at how to store large datasets in a relational database. Before moving on though, make sure you've understood the basics of serializing and deserializing via the end-of-part quiz! 😀