In Hibernate UserType Example using Spring Data JPA we will see how Hibernate allows us to implement and use custom types when built-in types do not satisfy an application’s requirements, or when we 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 allow us to develop an application without thinking about the target database column types that the target database supports and we easily develop our 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 we write HQSL (hibernate query language) then we 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 we want Hibernate to treat basic Java types or persistent classes differently than Hibernate normally treats them.

You may also like to read:

Spring Data batch Insertion

Spring EnableEncryptableProperties with Jasypt

Spring Data JPA Entity Auditing using EntityListeners

Spring Data JPA Entity Graph

Spring Data JPA CRUD Example

Here are some situations where you may need to define and use a custom type:

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. So, for example, you might want to concatenate together FIRST_NAME, INITIAL and SURNAME columns into a java.lang.String.

There are 3 approaches to developing a custom Hibernate type:

org.hibernate.type.Type, org.hibernate.usertype.UserType and org.hibernate.usertype.CompositeUserType.

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. We will return an employee’s first name and last name from database and map these two values into one Java property as a full name. Therefore we will implement UserType interface of Hibernate.

Below is the application.properties file under classpath directory src/main/resources and you need to define database credentials to establish connection with database. If you do not want server to run on default port then you may want to specify the server port using server.port key. Here in Hibernate UserType Example using Spring Data JPA, I am going to use Oracle database but you may use any database as per your requirements.

#datasource
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
spring.datasource.url=jdbc:Oracle:thin:@//<host>:<port>/<service name>
spring.datasource.username=<username>
spring.datasource.password=<password>
server.port=9999
#disable schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none

Below is the configuration class that will be used to define various database related beans such as DataSource, EntityManagerFactory etc.

As we have application.properties file in classpath, so we don’t need to load the properties file.

We have let Spring know where our Spring Data JPA Repository interfaces using the annotation @EnableJpaRepositories and we have also let Spring know where to look for Entity classes using the setter method factory.setPackagesToScan(“com.jeejava.entity”).

package com.jeejava.config;

import javax.persistence.EntityManagerFactory;
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.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

@Configuration
@EnableJpaRepositories(basePackages = "com.jeejava.repository")
public class DatabaseConfig {
	@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;
	}
	@Bean
	public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		vendorAdapter.setDatabase(Database.ORACLE);
		LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
		factory.setJpaVendorAdapter(vendorAdapter);
		factory.setPackagesToScan("com.jeejava.entity");
		factory.setDataSource(dataSource);
		factory.afterPropertiesSet();
		return factory.getObject();
	}
}

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(), we need to return column types. In this example we will return two columns from database table so we are returning their types.
For method, returnedClass(), we will return the class type of the returned value. Here the class type is String because we will combine first name and last name and return the full name.

Here 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.jeejava.entity.usertype;

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.SessionImplementor;
import org.hibernate.usertype.UserType;

public class EmployeeUserType 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 nullSafeGet(ResultSet rs, String[] names, SessionImplementor 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, SessionImplementor 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);
		}
	}
	@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);
	}
}

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. We have only one custom type class that is used in the below class and that’s why I have used annotation @TypeDef, if we have multiple custom type classes that will be used for a particular entity class then we have to use @TypeDefs.

Further we 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 we have mapped two database table’s column into one attribute empName.

package com.jeejava.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.jeejava.entity.usertype.EmployeeUserType;
@Entity
@Table(name = "EMPLOYEE")
@TypeDef(name = "EmployeeUserType", typeClass = EmployeeUserType.class)
public class Employee implements Serializable {
	private static final long serialVersionUID = 1L;
	
	@Id
	@Column(name = "EMPLOYEE_ID")
	private Integer empId;
	
	@Type(type = "EmployeeUserType")
	@Columns(columns = { @Column(name = "EMPLOYEE_FIRST_NAME"), @Column(name = "EMPLOYEE_LAST_NAME") })
	private String empName;
	
	@Column(name = "IS_ACTIVE")
	private String isActive;
	
	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;
	}
	public String getIsActive() {
		return isActive;
	}
	public void setIsActive(String isActive) {
		this.isActive = isActive;
	}
	@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());
		result = prime * result + ((isActive == null) ? 0 : isActive.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;
		if (isActive == null) {
			if (other.isActive != null)
				return false;
		} else if (!isActive.equals(other.isActive))
				return false;
		return true;
	}
}

Here is the Spring Data JPA Repository interface. If you would like to know more on this you can read here at https://docs.spring.io/spring-data/jpa/docs/current/reference/html/

package com.jeejava.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.jeejava.entity.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}

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.jeejava.service;

import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.jeejava.entity.Employee;
import com.jeejava.repository.EmployeeRepository;

@Service
public class EmployeeService {
	@Resource
	private EmployeeRepository employeeRepository;
	
	public List<Employee> getEmployees() {
		return employeeRepository.findAll();
	}
}

The Spring REST Controller class is responsible for handling requests and responses from clients. This holds all the REST services end-points. Using these end-points we would be able to get the JSON response.

Here we have only one end-point called /employees that should give you all employees from the database when you hit the URL http://localhost:9999/employees from the browser or REST client or Postman.

As this is a GET request so you should be able to fetch the data using browser.

package com.jeejava.controller;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jeejava.entity.Employee;
import com.jeejava.service.EmployeeService;

@RestController
public class EmployeeRestController {
	@Autowired
	private EmployeeService employeeService;
	@GetMapping("/employees")
	public ResponseEntity<List<Employee>> getEmployees() {
		List<Employee> employees = employeeService.getEmployees();
		return new ResponseEntity<List<Employee>>(employees, HttpStatus.OK);
	}
}

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

package com.jeejava.application;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.jeejava")
public class Application {
	
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
	
}

Once you run the above main class and application gets start up, hit the URL http://localhost:9999/employees from the browser or REST client or Postman and you should get the JSON response of all employees.

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

You may also like to read:

Spring Data batch Insertion

Spring EnableEncryptableProperties with Jasypt

Spring Data JPA Entity Auditing using EntityListeners

Spring Data JPA Entity Graph

Spring Data JPA CRUD Example

Thanks for reading.

Tags:

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on Roy Tutorials | TwitterFacebook Google PlusLinkedin | Reddit

Leave a Reply

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