Support Java Optional in Hibernate 6 entity, thanks to the Lib Custom library, a wrapper that simplifies calls to Byte Buddy library
In this tuto we use the functional option class: OptionF, but with little changes you can use any other option class, such as Optional
1) Add lib-custom to your pom.xml dependencies
Lib-custom is a library that allows easy customization of external third-party libs like Hibernate
<dependency>
<groupId>io.github.jleblanc64</groupId>
<artifactId>lib-custom</artifactId>
<version>1.0.0</version>
</dependency>
2) Customize Hibernate 6 with Java Optional
We need to override 3 Hibernate functions:
UnknownBasicJavaType.getRecommendedJdbcType()
UnknownBasicJavaType.unwrap()
UnknownBasicJavaType.wrap()
basically we are explaining to Hibernate how to do the conversion String.class <-> OptionF.class:
[string=null] <-> [option=OptionF.emptyO()]
[string=x] <-> [option=OptionF.o(x)]
Java code HibernateOption:
public class HibernateOption {
public static void override() {
// replace with your own values
var rootPackage = "com.demo";
var optionClass = OptionF.class;
var tableToEntity = f(new Reflections(rootPackage).getTypesAnnotatedWith(Entity.class))
.toMap(x -> x.getAnnotation(Table.class).name(), x -> x);
LibCustom.override(UnknownBasicJavaType.class, "getRecommendedJdbcType", args -> {
var ind = args[0];
if (!(ind instanceof BasicValue))
return null;
var b = (BasicValue) ind;
var s = b.getColumn();
if (!(s instanceof Column))
return null;
var c = (Column) s;
var entity = tableToEntity.get(b.getTable().getName());
var fields = f(entity.getDeclaredFields());
var field = fields.findSafe(x -> x.getName().equals(c.getName()));
if (field.getType() == optionClass)
return new VarcharJdbcType();
return null;
});
LibCustom.overrideWithSelf(UnknownBasicJavaType.class, "unwrap", argsSelf -> {
var args = argsSelf.args;
var v = args[0];
var type = (Class<?>) args[1];
OptionF<?> o;
if (type == String.class && instanceOf(v, optionClass)) {
o = (OptionF<?>) v;
if (o.isPresent())
return o.get();
return new ValueWrapper(null);
}
if (type == optionClass && !instanceOf(v, optionClass))
// return Option from nullable value v
return o(v);
return v;
});
LibCustom.overrideWithSelf(UnknownBasicJavaType.class, "wrap", argsSelf -> {
var args = argsSelf.args;
var u = (UnknownBasicJavaType) argsSelf.self;
var v = args[0];
var type = u.getJavaTypeClass();
if (type == String.class && instanceOf(v, optionClass)) {
var o = (OptionF<?>) v;
if (o.isPresent())
return o.get();
return new ValueWrapper(null);
}
if (type == optionClass && !instanceOf(v, optionClass))
// return Option from nullable value v
return o(v);
return v;
});
}
static boolean instanceOf(Object o, Class<?> c) {
return o != null && o.getClass() == c;
}
}
3) Load custom Hibernate 6 byte code at the start of your app
Here we are using Spring boot, and a good place to load the custom byte code is in the DataSourceConfig configuration class. To load the custom byte code (customized by LibCustom in step 2)), just call:
HibernateOption.override();
LibCustom.load();
Java code DataSourceConfig:
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String username;
@Value("${spring.datasource.password}")
String password;
@Bean
public DataSource getDataSource() {
HibernateList.override();
Jackson.override();
HibernateOption.override();
LibCustom.load();
var ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername(username);
ds.setPassword(password);
// default is 10
ds.setMaximumPoolSize(8);
var config = Flyway.configure().dataSource(url, username, password);
config.load().migrate();
return ds;
}
}
4) You can now use Optional class in your Hibernate 6 entity code:
For instance, for column name:
private OptionF<String> name;
Java code Customer:
@Getter
@Setter
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private OptionF<String> name;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private ListF<Order> orders;
}
5) Full working example source code
Test class:
https://github.com/jleblanc64/hibernate6/blob/main/src/test/java/com/demo/ApplicationTests.java