Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18787 Custom UserType not recognised for array properties #9205

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
* type for values of type {@link UserType#returnedClass()}.
*/
default void contributeType(UserType<?> type) {
contributeType( type, type.returnedClass().getName() );
contributeType( type, type.returnedClass().getTypeName() );

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
TypeContributions.contributeType
should be avoided because it has been deprecated.
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.type.contributor.usertype.hhh18787;

/**
* Simple object holding some properties
*/
public class CustomData {
private String text;
private Long number;

public CustomData(String text, Long number) {
this.text = text;
this.number = number;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public Long getNumber() {
return number;
}

public void setNumber(Long number) {
this.number = number;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.type.contributor.usertype.hhh18787;

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SqlTypes;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Custom type implementing {@link UserType} so <code>CustomData[]</code> can be converted.
*/
public class CustomDataType implements UserType<CustomData[]> {

public static final CustomDataType INSTANCE = new CustomDataType();

@Override
public int getSqlType() {
return SqlTypes.VARCHAR;
}

@Override
public Class<CustomData[]> returnedClass() {
return CustomData[].class;
}

@Override
public boolean equals(CustomData[] x, CustomData[] y) {
return Arrays.equals(x, y);
}

@Override
public int hashCode(CustomData[] x) {
return Arrays.hashCode(x);
}

@Override
public CustomData[] nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session,
Object owner) throws SQLException {

final var customDataStr = rs.getString(position);
return rs.wasNull() ? new CustomData[0] : parseDataFromString(customDataStr);
}

@Override
public void nullSafeSet(PreparedStatement st, CustomData[] value, int index,
SharedSessionContractImplementor session) throws SQLException {

if (value == null || value.length == 0) {
st.setNull(index, Types.VARCHAR);
} else {
final var str =
Stream.of(value).map(u -> String.format("%s|%s", u.getText(), u.getNumber())).collect(Collectors.joining(","));

st.setString(index, str);
}
}

@Override
public CustomData[] deepCopy(CustomData[] value) {
return Arrays.copyOf(value, value.length);
}

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

@Override
public Serializable disassemble(CustomData[] value) {
return deepCopy(value);
}

@Override
public CustomData[] assemble(Serializable cached, Object owner) {
return deepCopy((CustomData[]) cached);
}

private CustomData[] parseDataFromString(String value) {
return Arrays.stream(value.split(",")).map(singleValue -> {
final var params = singleValue.split("\\|");
return new CustomData(params[0], Long.parseLong(params[1]));
}).toArray(CustomData[]::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.type.contributor.usertype.hhh18787;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

/**
* Some entity, important is the property <code>customData</code>.
*/
@Entity
@Table(name = "whatever")
public class SomeEntity {
@Id
@GeneratedValue
private Long id;

@Column
private CustomData[] customData;

public SomeEntity() {
}

public SomeEntity(CustomData[] customData) {
this.customData = customData;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public CustomData[] getCustomData() {
return customData;
}

public void setCustomData(CustomData[] custom) {
this.customData = custom;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.type.contributor.usertype.hhh18787;

import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;

/**
* Registering custom user type {@link CustomDataType}.
*/
public class TypesContributor implements TypeContributor {

@Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeType(CustomDataType.INSTANCE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.type.contributor.usertype.hhh18787;

import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@DomainModel(
annotatedClasses = SomeEntity.class,
typeContributors = TypesContributor.class
)
@SessionFactory
@JiraKey( "HHH-18787" )
class UserTypeNotRecognisedTestCase {

@Test
void customUserTypeWithTypeContributorRegistrationTest(SessionFactoryScope scope) {
final var data = new CustomData( "whatever", 1L );
scope.inTransaction( em -> {
// creating some data, flushing and clearing context
em.merge( new SomeEntity( new CustomData[] {data} ) );
} );

scope.inSession( em -> {
// getting the data
final var query = em.createQuery( "select s from SomeEntity s where id is not null", SomeEntity.class );
final var resultList = query.getResultList();

// single result should be present
assertNotNull( resultList );
assertEquals( 1, resultList.size() );

// the entity shouldn't be null
final var entity = resultList.get( 0 );
assertNotNull( entity );

// custom data array shouldn't be null and there should be single object present
final var customData = entity.getCustomData();
assertNotNull( customData );
assertEquals( 1, customData.length );

// custom data object shouldn't be null and all fields should be set with correct values
final var singleCustomData = customData[0];
assertNotNull( singleCustomData );
assertEquals( data.getText(), singleCustomData.getText() );
assertEquals( data.getNumber(), singleCustomData.getNumber() );
} );
}
}