Skip to content

Commit

Permalink
support for MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
fprochazka committed Nov 11, 2024
1 parent 03a4f77 commit 2ccd867
Show file tree
Hide file tree
Showing 26 changed files with 827 additions and 58 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ Find the latest version in this project's GitHub releases or on Maven Central.

If you want just the plain classes, you can install only the [org.framefork:typed-ids](https://central.sonatype.com/artifact/org.framefork/typed-ids).

## Hibernate type mapping

The library tries to make sure your data is stored with the types best fit for the job.
In some cases, that means changing the default DDL that Hibernate uses for the `SqlTypes.UUID` jdbc type.

| Database | Database Type for UUID |
|---------------------|------------------------------------------------|
| PostgreSQL | `uuid` |
| MySQL | `binary(16)` |
| MariaDB 10.7+ | `uuid` |
| MariaDB before 10.7 | `binary(16)` |
| other | whatever Hibernate uses as the dialect default |

The library only sets the type if there is no JDBC type for `SqlTypes.UUID` already set,
which means that if you want to use something different you should be able to do so using a custom `org.hibernate.boot.model.TypeContributor`.

## Usage: ObjectUUID

The base type is designed to wrap a native UUID, and allows you to expose any utility functions you may need.
Expand Down Expand Up @@ -64,7 +80,7 @@ public record User(Id id)
var user = new User(Id.random());
```

With Kotlin, the boilerplate is visibly shorter
With Kotlin, the standard boilerplate should look like the following snippet

```kt
data class User(id: Id) {
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {

repositories {
mavenCentral()
mavenLocal()
}

group = "org.framefork"
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun
testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version.ref = "testcontainers" }
testcontainers-base = { module = "org.testcontainers:testcontainers" }
testcontainers-postgresql = { module = "org.testcontainers:postgresql" }
testcontainers-mysql = { module = "org.testcontainers:mysql" }
testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter" }
datasource-hikaricp = { module = "com.zaxxer:HikariCP", version = "4.0.3" }
datasource-proxy = { module = "net.ttddyy:datasource-proxy", version = "1.10" }
postgresql = { module = "org.postgresql:postgresql", version = "42.7.4" }
mysql = { module = "com.mysql:mysql-connector-j", version = "8.3.0" }
logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.5.12" }
hibernate-orm-v55 = { group = "org.hibernate", name = "hibernate-core", version = "5.5.9.Final" }
hibernate-orm-v56 = { group = "org.hibernate", name = "hibernate-core", version = "5.6.15.Final" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

import org.framefork.typedIds.uuid.ObjectUuid;
import org.framefork.typedIds.uuid.ObjectUuidTypeUtils;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.UUIDJavaType;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType;
import org.hibernate.usertype.DynamicParameterizedType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -21,6 +30,8 @@
public class ObjectUuidJavaType implements BasicJavaType<ObjectUuid<?>>, DynamicParameterizedType, Serializable
{

public static final int UUID_BYTE_LENGTH = 16;

private final UUIDJavaType inner;

@Nullable
Expand Down Expand Up @@ -58,7 +69,7 @@ public void setParameterValues(final Properties parameters)
@Override
public Type getJavaType()
{
return Objects.requireNonNull(identifierClass, "identifierClass must not be null");
return getJavaTypeClass();
}

@Override
Expand All @@ -82,6 +93,25 @@ public boolean areEqual(
return Objects.equals(one, another);
}

@Override
public JdbcType getRecommendedJdbcType(final JdbcTypeIndicators indicators)
{
final JdbcType descriptor = indicators.getJdbcType(indicators.resolveJdbcTypeCode(SqlTypes.UUID));
return descriptor instanceof AdjustableJdbcType
? ((AdjustableJdbcType) descriptor).resolveIndicatedType(indicators, this)
: descriptor;
}

@Override
public long getDefaultSqlLength(final Dialect dialect, final JdbcType jdbcType)
{
if (jdbcType instanceof VarbinaryJdbcType) {
return UUID_BYTE_LENGTH;
}

return Size.DEFAULT_LENGTH;
}

@Nullable
@Override
public <X> X unwrap(
Expand Down Expand Up @@ -118,6 +148,12 @@ public ObjectUuid<?> fromString(@Nullable final CharSequence string)
return (string == null) ? null : wrapUuidToIdentifier(UUID.fromString(string.toString()));
}

@Override
public MutabilityPlan<ObjectUuid<?>> getMutabilityPlan()
{
return ImmutableMutabilityPlan.instance();
}

private ObjectUuid<?> wrapUuidToIdentifier(final UUID uuid)
{
return ObjectUuidTypeUtils.wrapUuidToIdentifier(
Expand All @@ -126,6 +162,12 @@ private ObjectUuid<?> wrapUuidToIdentifier(final UUID uuid)
);
}

@Override
public String toString()
{
return "object-uuid(%s)".formatted(identifierClass != null ? identifierClass.getName() : "???");
}

@SuppressWarnings("unused")
private void writeObject(final ObjectOutputStream stream)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
package org.framefork.typedIds.uuid.hibernate;

import io.hypersistence.utils.hibernate.type.DescriptorImmutableType;
import io.hypersistence.utils.hibernate.type.ImmutableType;
import io.hypersistence.utils.hibernate.type.util.ParameterizedParameterType;
import org.framefork.typedIds.uuid.ObjectUuid;
import org.framefork.typedIds.uuid.ObjectUuidTypeUtils;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.BindableType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutabilityPlanExposer;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.type.spi.TypeConfigurationAware;
import org.hibernate.usertype.DynamicParameterizedType;
import org.jetbrains.annotations.Nullable;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Properties;

public class ObjectUuidType extends DescriptorImmutableType<ObjectUuid<?>, UUIDJdbcType, ObjectUuidJavaType> implements DynamicParameterizedType, MutabilityPlanExposer<ObjectUuid<?>>
public class ObjectUuidType extends ImmutableType<ObjectUuid<?>> implements
BindableType<ObjectUuid<?>>,
DomainType<ObjectUuid<?>>,
MutabilityPlanExposer<ObjectUuid<?>>,
DynamicParameterizedType,
TypeConfigurationAware
{

public static final String NAME = "objectUuid";
public static final String NAME = "object-uuid";

private final ObjectUuidJavaType javaTypeDescriptor = new ObjectUuidJavaType();

@Nullable
private JdbcType jdbcTypeDescriptor;

@Nullable
private TypeConfiguration typeConfiguration;

public ObjectUuidType()
{
this(UUIDJdbcType.INSTANCE);
this((JdbcType) null);
}

public ObjectUuidType(final UUIDJdbcType uuidJdbcType)
public ObjectUuidType(@Nullable final JdbcType uuidJdbcType)
{
super(
ObjectUuidTypeUtils.getObjectUuidRawClass(),
uuidJdbcType,
new ObjectUuidJavaType()
);
super(ObjectUuidTypeUtils.getObjectUuidRawClass());
this.jdbcTypeDescriptor = uuidJdbcType;
}

public ObjectUuidType(final Class<?> implClass)
{
this(implClass, UUIDJdbcType.INSTANCE);
this(implClass, null);
}

public ObjectUuidType(final Class<?> implClass, final UUIDJdbcType uuidJdbcType)
public ObjectUuidType(final Class<?> implClass, @Nullable final JdbcType uuidJdbcType)
{
this(uuidJdbcType);

Expand All @@ -47,6 +66,19 @@ public ObjectUuidType(final Class<?> implClass, final UUIDJdbcType uuidJdbcType)
this.setParameterValues(parameters);
}

@Override
public void setTypeConfiguration(final TypeConfiguration typeConfiguration)
{
this.typeConfiguration = typeConfiguration;
}

@Nullable
@Override
public TypeConfiguration getTypeConfiguration()
{
return typeConfiguration;
}

@Override
public String getName()
{
Expand All @@ -59,35 +91,83 @@ public int getSqlType()
return SqlTypes.UUID;
}

@Override
public long getDefaultSqlLength(final Dialect dialect, final JdbcType jdbcType)
{
return javaTypeDescriptor.getDefaultSqlLength(dialect, jdbcType);
}

@Override
public JdbcType getJdbcType(final TypeConfiguration typeConfiguration)
{
return Objects.requireNonNull(jdbcTypeDescriptor, "jdbcTypeDescriptor is not yet initialized");
}

@Override
public ObjectUuidJavaType getExpressibleJavaType()
{
return javaTypeDescriptor;
}

@Override
public void setParameterValues(final Properties parameters)
{
getExpressibleJavaType().setParameterValues(parameters);
javaTypeDescriptor.setParameterValues(parameters);
if (jdbcTypeDescriptor == null && typeConfiguration != null) {
jdbcTypeDescriptor = typeConfiguration.getJdbcTypeRegistry().getDescriptor(getSqlType());
}
}

@Override
public Class<ObjectUuid<?>> returnedClass()
{
return getExpressibleJavaType().getJavaTypeClass();
return javaTypeDescriptor.getJavaTypeClass();
}

@Override
public Class<ObjectUuid<?>> getBindableJavaType()
{
return returnedClass();
}

@Nullable
@Override
public ObjectUuid<?> fromStringValue(@Nullable final CharSequence sequence)
{
return getExpressibleJavaType().fromString(sequence);
return javaTypeDescriptor.fromString(sequence);
}

@Override
public ObjectUuidJavaType getExpressibleJavaType()
public MutabilityPlan<ObjectUuid<?>> getExposedMutabilityPlan()
{
return (ObjectUuidJavaType) super.getExpressibleJavaType();
return javaTypeDescriptor.getMutabilityPlan();
}

/**
* the nullSafeGet() is delegated here in the {@link ImmutableType}
*/
@Nullable
@Override
public MutabilityPlan<ObjectUuid<?>> getExposedMutabilityPlan()
protected ObjectUuid<?> get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException
{
return getJdbcType(session.getTypeConfiguration())
.getExtractor(javaTypeDescriptor).extract(rs, position, session);
}

/**
* the nullSafeSet() is delegated here in the {@link ImmutableType}
*/
@Override
protected void set(PreparedStatement st, @Nullable ObjectUuid<?> value, int index, SharedSessionContractImplementor session) throws SQLException
{
getJdbcType(session.getTypeConfiguration())
.getBinder(javaTypeDescriptor).bind(st, value, index, session);
}

@Override
public String toString()
{
return ImmutableMutabilityPlan.instance();
return "%s backed by %s".formatted(javaTypeDescriptor, jdbcTypeDescriptor != null ? jdbcTypeDescriptor : "???");
}

}
Loading

0 comments on commit 2ccd867

Please sign in to comment.