Fix BasicTypeResolution for collections annotated with @MapKeyTemporal

This commit is contained in:
Andrea Boriero 2022-02-15 10:06:26 +01:00 committed by Andrea Boriero
parent f14e70cb8f
commit 26e98c0879
4 changed files with 264 additions and 22 deletions

View File

@ -41,12 +41,10 @@ import org.hibernate.type.spi.TypeConfiguration;
* type info was supplied.
*/
public class InferredBasicValueResolver {
/**
* Create an inference-based resolution
*/
public static <T> BasicValue.Resolution<T> from(
Function<TypeConfiguration, BasicJavaType> explicitJavaTypeAccess,
Function<TypeConfiguration, JdbcType> explicitSqlTypeAccess,
BasicJavaType<T> explicitJavaType,
JdbcType explicitJdbcType,
Type resolvedJavaType,
Supplier<JavaType<T>> reflectedJtdResolver,
JdbcTypeIndicators stdIndicators,
@ -55,14 +53,6 @@ public class InferredBasicValueResolver {
String ownerName,
String propertyName,
TypeConfiguration typeConfiguration) {
final BasicJavaType<T> explicitJavaType = explicitJavaTypeAccess != null
? explicitJavaTypeAccess.apply( typeConfiguration )
: null;
final JdbcType explicitJdbcType = explicitSqlTypeAccess
!= null ? explicitSqlTypeAccess.apply( typeConfiguration )
: null;
final JavaType<T> reflectedJtd = reflectedJtdResolver.get();
// NOTE : the distinction that is made below wrt `explicitJavaType` and `reflectedJtd` is
@ -85,6 +75,16 @@ public class InferredBasicValueResolver {
legacyType = jdbcMapping;
}
else {
if ( explicitJavaType instanceof TemporalJavaType ) {
return fromTemporal(
(TemporalJavaType<T>) explicitJavaType,
null,
null,
resolvedJavaType,
stdIndicators,
typeConfiguration
);
}
// we need to infer the JdbcType and use that to build the value-mapping
final JdbcType inferredJdbcType = explicitJavaType.getRecommendedJdbcType( stdIndicators );
if ( inferredJdbcType instanceof ObjectJdbcType ) {
@ -271,6 +271,41 @@ public class InferredBasicValueResolver {
);
}
/**
* Create an inference-based resolution
*/
public static <T> BasicValue.Resolution<T> from(
Function<TypeConfiguration, BasicJavaType> explicitJavaTypeAccess,
Function<TypeConfiguration, JdbcType> explicitSqlTypeAccess,
Type resolvedJavaType,
Supplier<JavaType<T>> reflectedJtdResolver,
JdbcTypeIndicators stdIndicators,
Table table,
Selectable selectable,
String ownerName,
String propertyName,
TypeConfiguration typeConfiguration) {
final BasicJavaType<T> explicitJavaType = explicitJavaTypeAccess != null
? explicitJavaTypeAccess.apply( typeConfiguration )
: null;
final JdbcType explicitJdbcType = explicitSqlTypeAccess
!= null ? explicitSqlTypeAccess.apply( typeConfiguration )
: null;
return InferredBasicValueResolver.from(
explicitJavaType,
explicitJdbcType,
resolvedJavaType,
reflectedJtdResolver,
stdIndicators,
table,
selectable,
ownerName,
propertyName,
typeConfiguration
);
}
public static <T> BasicType<T> resolveSqlTypeIndicators(
JdbcTypeIndicators stdIndicators,
BasicType<T> resolved,

View File

@ -522,7 +522,8 @@ public class BasicValueBinder implements JdbcTypeIndicators {
final MapKeyClass mapKeyClassAnn = mapAttribute.getAnnotation( MapKeyClass.class );
if ( mapKeyClassAnn != null ) {
return (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry().getDescriptor( mapKeyClassAnn.value() );
return (BasicJavaType<?>) typeConfiguration.getJavaTypeRegistry()
.getDescriptor( mapKeyClassAnn.value() );
}
return null;

View File

@ -411,12 +411,16 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
// determine JavaType if we can
final BasicJavaType explicitJtd;
if ( explicitJavaTypeAccess != null ) {
final BasicJavaType<?> explicitJtd = explicitJavaTypeAccess.apply( typeConfiguration );
explicitJtd = explicitJavaTypeAccess.apply( typeConfiguration );
if ( explicitJtd != null ) {
jtd = explicitJtd;
}
}
else {
explicitJtd = null;
}
if ( jtd == null ) {
if ( implicitJavaTypeAccess != null ) {
@ -434,12 +438,17 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
}
}
final JdbcType jdbcType;
if ( explicitJdbcTypeAccess != null ) {
jdbcType = explicitJdbcTypeAccess.apply( typeConfiguration );
}
else {
jdbcType = null;
}
if ( jtd == null ) {
if ( explicitJdbcTypeAccess != null ) {
final JdbcType jdbcType = explicitJdbcTypeAccess.apply( typeConfiguration );
if ( jdbcType != null ) {
jtd = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
}
if ( jdbcType != null ) {
jtd = jdbcType.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration );
}
}
@ -460,8 +469,8 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
}
return InferredBasicValueResolver.from(
explicitJavaTypeAccess,
explicitJdbcTypeAccess,
explicitJtd,
jdbcType,
resolvedJavaType,
this::determineReflectedJavaType,
this,

View File

@ -0,0 +1,197 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
//$Id$
package org.hibernate.orm.test.jpa;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyClass;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.MapKeyTemporal;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Jpa(
annotatedClasses = {
MapKeyTest.School.class,
MapKeyTest.Person.class
}
)
public class MapKeyTest {
@Test
public void testMapKeyTemporal(EntityManagerFactoryScope scope) throws Exception {
SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" );
final Date date1 = formatter.parse( "2022-02-02" );
final Date date2 = java.sql.Date.valueOf( formatter.format( Calendar.getInstance().getTime() ) );
Set<Date> expectedDates = new HashSet<>();
expectedDates.add( date1 );
expectedDates.add( date2 );
School school1 = new School( 1, "High School" );
School school2 = new School( 2, "Primary School" );
Person person1 = new Person( 1, "Andrea", school2 );
Person person2 = new Person( 2, "Luigi", school2 );
Set<Person> expectedPeople = new HashSet<>();
expectedPeople.add( person1 );
expectedPeople.add( person2 );
scope.inTransaction(
entityManager -> {
Map<Date, Person> lastNames = new HashMap<>();
lastNames.put( date1, person1 );
lastNames.put( date2, person2 );
school2.setStudentsByDate( lastNames );
entityManager.persist( school1 );
entityManager.persist( school2 );
entityManager.persist( person1 );
entityManager.persist( person2 );
}
);
scope.inTransaction(
entityManager -> {
Person person = entityManager.find( Person.class, 2 );
School school = person.getSchool();
Map<Date, Person> studentsByDate = school.getStudentsByDate();
Set<Date> dates = studentsByDate.keySet();
assertEquals( expectedDates.size(), dates.size() );
assertTrue( expectedDates.containsAll( dates ) );
Collection<Person> people = studentsByDate.values();
assertEquals( expectedPeople.size(), people.size() );
assertTrue( expectedPeople.containsAll( people ) );
}
);
}
@Entity
@Table(name = "PERSON_TABLE")
public static class Person {
@Id
private int id;
private String name;
@ManyToOne
private School school;
public Person() {
}
public Person(int id, String name, School school) {
this.id = id;
this.name = name;
this.school = school;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public School getSchool() {
return school;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Person person = (Person) o;
return name.equals( person.name );
}
@Override
public int hashCode() {
return Objects.hash( name );
}
}
@Entity
@Table(name = "SCHOOL_TABLE")
public static class School {
@Id
private int id;
private String name;
@OneToMany(mappedBy = "school")
@MapKeyClass(Date.class)
@MapKeyColumn(name = "THE_DATE")
@MapKeyTemporal(TemporalType.DATE)
private Map<Date, Person> studentsByDate;
@Temporal( TemporalType.DATE )
private Date aDate;
public School() {
}
public School(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Map getStudentsByDate() {
return studentsByDate;
}
public void setStudentsByDate(Map studentsByDate) {
this.studentsByDate = studentsByDate;
}
}
}