iText pdf library
Website search

3. A simple invoice database

Database diagram

Figure 3.1 shows the minimal set of tables and fields that we'll use for the examples in the next couple of chapters.

Figure 3.1: Simple invoice database schema

Figure 3.1: Simple invoice database schema

We'll work with only four tables:

  • INVOICE: every record contains an id and an invoice date. The customer id refers to the CUSTOMER table.

  • CUSTOMER: every record contains an id, a first name, last name, street, postal code, city and country id.

  • ITEM: an invoice will involve one or more items. The invoice id refers to the INVOICE table; item is a sequential number for each separate invoice line. Product id refers to the PRODUCT table; the quantity field tells you how many products are being purchased.

  • PRODUCT: every record contains an id, a name, a price and a VAT percentage.

In the real world, CRM databases contain much more information, but for the sake of this tutorial, we want to keep the complexity as low as possible.

  • We won't create a country table,

  • We'll assume that the customers are individuals without a company id,

  • We won't store the address of the seller in our database,

  • And so on.

This is not a tutorial on how to build a CRM system. We just need some data for our examples.

We will access our database using JDBC, and to make things simple, we'll work with a series of Plain Old Java Objects (POJO).

Creating database POJOs

We start by creating the classes Invoice, Customer, Item, and Product. These are our POJOs: each class corresponds with a table in our database and consists of a series of member-variables (one for each field) and the corresponding getters and setters.

For instance: the body of the Product class looks like this:

// member-variables
protected int id;
protected String name;
protected double price;
protected double vat;
// getters and setters
public int getId() {
    return id;
public void setId(int id) { = id;
public String getName() {
    return name;
public void setName(String name) { = name;
public double getPrice() {
    return price;
public void setPrice(double price) {
    this.price = price;
public double getVat() {
    return vat;
public void setVat(double vat) {
    this.vat = vat;

We'll also add a toString() method that renders this content as a String value:

public String toString() {
    StringBuilder sb = new StringBuilder();
        .append(price).append("\u20ac\tvat ").append(vat).append("%");
    return sb.toString();

Once we have created such a class for every table, we create a series of queries that will return Invoice objects. An Invoice object contains a Customer object and a list of Item objects, each of which refers to a Product object.

Creating a POJO factory

When working with hsqldb, all the data is stored in a .script file. In our case, the file is named invoices.script. We'll write a PojoFactory class that connects to this database and that initializes a series of prepared statements:

protected Connection connection;
protected HashMap customerCache
    = new HashMap();
protected HashMap productCache
    = new HashMap();
protected PreparedStatement getCustomer;
protected PreparedStatement getProduct;
protected PreparedStatement getItems;
// constructor
private PojoFactory() throws ClassNotFoundException, SQLException {
    connection = DriverManager.getConnection(
        "jdbc:hsqldb:resources/zugferd/db/invoices", "SA", "");
    getCustomer = connection.prepareStatement(
        "SELECT * FROM Customer WHERE id = ?");
    getProduct = connection.prepareStatement(
        "SELECT * FROM Product WHERE id = ?");
    getItems = connection.prepareStatement(
        "SELECT * FROM Item WHERE invoiceid = ?");

Let's take a closer look at the member-variables in the PojoFactory class:

  • In line 1, we have a connection object that is an object of type java.sql.Connection. We load the hsqldb database driver in line 11 and create the connection in lines 12 and 13. In our case, the invoices.script is stored in the folder db, which is a subdirectory of the folder resources which is in turn a subdirectory of the working directory of our Java Virtual Machine (JVM). The username and password of that database are "SA" and "".

  • In lines 2-3 and 4-5, we create a cache for Customer objects and a cache for Product objects by storing an integer (the id of a record) and the object containing the data of a record in HashMap objects. That way, we won't always have to execute a query on the database when a Customer or a Product is needed more than once; we can just fetch it from the HashMap that caches these objects. Obviously, this will only work if we never change any record in our database. Please keep in mind that this is just a demo database and some demo code. In the real world, your database and database access may be completely different.

  • In lines 6, 7, and 8, we have three prepared statements. These are defined in lines 14-15, 16-17, and 18-19. As you can see, these are simple SELECT statements that return all the fields from the records in the tables CUSTOMER, PRODUCT, and ITEM based in an id.

You can get all the invoices at once, using the getInvoices() method. This method selects all the fields from all the records in the INVOICE table:

public List getInvoices() throws SQLException {
    List invoices = new ArrayList();
    Statement stm = connection.createStatement();
    ResultSet rs = stm.executeQuery("SELECT * FROM Invoice");
    while ( {
    return invoices;

The getInvoice() method will perform several sub-queries:

public Invoice getInvoice(ResultSet rs) throws SQLException {
    Invoice invoice = new Invoice();
    List items = getItems(rs.getInt("id"));
    double total = 0;
    for (Item item : items)
        total += item.getCost();
    return invoice;

You see that we even calculate the total sum of the invoice. This is a value that you'd usually store in your database redundantly, to avoid that the total invoice price of an old invoice changes when you introduce new prices for products mentioned on that old invoice. Once again: we have kept our database as minimal as possible.

The Customer object that corresponds with this invoice, is obtained with the getCustomer() method:

    public Customer getCustomer(int id) throws SQLException {
        if (customerCache.containsKey(id))
            return customerCache.get(id);
        getCustomer.setInt(1, id);
        ResultSet rs = getCustomer.executeQuery();
        if ( {
            Customer customer = new Customer();
            customerCache.put(id, customer);
            return customer;
        return null;

If the id is found in the cache, we return the corresponding Customer object. If not, we perform a query using one of our prepared statements, and we store the resulting Customer instance in the cache before returning it.

When populating the Invoice object, we get a List of Item objects using the getItems() method:

public List getItems(int invoiceid) throws SQLException {
    List items = new ArrayList();
    getItems.setInt(1, invoiceid);
    ResultSet rs = getItems.executeQuery();
    while ( {
    return items;

This method calls the getItem() method for every invoice line that belongs to a specific Invoice:

public Item getItem(ResultSet rs) throws SQLException {
    Item item = new Item();
    Product product = getProduct(rs.getInt("ProductId"));
    item.setCost(item.getQuantity() * product.getPrice());
    return item;

The getItem() method also fetches the corresponding Product:

public Product getProduct(int id) throws SQLException {
    if (productCache.containsKey(id))
        return productCache.get(id);
    getProduct.setInt(1, id);
    ResultSet rs = getProduct.executeQuery();
    if ( {
        Product product = new Product();
        productCache.put(id, product);
        return product;
    return null;

Now we have everything we need to retrieve all the invoice data that is stored in our very simple invoice database.

Testing the database

Before we use this database to create ZUGFeRD XMLs and invoices, let's run the DatabaseTest example to test the database:

public static void main(String[] args) throws SQLException {
    PojoFactory factory = PojoFactory.getInstance();
    List invoices = factory.getInvoices();
    for (Invoice invoice : invoices)

We use the PojoFactory to get a List of Invoice objects and we write the content of such an object to the System.out. The result will be a sequence of text snippets (one for each invoice) that look like this:

Invoice id: 4 Date: 2015-04-01 Total cost: 1507.0€
Customer: 30
    First Name: Bill
    Last Name: Sommer
    Street: 362 - 20th Ave.
    City: BE 9000 Ghent
  #0  (28)   Running jersey      8.0€   vat 21.0%  Quantity: 9  Cost: 72.0€
  #1  (35)   Golf polo           8.0€   vat 21.0%  Quantity: 1  Cost: 8.0€
  #2  (41)   Threadmill          600.0€ vat 21.0%  Quantity: 2  Cost: 1200.0€
  #3  (23)   Pro steel dartboard 25.0€  vat 21.0%  Quantity: 2  Cost: 50.0€
  #4  (9)    My First Cookbook   17.0€  vat 6.0%   Quantity: 1  Cost: 17.0€
  #5  (37)   Golf kit            80.0€  vat 21.0%  Quantity: 2  Cost: 160.0€

This already looks more or less like an invoice, doesn't it? Now let's find out how all this data fits into the ZUGFeRD data model.

Ready to use iText?

Try our iText 7 Library and add-ons FREE for 30 days. Test your proof of concept, and see if our solution is right for you.

Get my FREE trial

Still have questions? 

We're happy to answer your questions. Reach out to us and we'll get back to you shortly.

Contact us
Stay updated

Join 11,000+ subscribers and become an iText PDF expert by staying up to date with our new products, updates, tips, technical solutions and happenings.

Subscribe Now