Hibernate UserType Example using Spring Data JPA

Introduction

I am going to show you here what is UserType and why do you need UserType while working with database and web application. In Hibernate UserType Example using Spring Data JPA I will show you how Hibernate allows you to implement and use custom types when built-in types do not satisfy an application’s requirements, or when you want to change the default behavior of a built-in type. As you will see, you can easily implement a custom-type class and then use it in the same way as a built-in one.

Hibernate provides an abstraction of database SQL types to prevent an application from getting mapped to underlying actual database column types. This allows you to develop an application without thinking about the target database column types that the target database supports and you can easily develop your application and get involved with mapping Java types to Hibernate types.

The database dialect which is a part of Hibernate responsible for mapping Java types to target database column types. If you write HQL (hibernate query language) then you can easily switch to different database by changing the dialect without changing the application code.

For most of the mappings, Hibernate’s built-in types are enough but, in some situations, you may need to define a custom type. These situations generally happen when you want Hibernate to treat basic Java types or persistent classes differently than Hibernate normally treats them.

Where to use Custom Type

There are some situations where you may need to define and use a custom type, for example:

  • Storing a particular Java type in a column with a different SQL type than Hibernate normally uses.
  • Mapping a value type.
  • Splitting up a single property value and storing the result in more than one database column.
  • Storing more than one property in a single column.
  • Using an application-specific class as an identifier for the persistent class. For example, you might want to persist properties of type java.lang.BigInteger to VARCHAR columns.

Custom types are not limited to mapping values to a single table column. For example, you might want to concatenate together first name, initial and last name columns into a java.lang.String.

Different Approaches

There are 3 approaches of developing a custom Hibernate type:

  • org.hibernate.type.Type
  • org.hibernate.usertype.UserType
  • org.hibernate.usertype.CompositeUserType

UserType Implementation

Here I am going to show you Hibernate UserType example using Spring Data JPA.

In this example I will map database’s two columns into one property of Java class. I will return an employee’s first name and last name from database table and map these two column values into one Java property as a full name.

Prerequisites

Java at least 8, Spring Boot 2.3.2 – 2.4.3, MySQL 8.0.17 – 8.0.22

MySQL Table

I am creating an employee table under roytuts database in MySQL server.

CREATE TABLE IF NOT EXISTS `employee` (
  `id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
  `first_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `last_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

I am also putting some data into the table to test the application right away:

INSERT INTO `employee` (`id`, `first_name`, `last_name`) VALUES
	(1, 'Soumitra', 'Roy'),
	(2, 'Rahul', 'Kumar');

Project Setup

You can create gradle or maven based project. The name of the project is spring-data-jpa-hibernate-usertype.

If you are creating gradle based project then you can use below build.gradle script for your gradle build tool:

buildscript {
	ext {
		springBootVersion = '2.3.2.RELEASE' to 2.4.3
	}
	
    repositories {
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version "${springBootVersion}"
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
}

dependencies {
	implementation "org.springframework.boot:spring-boot-starter:${springBootVersion}"
    implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
    runtime("mysql:mysql-connector-java:8.0.17") //to 8.0.22
	
	//required for jdk 9 or above
	runtimeOnly('javax.xml.bind:jaxb-api:2.4.0-b180830.0359')
}

If you are using maven as a build tool then you can create below pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-data-jpa-hibernate-usertype</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>12</maven.compiler.source>
		<maven.compiler.target>12</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.3</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!--required only if jdk 9 or higher version is used -->
		<dependency>
			<groupId>javax.xml.bind</groupId>
			<artifactId>jaxb-api</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Configuration

Create a config file called application.properties under classpath directory src/main/resources.

You need to put database credentials into this file to establish connection with database.

Here in Hibernate UserType example using Spring Data JPA, I am going to use MySQL database but you may use any database as per your requirements.

spring.datasource.url=jdbc:mysql://localhost/roytuts
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver

#disable schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none

Create a Spring configuration class that will be used to define database related bean.

package com.roytuts.spring.data.jpa.hibernate.usertype.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@PropertySource("classpath:application.properties")
public class Config {

	@Autowired
	private Environment environment;

	@Bean
	public DataSource dataSource() {

		DriverManagerDataSource ds = new DriverManagerDataSource();
		ds.setDriverClassName(environment.getRequiredProperty("spring.datasource.driverClassName"));
		ds.setUrl(environment.getRequiredProperty("spring.datasource.url"));
		ds.setUsername(environment.getRequiredProperty("spring.datasource.username"));
		ds.setPassword(environment.getRequiredProperty("spring.datasource.password"));
		return ds;
	}

}

Custom UserType

Here is the custom class that will be used for an attribute of the Java entity class.

When you implement the interface UserType then you will see the required methods will be generated. You just need to write the implementations of these generated methods.

For method, sqlTypes(), you need to return column types. In this example I will return two columns from database table so I am returning their types.

For method, returnedClass(), you need to return the class type of the returned value. Here the class type is String because I will combine first name and last name and return as a full name.

There are two important methods – nullSafeGet() and nullSafeSet(), where you need to implement the logic how you are going to get the values from database and save the values into database respectively and rest of the methods are be self-explanatory.

package com.roytuts.spring.data.jpa.hibernate.usertype.type;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;

public class EmployeeType implements UserType {
	@Override
	public int[] sqlTypes() {
		return new int[] { Types.VARCHAR, Types.VARCHAR };
	}

	@Override
	public Class<String> returnedClass() {
		return String.class;
	}

	@Override
	public boolean equals(Object x, Object y) throws HibernateException {
		return ((x == y) || (x != null && y != null && x.equals(y)));
	}

	@Override
	public int hashCode(Object x) throws HibernateException {
		return x != null ? x.hashCode() : 0;
	}

	@Override
	public Object deepCopy(Object value) throws HibernateException {
		return value == null ? null : value;
	}

	@Override
	public boolean isMutable() {
		return false;
	}

	@Override
	public Serializable disassemble(Object value) throws HibernateException {
		Object deepCopy = deepCopy(value);
		if (!(deepCopy instanceof Serializable)) {
			return (Serializable) deepCopy;
		}
		return null;
	}

	@Override
	public Object assemble(Serializable cached, Object owner) throws HibernateException {
		return deepCopy(cached);
	}

	@Override
	public Object replace(Object original, Object target, Object owner) throws HibernateException {
		return deepCopy(original);
	}

	@Override
	public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
			throws HibernateException, SQLException {
		if (rs.wasNull()) {
			return null;
		}
		String firstName = rs.getString(names[0]);
		String lastName = rs.getString(names[1]);
		return firstName + " " + lastName;
	}

	@Override
	public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
			throws HibernateException, SQLException {
		if (value != null) {
			String[] names = ((String) value).split("\s");
			st.setString(index, names[0]);
			st.setString(index + 1, names[0]);
		} else {
			st.setString(index, null);
			st.setString(index + 1, null);
		}
	}

}

Entity Class

This is the entity class that maps Java object to database table.

Here notice how I have annotated the class to define our custom type column.

I have only one custom type class that is used in the below class and that’s why I have used annotation @TypeDef, if you have multiple custom type classes that will be used for a particular entity class then you have to use @TypeDefs.

Further you can use @TypeDef or @TypeDefs annotation at the package level as well but to improve the readability of users I have used at the class level.

Notice how I have mapped two database table’s column into one attribute empName.

package com.roytuts.spring.data.jpa.hibernate.usertype.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

import com.roytuts.spring.data.jpa.hibernate.usertype.type.EmployeeType;

@Entity
@Table(name = "employee")
@TypeDef(name = "EmployeeType", typeClass = EmployeeType.class)
public class Employee implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@Column(name = "id")
	private Integer empId;

	@Type(type = "EmployeeType")
	@Columns(columns = { @Column(name = "first_name"), @Column(name = "last_name") })
	private String empName;

	public Integer getEmpId() {
		return empId;
	}

	public void setEmpId(Integer empId) {
		this.empId = empId;
	}

	public String getEmpName() {
		return empName;
	}

	public void setEmpName(String empName) {
		this.empName = empName;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((empId == null) ? 0 : empId.hashCode());
		result = prime * result + ((empName == null) ? 0 : empName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (empId == null) {
			if (other.empId != null)
				return false;
		} else if (!empId.equals(other.empId))
			return false;
		if (empName == null) {
			if (other.empName != null)
				return false;
		} else if (!empName.equals(other.empName))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [empId=" + empId + ", empName=" + empName + "]";
	}

}

Spring Data JPA Repository

Here is the Spring Data JPA Repository interface. Spring Data JPA API provides built-in functions for performing basic CRUD operations. So you don’t need to create any function for this example for fetching data from database.

package com.roytuts.spring.data.jpa.hibernate.usertype.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.roytuts.spring.data.jpa.hibernate.usertype.entity.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

}

Service Class

This is the service class that interacts with data layer as well as controller layer and acts as a mediator between them.

This class generally handles all business logic.

package com.roytuts.spring.data.jpa.hibernate.usertype.service;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.roytuts.spring.data.jpa.hibernate.usertype.entity.Employee;
import com.roytuts.spring.data.jpa.hibernate.usertype.repository.EmployeeRepository;

@Service
public class EmployeeService {

	@Resource
	private EmployeeRepository employeeRepository;

	public List<Employee> getEmployees() {
		return employeeRepository.findAll();
	}

}

Main Class

Here is the application main class that is enough to start up the application in Spring Boot.

package com.roytuts.spring.data.jpa.hibernate.usertype;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.roytuts.spring.data.jpa.hibernate.usertype.service.EmployeeService;

@SpringBootApplication
public class SpringDataJpaHibernateUserTypeApp implements CommandLineRunner {

	@Autowired
	private EmployeeService employeeService;

	public static void main(String[] args) {
		SpringApplication.run(SpringDataJpaHibernateUserTypeApp.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		employeeService.getEmployees().forEach(e -> System.out.println(e));
	}

}

Testing the Application

Once you run the above main class you will see the below output in the console.

Employee [empId=1, empName=Soumitra Roy]
Employee [empId=2, empName=Rahul Kumar]

In the output you will see that each employee has full name displayed instead of having first and last name separately displayed.

That’s all. Hope you find an idea on Hibernate UserType Example using Spring Data JPA.

Source Code

Download

1 thought on “Hibernate UserType Example using Spring Data JPA

  1. Bonjour,
    comment rendre ces fonction en jpa sous spring
    public void initSchema() throws SQLException {
    final String query = “create table if not exists NAME (” //
    + ” id integer not null, ” //
    + ” name varchar(50) not null, ” //
    + ” primary key (id) )”;
    try (Connection conn = newConnection()) {
    conn.createStatement().execute(query);
    }
    }
    public void addName(int id, String name) throws SQLException {
    final String query = “insert into NAME values (?,?)”;
    try (Connection conn = newConnection()) {
    PreparedStatement st = conn.prepareStatement(query);
    st.setInt(1, id);
    st.setString(2, name);
    st.execute();
    }
    }
    public void deleteName(int id) throws SQLException {
    final String query = “Delete From NAME where (id = ?)”;
    try (Connection conn = newConnection()) {
    PreparedStatement st = conn.prepareStatement(query);
    st.setInt(1, id);
    st.execute();
    }
    }
    public void updateName(int id,String nom) throws SQLException {
    final String query = “UPDATE NAME SET name = ? where (id = ?)”;
    try (Connection conn = newConnection()) {
    PreparedStatement st = conn.prepareStatement(query);
    st.setString(1, nom);
    st.setInt(2, id);
    st.execute();
    }
    }
    public String findName(int id) throws SQLException {
    final String query = “Select * From NAME where (id = ?)”;
    try (Connection conn = newConnection()) {
    PreparedStatement st = conn.prepareStatement(query);
    st.setInt(1, id);
    ResultSet rs = st.executeQuery();
    if (rs.next()) {
    return rs.getString(“name”);
    }
    }
    return null;
    }
    public Collection findNames() throws SQLException {
    final String query = “Select * From NAME order by name”;
    Collection result = new LinkedList();
    try (Connection conn = newConnection()) {
    Statement st = conn.createStatement();
    ResultSet rs = st.executeQuery(query);
    while (rs.next()) {
    result.add(rs.getString(“name”));
    }
    }
    return result;
    }
    }

Leave a Reply

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