Posted on Leave a comment

Using SecondaryTable For Flexible Database Design (With example)

Let’s consider an example. You have been working on a website selling phones since 2000. In early 2000, there wasn’t any smartphone so you may created an object Phone like this:

@Table(name = "PHONE")
@Entity
@Data
public class Phone {
    @Column(name = "PHONE_ID", nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String model;
    private Float price;
    private Float screenSize;
    private Float weight;
}

I’m using lombok (in case you find @Data strange).

So, back in 2000s, phones were simple like that. However, with the appearance of smartphone. Things get complicated.

To accommodate to the new era, you create a new entity like this:

package com.datmt.jpa2.entity;

import javax.persistence.Entity;

@Entity
public class SmartPhone extends Phone {
    private Integer cpuCoresCount;
    private Boolean hasSDCard;
    private Boolean hasFingerprintUnlock;
    private Float rearCameraResolution;
    private Float frontCameraResolution;
}

All is good.

However, problem arises when you check the database table when inserting a new smartphone.

For example, I inserted two phones, one smart, one isn’t to the table using this code:

 @Override
    public void run(String... args) throws Exception {
        Phone simplePhone = new Phone();
        simplePhone.setModel("NOKIA N8");
        simplePhone.setScreenSize(3.2f);
        simplePhone.setWeight(0.1f);
        simplePhone.setPrice(35.0f);


        SmartPhone smartPhone = new SmartPhone();
        smartPhone.setModel("iPhone 12");
        smartPhone.setPrice(999f);
        smartPhone.setScreenSize(5.7f);
        smartPhone.setWeight(0.5f);
        smartPhone.setCpuCoresCount(8);
        smartPhone.setFrontCameraResolution(20.1f);
        smartPhone.setRearCameraResolution(40.1f);
        smartPhone.setHasSDCard(false);
        smartPhone.setHasFingerprintUnlock(false);


        phoneRepository.save(simplePhone);
        smartPhoneRepository.save(smartPhone);
    }

This is what we have in the database:

many null columns in database

As you can see, a lot of NULL columns.

Who knows what phones will have in the future. Maybe they will have some additional 100 fields. Adding more columns to database isn’t a good strategy.

SecondaryTable to the rescue

There is a very cool feature in JPA that let you put all the extra fields/attributes that are available only in subclasses to a separate table. Thus, you don’t have NULL column and still can represent the relationship between entities.

Let’s modify SmartPhone entity so it’ll use a separate table to store its new attributes.

@Entity
@Data
@SecondaryTable(name = "smart_phone", pkJoinColumns=@PrimaryKeyJoinColumn(name="PHONE_ID"))
public class SmartPhone extends Phone {

    @Column(table = "smart_phone")
    private Integer cpuCoresCount;

    @Column(table = "smart_phone")
    private Boolean hasSDCard;

    @Column(table = "smart_phone")
    private Boolean hasFingerprintUnlock;

    @Column(table = "smart_phone")
    private Float rearCameraResolution;

    @Column(table = "smart_phone")
    private Float frontCameraResolution;
}

Now, if I run the application and check the database, here is the result:

New database reflects secondary table

As you can see, no more null columns!

Let’s get back and discuss the changes.

From the new SmartPhone snippet above, you can see that I’ve added @SecondaryTable annotation specifying some fields of this class will be store on a different table (other than the table of Phone) and the join column is PHONE_ID.

Next, in the fields, if I want to move a particular field to the new table, I simply add a @Column annotation with table point to the new table name.

That’s all it takes to remove the null columns.

I don’t know about you but this make me happy.

Multiple @SecondaryTable

You may wonder if you want to link to another table, would it be @TertiaryTable and so on?

The answer is no. You still use @SecondaryTable to link to as many tables as you want.

So, for example, in the future phones will have brains. Since brains are so divine, we decide to store it to a separate table called brain_phone.

SmartPhone entity now look like this:

@SecondaryTable(name = "smart_phone", pkJoinColumns=@PrimaryKeyJoinColumn(name="PHONE_ID"))
@SecondaryTable(name = "brain_phone", pkJoinColumns=@PrimaryKeyJoinColumn(name="PHONE_ID"))
public class SmartPhone extends Phone {

//...

    @Column(table = "brain_phone")
    private Float brainCount;

}

If we insert a new smartphone with brain count, we’ll get data populated to brain_phone table:

Using more than one secondarytable annotation

You can checkout the code on github to get the full project.

Posted on Leave a comment

Setting and Getting Custom Properties In Quarkus

Often time, when creating web application, you need to set some values in application settings and later, pull such values to display to users.

One of the most prevalent examples is the application’s name and/or application’s version. I’ll show you how to set such values in the app and later, display them in an example endpoint.

Setting custom properties in application.properties

One place you can set custom properties for the application is in application.properties file. For example, I set two properties as follow:

myapp.author=datmt
myapp.created_year=2021

Now, let’s create an endpoint and display those values.

Getting custom properties in REST endpoints

Setting properties was easy, getting them is no exception. Let’s see how I get those values in the follow snippets:

    @ConfigProperty(name = "myapp.author")
    String author;

    @ConfigProperty(name = "myapp.created_year")
    String createdYear;


    @GET
    @Path("info")
    @Produces(MediaType.TEXT_PLAIN)
    public String info() {

        return "App created by " + author + " in the year " + createdYear;

    }

When accessing the URL in my browser, this is the result:

custom properties set in application properties file displayed.

As you can see, the values I set in application.properties file were displayed correctly in the browser.

Setting default fallback value

There are times you want to set a default value for a specific property in case it is not available. You can do that easily with default in @ConfigProperty.

For example, I’ll try to access a property that didn’t set in application.properties file called myapp.language. In @ConfigProperty, besides setting name, I also set a default value:

    @ConfigProperty(name = "myapp.language", defaultValue = "French")
    String language;

In the info method above, I updated the return string to be like so:

    @GET
    @Path("info")
    @Produces(MediaType.TEXT_PLAIN)
    public String info() {

        return "App created by " + author + " in the year " + createdYear + " in " + language;

    }

When viewing on my browser, I got the language displayed as French since there wasn’t any setting for that property in application.properties file:

Getting default property value in JAX-RS

Setting multiple values for property

As you’ve seen above, I set only String to the properties. However, if you want, you can set a list as one property.

myapp.programming_languages=Java,PHP,JavaScript,Go

I created a new endpoint called language-info with the following details:

    @GET
    @Path("language-info")
    @Produces(MediaType.TEXT_PLAIN)
    public String languageInfo() {

        return  String.join(",", languages);
    }

Sure enough, when visiting the endpoint, I got the expected result:

In case you have multiple properties and don’t want to inject each property separately, you can inject Config object:

    @Inject
    Config config;

and get the value using getValue.

String appLanguage = config.getValue("myapp.language", String.class);
Posted on Leave a comment

How To Return File Download In JAX-RS Application

When developing JAX-RS applications, most of the time, you return JSON to the client. However, there are times you need to return file download. For example, your client may require that when an user access a certain URL, the application must prompt a save file dialog.

If you have trouble with this requirement, look no further. I’m going to show you exactly how to return file download in JAX-RS application.

How to return file download

Let’s consider the following rest enpoint:

    @GET
    @Path("file")
    @Produces(MediaType.TEXT_PLAIN)
    public String download() {
        return "directly to browser";
    }

If you access the URL from the browser, I’m sure you can guess the result. It’s the text “directly to browser” just like this:

Text printed directly to the browser

Now, what if you want instead of print the content to the browser, when an user visit the URL, a file is downloaded instead?

Consider the following code:

    @GET
    @Path("file")
    @Produces(MediaType.TEXT_PLAIN)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;")
                .build();
    }

If you visit the browser right now following the same URL as above, a file is downloaded instead.

You may wonder why the name of the file is file.txt? The reason is quite simple: Since I didn’t specify the file name (I’ll show you how later) and from the annotation, I set @Produce to TEXT_PLAIN, so JAX-RS was smart enough to get the path name and the content type to create the file name.

Let’s change the path to something else and change @Produce to XML.

    @GET
    @Path("file-other")
    @Produces(MediaType.APPLICATION_XML)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;")
                .build();
    }

Sure enough, the file name is now:

Change file name for the file downloaded

Let’s say now your client is very happy with the progress so far. However, she wants the name of the file to be something else and she doesn’t want to change the URL. What can you do?

Specify the download file’s name

If you need to set name for the downloaded file, simply set it in the header like so:

    @GET
    @Path("file")
    @Produces(MediaType.APPLICATION_XML)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=great_file.xml")
                .build();
    }

Now, if I visit the URL, I’ll get a file named great_file.xml

Specify file name for download file in JAX-RS

Conclusion

Returning file download is a common requirement in web projects. As you can see, by adding Content-Disposition header and set it to attachment, you can make a request return a file download instead of printing out text to the browser. You can also set name for the download file, if you like by specifying filename field.