How to map the PostgreSQL inet type with JPA and Hibernate
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
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.
And you don’t even have to implement these types I’m presenting here since they are available via the Hypersistence Utils 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 Hypersistence Utils project.
Inet Hibernate Type
When mapping a Hibernate custom Type, you have two options:
- you can implement the
UserType
interface - you can extend the
AbstractSingleColumnStandardBasicType
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 Hypersistence Utils project is to take a look at the following class diagram:
Notice that the vast majority of the UserType
methods are handled by the ImmutableType
abstract base class while the PostgreSQLInetType
just has to implement three methods only.
Maven dependency
As already mentioned, you don’t need to create the aforementioned classes. You can get them via the Hypersistence Utils Maven dependency:
<dependency> <groupId>io.hypersistence</groupId> <artifactId>hypersistence-utils-hibernate-55</artifactId> <version>${hypersistence-utils.version}</version> </dependency>
If you’re using older versions of Hibernate, check out the Hypersistence Utils 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.
For Hibernate 6, the mapping will look as follows:
@Entity(name = "Event") @Table(name = "event") public class Event { @Id @GeneratedValue private Long id; @Type(PostgreSQLInetType.class) @Column( name = "ip", columnDefinition = "inet" ) private Inet ip; }
And for Hibernate 5, like this:
@Entity(name = "Event") @Table(name = "event") @TypeDef(typeClass = PostgreSQLInetType.class, defaultForType = Inet.class) public class Event { @Id @GeneratedValue private Long id; @Column( name = "ip", columnDefinition = "inet" ) private Inet ip; }
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 to 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 Hypersistence Utils 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.
