Skip to content

Commit 44752fa

Browse files
committed
add ObjectBigIntId identity ID generator
1 parent 2b56cd9 commit 44752fa

File tree

5 files changed

+300
-0
lines changed

5 files changed

+300
-0
lines changed

modules/typed-ids-hibernate-62/src/main/java/org/framefork/typedIds/bigint/hibernate/ObjectBigIntIdTypeGenerationMetadataContributor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.auto.service.AutoService;
44
import org.framefork.typedIds.bigint.ObjectBigIntId;
5+
import org.framefork.typedIds.bigint.hibernate.id.ObjectBigIntIdIdentityGenerator;
56
import org.framefork.typedIds.bigint.hibernate.id.ObjectBigIntIdSequenceStyleGenerator;
67
import org.hibernate.boot.ResourceStreamLocator;
78
import org.hibernate.boot.spi.AdditionalMappingContributions;
@@ -48,6 +49,8 @@ private void remapIdentifierGeneratorStrategy(final SimpleValue identifier)
4849
{
4950
var newIdentifierGeneratorStrategy = switch (identifier.getIdentifierGeneratorStrategy()) {
5051
case "org.hibernate.id.enhanced.SequenceStyleGenerator" -> ObjectBigIntIdSequenceStyleGenerator.class.getName();
52+
case "org.hibernate.id.IdentityGenerator" -> ObjectBigIntIdIdentityGenerator.class.getName();
53+
case "identity" -> ObjectBigIntIdIdentityGenerator.class.getName();
5154
default -> identifier.getIdentifierGeneratorStrategy();
5255
};
5356

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package org.framefork.typedIds.bigint.hibernate.id;
2+
3+
import org.framefork.typedIds.bigint.hibernate.ObjectBigIntIdType;
4+
import org.framefork.typedIds.common.ReflectionHacks;
5+
import org.hibernate.HibernateException;
6+
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
7+
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
8+
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
9+
import org.hibernate.engine.spi.SessionFactoryImplementor;
10+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
11+
import org.hibernate.id.IdentityGenerator;
12+
import org.hibernate.id.PostInsertIdentityPersister;
13+
import org.hibernate.id.insert.Binder;
14+
import org.hibernate.id.insert.IdentifierGeneratingInsert;
15+
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
16+
import org.hibernate.jdbc.Expectation;
17+
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
18+
import org.hibernate.service.ServiceRegistry;
19+
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
20+
import org.hibernate.type.CustomType;
21+
import org.hibernate.type.Type;
22+
import org.hibernate.type.descriptor.java.spi.JavaTypeBasicAdaptor;
23+
import org.hibernate.type.descriptor.jdbc.JdbcType;
24+
import org.hibernate.type.internal.ImmutableNamedBasicTypeImpl;
25+
import org.jspecify.annotations.Nullable;
26+
27+
import java.lang.reflect.InvocationHandler;
28+
import java.lang.reflect.Method;
29+
import java.lang.reflect.Proxy;
30+
import java.sql.PreparedStatement;
31+
import java.util.Objects;
32+
import java.util.Properties;
33+
34+
/**
35+
* This class handles making Hibernate think it's generating a primitive long type instead of a custom type,
36+
* and once Hibernate internals do their job, it then handles wrapping the generated long value in an ObjectBigIntId.
37+
*/
38+
public class ObjectBigIntIdIdentityGenerator extends IdentityGenerator
39+
{
40+
41+
@Nullable
42+
private ObjectBigIntIdType objectBigIntIdType;
43+
@Nullable
44+
private ImmutableNamedBasicTypeImpl<Long> primitiveType;
45+
46+
@Override
47+
public void configure(final Type type, final Properties parameters, final ServiceRegistry serviceRegistry)
48+
{
49+
this.objectBigIntIdType = toObjectBigIntIdType(type);
50+
this.primitiveType = new ImmutableNamedBasicTypeImpl<>(
51+
new JavaTypeBasicAdaptor<Long>(Long.class),
52+
toJdbcType(objectBigIntIdType),
53+
"bigint"
54+
);
55+
}
56+
57+
private ObjectBigIntIdType toObjectBigIntIdType(final Type type)
58+
{
59+
if (type instanceof CustomType<?> customType) {
60+
var userType = customType.getUserType();
61+
if (userType instanceof ObjectBigIntIdType objectBigIntIdType) {
62+
return objectBigIntIdType;
63+
}
64+
}
65+
66+
throw new HibernateException("The given type is expected to be a CustomType wrapper over a %s, but was '%s' instead".formatted(ObjectBigIntIdType.class.getSimpleName(), type));
67+
}
68+
69+
private JdbcType toJdbcType(final ObjectBigIntIdType objectBigIntIdType)
70+
{
71+
return objectBigIntIdType.getJdbcType(null);
72+
}
73+
74+
@Override
75+
public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(final PostInsertIdentityPersister persister)
76+
{
77+
var persisterProxy = proxyPersister(persister);
78+
79+
var originalDelegate = super.getGeneratedIdentifierDelegate(persisterProxy);
80+
81+
return new InsertGeneratedIdentifierDelegateWrapper(originalDelegate);
82+
}
83+
84+
private PostInsertIdentityPersister proxyPersister(final PostInsertIdentityPersister persister)
85+
{
86+
return (PostInsertIdentityPersister) Proxy.newProxyInstance(
87+
persister.getClass().getClassLoader(),
88+
ReflectionHacks.getAllInterfaces(persister),
89+
new InvocationHandler()
90+
{
91+
@Override
92+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
93+
{
94+
if ("getIdentifierType".equals(method.getName()) && (args == null || args.length == 0)) {
95+
// Override the getIdentifierType method
96+
// Return a primitive long type instead of the custom type
97+
return Objects.requireNonNull(primitiveType, "primitiveType must not be null");
98+
}
99+
100+
// For all other methods, delegate to the original persister
101+
return method.invoke(persister, args);
102+
}
103+
}
104+
);
105+
}
106+
107+
@SuppressWarnings("deprecation")
108+
private final class InsertGeneratedIdentifierDelegateWrapper implements InsertGeneratedIdentifierDelegate
109+
{
110+
111+
private final InsertGeneratedIdentifierDelegate delegate;
112+
113+
public InsertGeneratedIdentifierDelegateWrapper(final InsertGeneratedIdentifierDelegate delegate)
114+
{
115+
this.delegate = delegate;
116+
}
117+
118+
@Override
119+
public Object performInsert(final String insertSQL, final SharedSessionContractImplementor session, final Binder binder)
120+
{
121+
var idType = Objects.requireNonNull(objectBigIntIdType, "objectBigIntIdType must not be null");
122+
123+
return idType.wrapJdbcValue(delegate.performInsert(insertSQL, session, binder));
124+
}
125+
126+
@Override
127+
public Object performInsert(final PreparedStatementDetails insertStatementDetails, final JdbcValueBindings valueBindings, final Object entity, final SharedSessionContractImplementor session)
128+
{
129+
var idType = Objects.requireNonNull(objectBigIntIdType, "objectBigIntIdType must not be null");
130+
131+
return idType.wrapJdbcValue(delegate.performInsert(insertStatementDetails, valueBindings, entity, session));
132+
}
133+
134+
@Override
135+
public String prepareIdentifierGeneratingInsert(final String insertSQL)
136+
{
137+
return delegate.prepareIdentifierGeneratingInsert(insertSQL);
138+
}
139+
140+
@Override
141+
public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(final SqlStringGenerationContext context)
142+
{
143+
return delegate.prepareIdentifierGeneratingInsert(context);
144+
}
145+
146+
@Override
147+
public TableInsertBuilder createTableInsertBuilder(final BasicEntityIdentifierMapping identifierMapping, final Expectation expectation, final SessionFactoryImplementor sessionFactory)
148+
{
149+
return delegate.createTableInsertBuilder(identifierMapping, expectation, sessionFactory);
150+
}
151+
152+
@Override
153+
public PreparedStatement prepareStatement(final String insertSql, final SharedSessionContractImplementor session)
154+
{
155+
return delegate.prepareStatement(insertSql, session);
156+
}
157+
158+
}
159+
160+
}

modules/typed-ids-hibernate-63/src/main/java/org/framefork/typedIds/bigint/hibernate/ObjectBigIntIdTypeGenerationMetadataContributor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.auto.service.AutoService;
44
import org.framefork.typedIds.bigint.ObjectBigIntId;
5+
import org.framefork.typedIds.bigint.hibernate.id.ObjectBigIntIdIdentityGenerator;
56
import org.framefork.typedIds.bigint.hibernate.id.ObjectBigIntIdSequenceStyleGenerator;
67
import org.hibernate.boot.ResourceStreamLocator;
78
import org.hibernate.boot.spi.AdditionalMappingContributions;
@@ -48,6 +49,8 @@ private void remapIdentifierGeneratorStrategy(final SimpleValue identifier)
4849
{
4950
var newIdentifierGeneratorStrategy = switch (identifier.getIdentifierGeneratorStrategy()) {
5051
case "org.hibernate.id.enhanced.SequenceStyleGenerator" -> ObjectBigIntIdSequenceStyleGenerator.class.getName();
52+
case "org.hibernate.id.IdentityGenerator" -> ObjectBigIntIdIdentityGenerator.class.getName();
53+
case "identity" -> ObjectBigIntIdIdentityGenerator.class.getName();
5154
default -> identifier.getIdentifierGeneratorStrategy();
5255
};
5356

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package org.framefork.typedIds.bigint.hibernate.id;
2+
3+
import org.framefork.typedIds.bigint.hibernate.ObjectBigIntIdType;
4+
import org.framefork.typedIds.common.ReflectionHacks;
5+
import org.hibernate.HibernateException;
6+
import org.hibernate.id.IdentityGenerator;
7+
import org.hibernate.id.PostInsertIdentityPersister;
8+
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
9+
import org.hibernate.service.ServiceRegistry;
10+
import org.hibernate.type.CustomType;
11+
import org.hibernate.type.Type;
12+
import org.hibernate.type.descriptor.java.spi.JavaTypeBasicAdaptor;
13+
import org.hibernate.type.descriptor.jdbc.JdbcType;
14+
import org.hibernate.type.internal.ImmutableNamedBasicTypeImpl;
15+
import org.jspecify.annotations.Nullable;
16+
17+
import java.lang.reflect.InvocationHandler;
18+
import java.lang.reflect.Method;
19+
import java.lang.reflect.Proxy;
20+
import java.util.Objects;
21+
import java.util.Properties;
22+
23+
/**
24+
* This class handles making Hibernate think it's generating a primitive long type instead of a custom type,
25+
* and once Hibernate internals do their job, it then handles wrapping the generated long value in an ObjectBigIntId.
26+
*/
27+
public class ObjectBigIntIdIdentityGenerator extends IdentityGenerator
28+
{
29+
30+
@Nullable
31+
private ObjectBigIntIdType objectBigIntIdType;
32+
@Nullable
33+
private ImmutableNamedBasicTypeImpl<Long> primitiveType;
34+
35+
@Override
36+
public void configure(final Type type, final Properties parameters, final ServiceRegistry serviceRegistry)
37+
{
38+
this.objectBigIntIdType = toObjectBigIntIdType(type);
39+
this.primitiveType = new ImmutableNamedBasicTypeImpl<>(
40+
new JavaTypeBasicAdaptor<Long>(Long.class),
41+
toJdbcType(objectBigIntIdType),
42+
"bigint"
43+
);
44+
}
45+
46+
private ObjectBigIntIdType toObjectBigIntIdType(final Type type)
47+
{
48+
if (type instanceof CustomType<?> customType) {
49+
var userType = customType.getUserType();
50+
if (userType instanceof ObjectBigIntIdType objectBigIntIdType) {
51+
return objectBigIntIdType;
52+
}
53+
}
54+
55+
throw new HibernateException("The given type is expected to be a CustomType wrapper over a %s, but was '%s' instead".formatted(ObjectBigIntIdType.class.getSimpleName(), type));
56+
}
57+
58+
private JdbcType toJdbcType(final ObjectBigIntIdType objectBigIntIdType)
59+
{
60+
return objectBigIntIdType.getJdbcType(null);
61+
}
62+
63+
@Override
64+
public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(final PostInsertIdentityPersister persister)
65+
{
66+
var persisterProxy = proxyPersister(persister);
67+
68+
var originalDelegate = super.getGeneratedIdentifierDelegate(persisterProxy);
69+
70+
return proxyIdentifierDelegate(originalDelegate);
71+
}
72+
73+
private PostInsertIdentityPersister proxyPersister(final PostInsertIdentityPersister persister)
74+
{
75+
return (PostInsertIdentityPersister) Proxy.newProxyInstance(
76+
persister.getClass().getClassLoader(),
77+
ReflectionHacks.getAllInterfaces(persister),
78+
new InvocationHandler()
79+
{
80+
@Override
81+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
82+
{
83+
if ("getIdentifierType".equals(method.getName()) && (args == null || args.length == 0)) {
84+
// Override the getIdentifierType method
85+
// Return a primitive long type instead of the custom type
86+
return Objects.requireNonNull(primitiveType, "primitiveType must not be null");
87+
}
88+
89+
// For all other methods, delegate to the original persister
90+
return method.invoke(persister, args);
91+
}
92+
}
93+
);
94+
}
95+
96+
private InsertGeneratedIdentifierDelegate proxyIdentifierDelegate(final InsertGeneratedIdentifierDelegate delegate)
97+
{
98+
return (InsertGeneratedIdentifierDelegate) Proxy.newProxyInstance(
99+
delegate.getClass().getClassLoader(),
100+
ReflectionHacks.getAllInterfaces(delegate),
101+
new InvocationHandler()
102+
{
103+
@Override
104+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
105+
{
106+
// For all other methods, delegate to the original persister
107+
Object result = method.invoke(delegate, args);
108+
109+
if ("performInsert".equals(method.getName())) {
110+
var idType = Objects.requireNonNull(objectBigIntIdType, "objectBigIntIdType must not be null");
111+
return idType.wrapJdbcValue(result);
112+
113+
} else {
114+
return result;
115+
}
116+
}
117+
}
118+
);
119+
}
120+
121+
}

modules/typed-ids/src/main/java/org/framefork/typedIds/common/ReflectionHacks.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.lang.reflect.Constructor;
1010
import java.lang.reflect.Field;
1111
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.HashSet;
1214

1315
@ApiStatus.Internal
1416
public final class ReflectionHacks
@@ -88,4 +90,15 @@ public static MethodHandle getMainConstructor(final Class<?> type, final Class<?
8890
}
8991
}
9092

93+
public static Class<?>[] getAllInterfaces(final Object object)
94+
{
95+
var interfaces = new HashSet<Class<?>>();
96+
Class<?> classNode = object.getClass();
97+
while (classNode != null && classNode != Object.class) {
98+
Collections.addAll(interfaces, classNode.getInterfaces());
99+
classNode = classNode.getSuperclass();
100+
}
101+
return interfaces.toArray(Class[]::new);
102+
}
103+
91104
}

0 commit comments

Comments
 (0)