How to map the PostgreSQL inet type with JPA and Hibernate

(Last Updated On: September 7, 2018)

Introduction

In this article, we are going to see how to map the PostgreSQL inet type with JPA and Hibernate. Traditionally, PostgreSQL has been offering more column types than other relational database systems.

Previously, I showed you how to map JSON and ARRAY as Hibernate Types, and mapping the PostgreSQL inet type is going to be just as easy.

You don’t even have to implement these types since they are available via the hibernate-types project.

Inet column type

The PostgreSQL inet type allows you to store Network addresses with both the IP address (IPv4 or IPv6) and the subnet as well.

While you could store a network address as VARCHAR or as a series of bytes or as a numeric type, the inet is more compact and allows you to use various network functions.

While the inet column type is used to store the network address on the database side, in the Domain Model, we are going to use the Inet class type instead:

public class Inet 
        implements Serializable {

    private final String address;

    public Inet(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) 
            return true;
            
        if (o == null || getClass() != o.getClass()) 
            return false;

        Inet inet = (Inet) o;

        return address != null ? 
                address.equals(inet.address) : 
                inet.address == null;
    }

    @Override
    public int hashCode() {
        return address != null ? 
                address.hashCode() : 
                0;
    }

    public InetAddress toInetAddress() {
        try {
            String host = address.replaceAll(
                "\\/.*$", ""
            );
            
            return Inet4Address.getByName(host);
        } catch (UnknownHostException e) {
            throw new IllegalStateException(e);
        }
    }
}

You don’t have to create the Inet class in your application as long as you are using the hibernate-types project.

Inet Hibernate Type

When mapping a Hibernate custom Type you have two options:

Using the former strategy, the PostgreSQLInetType looks as follows:

public class PostgreSQLInetType 
        extends ImmutableType<Inet> {

    public PostgreSQLInetType() {
        super(Inet.class);
    }

    @Override
    public int[] sqlTypes() {
        return new int[]{
            Types.OTHER
        };
    }

    @Override
    public Inet get(
            ResultSet rs, 
            String[] names, 
            SessionImplementor session, 
            Object owner
        ) throws SQLException {
        String ip = rs.getString(names[0]);
        return (ip != null) ? new Inet(ip) : null;
    }

    @Override
    public void set(
            PreparedStatement st, 
            Inet value, 
            int index, 
            SessionImplementor session
        ) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.OTHER);
        } else {
            Object holder = ReflectionUtils.newInstance(
                "org.postgresql.util.PGobject"
            );
            ReflectionUtils.invokeSetter(
                holder, 
                "type", 
                "inet"
            );
            ReflectionUtils.invokeSetter(
                holder, 
                "value", 
                value.getAddress()
            );
            st.setObject(index, holder);
        }
    }
}

The best way to understand why it’s worth extending the ImmutableType offered by the hibernate-types project is to take a look on the following class diagram:

PostgreSQL Inet Type

Notice that the vast majority of the UserType methods are handled by the ImmutableType abstract base class while the PostgreSQLInetType just has to implement 3 methods only.

Maven dependency

As already mentioned, you don’t need to create the aforementioned classes. You can get them via the hibernate-types Maven dependency:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

If you’re using older versions of Hibernate, check out the hibernate-types GitHub repository for more info about the matching dependency for your current Hibernate version.

Domain Model

Let’s assume our application needs to track the IP addresses of the clients connecting to our production systems. The Event entity will encapsulate the IP address as in the following example:

@Entity(name = "Event")
@Table(name = "event")
@TypeDef(
    name = "ipv4", 
    typeClass = PostgreSQLInetType.class, 
    defaultForType = Inet.class
)
public class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Column(
        name = "ip", 
        columnDefinition = "inet"
    )
    private Inet ip;

    public Long getId() {
        return id;
    }

    public Inet getIp() {
        return ip;
    }

    public void setIp(String address) {
        this.ip = new Inet(address);
    }
}

Notice the use of the @TypeDef annotation which tells Hibernate to use the PostgreSQLInetType Hibernate Type for handling the Inet entity properties.

Testing time

Now, when persisting the following two Event entities:

entityManager.persist(new Event());

Event event = new Event();
event.setIp("192.168.0.123/24");

entityManager.persist(event);

Hibernate generates the following SQL INSERT statements:

INSERT INTO event (ip, id) VALUES (NULL(OTHER), 1)

INSERT INTO event (ip, id) VALUES ('192.168.0.123/24', 2)

Notice that the first INSERT statement sets the ip column to NULL just like its associated entity property while the second INSERT statement sets the ip column accordingly. Even if the parameter is logged as a String, on the database site, the column type is inet and the value is stored in a parsed binary format.

When fetching the second Event entity, we can see that the ip attribute is properly retrieved from the underlying inet database column:

Event event = entityManager.find(Event.class, 2L);

assertEquals(
    "192.168.0.123/24", 
    event.getIp().getAddress()
);

assertEquals(
    "192.168.0.123", 
    event.getIp().toInetAddress().getHostAddress()
);

What’s nice about the inet column type is that we can use network address-specific operators like the && one, which verifies if the address on the left-hand side belongs the subnet address on the right-hand side:

Event event = (Event) entityManager
.createNativeQuery(
    "SELECT e.* " +
    "FROM event e " +
    "WHERE " +
    "   e.ip && CAST(:network AS inet) = true", Event.class)
.setParameter("network", "192.168.0.1/24")
.getSingleResult();

assertEquals(
    "192.168.0.123/24", 
    event.getIp().getAddress()
);

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Mapping non-standard database column types is fairly easy with Hibernate. However, with the help of the hibernate-types project, you don’t even have to write all these types. Just add the Maven dependency to your project pom.xml configuration file and add the @Type annotation to the entity attribute in question.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

2 thoughts on “How to map the PostgreSQL inet type with JPA and Hibernate

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.