Skip to content

Commit

Permalink
Add support for LocalDateTime[] mappings #407
Browse files Browse the repository at this point in the history
  • Loading branch information
vladmihalcea committed Mar 23, 2022
1 parent d9c19e6 commit d11ad93
Show file tree
Hide file tree
Showing 14 changed files with 273 additions and 15 deletions.
@@ -0,0 +1,50 @@
package com.vladmihalcea.hibernate.type.array;

import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
import com.vladmihalcea.hibernate.type.array.internal.LocalDateTimeArrayTypeDescriptor;
import com.vladmihalcea.hibernate.type.util.Configuration;
import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
import org.hibernate.usertype.DynamicParameterizedType;

import java.util.Properties;

/**
* Maps a {@code java.Time.LocalDateTime[]} array on a PostgreSQL timestamp[] ARRAY type. Multidimensional arrays are
* supported as well, as
* explained in <a href="https://vladmihalcea.com/multidimensional-array-jpa-hibernate/">this article</a>.
* <p>
* For more details about how to use it, check out
* <a href="https://vladmihalcea.com/how-to-map-java-and-sql-arrays-with-jpa-and-hibernate/">this
* article</a> on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
*
* @author Vlad Mihalcea
*/

public class LocalDateTimeArrayType extends AbstractArrayType<java.time.LocalDateTime[]> {

public static final LocalDateTimeArrayType INSTANCE =
new LocalDateTimeArrayType();

public LocalDateTimeArrayType() {
super(
new LocalDateTimeArrayTypeDescriptor()
);
}

public LocalDateTimeArrayType(Configuration configuration) {
super(
new LocalDateTimeArrayTypeDescriptor(), configuration
);
}

public LocalDateTimeArrayType(Class arrayClass) {
this();
Properties parameters = new Properties();
parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
setParameterValues(parameters);
}

public String getName() {
return "localdatetime-array";
}
}
Expand Up @@ -200,6 +200,13 @@ public static <T> T unwrapArray(Object[] originalArray, Class<T> arrayClass) {
array[i] = originalArray[i] != null ? ((java.sql.Date) originalArray[i]).toLocalDate() : null;
}
return (T) array;
} else if (java.time.LocalDateTime[].class.equals(arrayClass) && java.sql.Timestamp[].class.equals(originalArray.getClass())) {
// special case because conversion is neither with ctor nor valueOf
Object[] array = (Object[]) Array.newInstance(java.time.LocalDateTime.class, originalArray.length);
for (int i = 0; i < array.length; ++i) {
array[i] = originalArray[i] != null ? ((java.sql.Timestamp) originalArray[i]).toLocalDateTime() : null;
}
return (T) array;
} else if(arrayClass.getComponentType() != null && arrayClass.getComponentType().isArray()) {
int arrayLength = originalArray.length;
Object[] array = (Object[]) Array.newInstance(arrayClass.getComponentType(), arrayLength);
Expand Down
Expand Up @@ -11,6 +11,7 @@
import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

/**
Expand Down Expand Up @@ -125,7 +126,7 @@ public void setParameterValues(Properties parameters) {
sqlArrayType = "text";
} else if (UUID.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "uuid";
} else if (Date.class.isAssignableFrom(arrayElementClass)) {
} else if (Date.class.isAssignableFrom(arrayElementClass) || LocalDateTime.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "timestamp";
} else if (Boolean.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "boolean";
Expand Down
@@ -0,0 +1,17 @@
package com.vladmihalcea.hibernate.type.array.internal;

/**
* @author Vlad Mihalcea
*/
public class LocalDateTimeArrayTypeDescriptor
extends AbstractArrayTypeDescriptor<java.time.LocalDateTime[]> {

public LocalDateTimeArrayTypeDescriptor() {
super(java.time.LocalDateTime[].class);
}

@Override
protected String getSqlArrayType() {
return "timestamp";
}
}
Expand Up @@ -19,6 +19,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;
Expand Down Expand Up @@ -96,6 +97,7 @@ public void test() {
event.setTimestampValues(new Date[]{date1, date2});
event.setDecimalValues(new BigDecimal[]{BigDecimal.ONE, BigDecimal.ZERO, BigDecimal.TEN});
event.setLocalDateValues(new LocalDate[]{LocalDate.of(2022, 3, 22), LocalDate.of(2021, 4, 21)});
event.setLocalDateTimeValues(new LocalDateTime[]{LocalDateTime.of(2022, 3, 22, 11, 22, 33), LocalDateTime.of(2021, 4, 21, 22, 33, 44)});
event.setSensorBooleanValues(new Boolean[]{false, true, true});

entityManager.persist(event);
Expand All @@ -114,6 +116,7 @@ public void test() {
assertArrayEquals(new Date[]{date1, date2}, event.getTimestampValues());
assertArrayEquals(new BigDecimal[]{BigDecimal.ONE, BigDecimal.ZERO, BigDecimal.TEN}, event.getDecimalValues());
assertArrayEquals(new LocalDate[]{LocalDate.of(2022, 3, 22), LocalDate.of(2021, 4, 21)}, event.getLocalDateValues());
assertArrayEquals(new LocalDateTime[]{LocalDateTime.of(2022, 3, 22, 11, 22, 33), LocalDateTime.of(2021, 4, 21, 22, 33, 44)}, event.getLocalDateTimeValues());
assertArrayEquals(new Boolean[]{false, true, true}, event.getSensorBooleanValues());
});

Expand Down Expand Up @@ -207,6 +210,10 @@ public static class Event extends BaseEntity {
@Column(name = "localdate_values", columnDefinition = "date[]")
private LocalDate[] localDateValues;

@Type(type = "localdatetime-array")
@Column(name = "localdatetime_values", columnDefinition = "timestamp[]")
private LocalDateTime[] localDateTimeValues;

@Type(type = "sensor-state-array")
@Column(name = "sensor_states", columnDefinition = "sensor_state[]")
private SensorState[] sensorStates;
Expand Down Expand Up @@ -294,6 +301,14 @@ public LocalDate[] getLocalDateValues() {
public void setLocalDateValues(LocalDate[] localDateValues) {
this.localDateValues = localDateValues;
}

public LocalDateTime[] getLocalDateTimeValues() {
return localDateTimeValues;
}

public void setLocalDateTimeValues(LocalDateTime[] localDateTimeValues) {
this.localDateTimeValues = localDateTimeValues;
}
}

public enum SensorState {
Expand Down
Expand Up @@ -15,6 +15,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

Expand Down Expand Up @@ -129,6 +130,12 @@ public void test() {
LocalDate.of(2021, 4, 21)
)
)
.setLocalDateTimeValues(
Arrays.asList(
LocalDateTime.of(2022, 3, 22, 11, 22, 33),
LocalDateTime.of(2021, 4, 21, 22, 33, 44)
)
)
);
});

Expand Down Expand Up @@ -208,6 +215,13 @@ public void test() {
),
event.getLocalDateValues()
);
assertEquals(
Arrays.asList(
LocalDateTime.of(2022, 3, 22, 11, 22, 33),
LocalDateTime.of(2021, 4, 21, 22, 33, 44)
),
event.getLocalDateTimeValues()
);
});

doInJPA(entityManager -> {
Expand Down Expand Up @@ -259,6 +273,7 @@ public void testMixingNullValues() {
event.setTimestampValues(Arrays.asList(null, date));
event.setDecimalValues(Arrays.asList(null, BigDecimal.TEN));
event.setLocalDateValues(Arrays.asList(null, LocalDate.of(2021, 4, 21)));
event.setLocalDateTimeValues(Arrays.asList(null, LocalDateTime.of(2021, 4, 21, 22, 33, 44)));
entityManager.persist(event);
});

Expand All @@ -276,6 +291,7 @@ public void testMixingNullValues() {
assertArrayEquals(new Date[]{null, date}, event.getTimestampValues().toArray());
assertArrayEquals(new BigDecimal[]{null, BigDecimal.TEN}, event.getDecimalValues().toArray());
assertArrayEquals(new LocalDate[]{null, LocalDate.of(2021, 4, 21)}, event.getLocalDateValues().toArray());
assertArrayEquals(new LocalDateTime[]{null, LocalDateTime.of(2021, 4, 21, 22, 33, 44)}, event.getLocalDateTimeValues().toArray());
});
}

Expand All @@ -300,6 +316,7 @@ public void testNullValues() {
event.setTimestampValues(Arrays.asList(null, null));
event.setDecimalValues(Arrays.asList(null, null));
event.setLocalDateValues(Arrays.asList(null, null));
event.setLocalDateTimeValues(Arrays.asList(null, null));
entityManager.persist(event);
});

Expand All @@ -317,6 +334,7 @@ public void testNullValues() {
assertArrayEquals(new Date[]{null, null}, event.getTimestampValues().toArray());
assertArrayEquals(new BigDecimal[]{null, null}, event.getDecimalValues().toArray());
assertArrayEquals(new LocalDate[]{null, null}, event.getLocalDateValues().toArray());
assertArrayEquals(new LocalDateTime[]{null, null}, event.getLocalDateTimeValues().toArray());
});
}

Expand All @@ -341,6 +359,7 @@ public void testEmptyArrays() {
event.setTimestampValues(Collections.emptyList());
event.setDecimalValues(Collections.emptyList());
event.setLocalDateValues(Collections.emptyList());
event.setLocalDateTimeValues(Collections.emptyList());
entityManager.persist(event);
});

Expand All @@ -358,6 +377,7 @@ public void testEmptyArrays() {
assertArrayEquals(new Date[]{}, event.getTimestampValues().toArray());
assertArrayEquals(new BigDecimal[]{}, event.getDecimalValues().toArray());
assertArrayEquals(new LocalDate[]{}, event.getLocalDateValues().toArray());
assertArrayEquals(new LocalDateTime[]{}, event.getLocalDateTimeValues().toArray());
});
}

Expand Down Expand Up @@ -455,6 +475,13 @@ public static class Event {
)
private List<LocalDate> localDateValues;

@Type(type = "list-array")
@Column(
name = "localdatetime_values",
columnDefinition = "timestamp[]"
)
private List<LocalDateTime> localDateTimeValues;

public Long getId() {
return id;
}
Expand Down Expand Up @@ -562,6 +589,15 @@ public Event setLocalDateValues(List<LocalDate> localDateValues) {
this.localDateValues = localDateValues;
return this;
}

public List<LocalDateTime> getLocalDateTimeValues() {
return localDateTimeValues;
}

public Event setLocalDateTimeValues(List<LocalDateTime> localDateTimeValues) {
this.localDateTimeValues = localDateTimeValues;
return this;
}
}

@Test
Expand Down
Expand Up @@ -23,6 +23,7 @@
@TypeDef(name = "timestamp-array", typeClass = TimestampArrayType.class),
@TypeDef(name = "decimal-array", typeClass = DecimalArrayType.class),
@TypeDef(name = "localdate-array", typeClass = LocalDateArrayType.class),
@TypeDef(name = "localdatetime-array", typeClass = LocalDateTimeArrayType.class),
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
@TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class),
Expand Down
@@ -0,0 +1,54 @@
package com.vladmihalcea.hibernate.type.array;

import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
import com.vladmihalcea.hibernate.type.array.internal.LocalDateTimeArrayTypeDescriptor;
import com.vladmihalcea.hibernate.type.util.Configuration;
import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
import org.hibernate.usertype.DynamicParameterizedType;

import java.util.Properties;

/**
* Maps a {@code java.Time.LocalDateTime[]} array on a PostgreSQL timestamp[] ARRAY type. Multidimensional arrays are
* supported as well, as
* explained in <a href="https://vladmihalcea.com/multidimensional-array-jpa-hibernate/">this article</a>.
* <p>
* For more details about how to use it, check out
* <a href="https://vladmihalcea.com/how-to-map-java-and-sql-arrays-with-jpa-and-hibernate/">this
* article</a> on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
*
* @author Vlad Mihalcea
*/

public class LocalDateTimeArrayType extends AbstractArrayType<java.time.LocalDateTime[]> {

public static final LocalDateTimeArrayType INSTANCE =
new LocalDateTimeArrayType();

public LocalDateTimeArrayType() {
super(
new LocalDateTimeArrayTypeDescriptor()
);
}

public LocalDateTimeArrayType(Configuration configuration) {
super(
new LocalDateTimeArrayTypeDescriptor(), configuration
);
}

public LocalDateTimeArrayType(Class arrayClass) {
this();
Properties parameters = new Properties();
parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
setParameterValues(parameters);
}

public LocalDateTimeArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
}

public String getName() {
return "localdatetime-array";
}
}
Expand Up @@ -200,6 +200,13 @@ public static <T> T unwrapArray(Object[] originalArray, Class<T> arrayClass) {
array[i] = originalArray[i] != null ? ((java.sql.Date) originalArray[i]).toLocalDate() : null;
}
return (T) array;
} else if (java.time.LocalDateTime[].class.equals(arrayClass) && java.sql.Timestamp[].class.equals(originalArray.getClass())) {
// special case because conversion is neither with ctor nor valueOf
Object[] array = (Object[]) Array.newInstance(java.time.LocalDateTime.class, originalArray.length);
for (int i = 0; i < array.length; ++i) {
array[i] = originalArray[i] != null ? ((java.sql.Timestamp) originalArray[i]).toLocalDateTime() : null;
}
return (T) array;
} else if(arrayClass.getComponentType() != null && arrayClass.getComponentType().isArray()) {
int arrayLength = originalArray.length;
Object[] array = (Object[]) Array.newInstance(arrayClass.getComponentType(), arrayLength);
Expand Down
Expand Up @@ -11,6 +11,7 @@
import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

/**
Expand Down Expand Up @@ -125,7 +126,7 @@ public void setParameterValues(Properties parameters) {
sqlArrayType = "text";
} else if (UUID.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "uuid";
} else if (Date.class.isAssignableFrom(arrayElementClass)) {
} else if (Date.class.isAssignableFrom(arrayElementClass) || LocalDateTime.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "timestamp";
} else if (Boolean.class.isAssignableFrom(arrayElementClass)) {
sqlArrayType = "boolean";
Expand Down
@@ -0,0 +1,17 @@
package com.vladmihalcea.hibernate.type.array.internal;

/**
* @author Vlad Mihalcea
*/
public class LocalDateTimeArrayTypeDescriptor
extends AbstractArrayTypeDescriptor<java.time.LocalDateTime[]> {

public LocalDateTimeArrayTypeDescriptor() {
super(java.time.LocalDateTime[].class);
}

@Override
protected String getSqlArrayType() {
return "timestamp";
}
}

0 comments on commit d11ad93

Please sign in to comment.