conectar - Spring JDBC+Postgres SQL+Java 8-conversión de/a LocalDate
spring boot postgresql connection (2)
El JEP 170: JDBC 4.2 define el nuevo soporte API de fecha y fecha con JDBC. La compatibilidad de la página de descarga de Postgres con las nuevas características de JDBC 4.2 solo comienza a partir de la versión 9.4 de Postgres, por lo que aparecerán algunos desafíos de compatibilidad utilizando la nueva API con controladores anteriores.
Incluso setObject(1, new java.util.Date());
es rechazado por la misma restricción en Postgres (que MySQL acepta felizmente), no solo la nueva API como LocalDate
. Algunos comportamientos dependerán de la implementación, por lo que solo java.sql.*
Está garantizado (aproximadamente).
En cuanto al marco Spring JDBC, creo que anular su comportamiento funciona para sortearlo sin lamentarlo más tarde. Sugiero un enfoque ligeramente diferente para lo que ya hiciste:
- Extienda el comportamiento de
BeanPropertySqlParameterSource
para que funcione con la nueva API de fecha y hora, y otras clases asociadas con la entrada de parámetros si es necesario (no estoy familiarizado con esa API de Spring). - Extraiga el comportamiento ya
BeanPropertyRowMapper
deBeanPropertyRowMapper
a otra clase para obtener operaciones. - Envuelva todo con un patrón de fábrica o clase de utilidad para que no tenga que volver a mirarlo.
De esta forma, mejorará las capacidades futuras de refactorización si la API recibe soporte y reduce la cantidad de código necesario durante el desarrollo.
También puede ver algunos enfoques DAO .
Estoy usando Postgres SQL 9.2, Spring JDBC con la versión 4.0.5 y Java 8.
Java 8 introdujo una nueva API de fecha / hora y me gustaría usarla, pero encontré algunas dificultades. He creado la tabla TABLE_A:
CREATE TABLE "TABLE_A"
(
new_date date,
old_date date
)
Estoy usando Spring JDBC para comunicarme con la base de datos. He creado la clase Java, que corresponde a esta tabla:
public class TableA
{
private LocalDate newDate;
private Date oldDate;
//getters and setters
}
este es mi código que es responsable de insertar una nueva fila:
public void create(TableA tableA)
{
BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(tableA);
final String sql = "INSERT INTO public.TABLE_A (new_date,old_date) values(:newDate,:oldDate)";
namedJdbcTemplate.update(sql,parameterSource);
}
Cuando ejecuté este método obtuve una excepción:
org.postgresql.util.PSQLException: Can''t infer the SQL type to use for an instance of java.time.LocalDate. Use setObject() with an explicit Types value to specify the type to use.
así que actualicé el credo de BeanPropertySqlParameterSource:
BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(tableA);
parameterSource.registerSqlType("newDate", Types.DATE);
después de ese cambio, pude insertar la fila. Pero luego, me gustaría buscar filas desde la base de datos. Aquí está mi método:
public List<TableA> getAll()
{
final String sql = "select * from public.TABLE_A";
final BeanPropertyRowMapper<TableA> rowMapper = new BeanPropertyRowMapper<TableA>(TableA.class);
return namedJdbcTemplate.query(sql,rowMapper);
}
y por supuesto tengo una excepción:
...
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:474)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:511)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1119)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:902)
at org.springframework.jdbc.core.BeanPropertyRowMapper.mapRow(BeanPropertyRowMapper.java:255)
...
Caused by: java.lang.IllegalStateException: Cannot convert value of type [java.sql.Date] to required type [java.time.LocalDate] for property ''newDate'': no matching editors or conversion strategy found.
Así que actualicé mi código, esta vez BeanPropertyRowMapper, he agregado el servicio de conversión a bean wrapper, que puede realizar la conversión de java.sql.Date a java.time.LocalDate
public List<TableA> getAll()
{
final String sql = "select * from public.TABLE_A";
final BeanPropertyRowMapper<TableA> rowMapper = new BeanPropertyRowMapper<TableA>(TableA.class)
{
@Override
protected void initBeanWrapper(BeanWrapper bw) {
super.initBeanWrapper(bw);
bw.setConversionService(new ConversionService() {
@Override
public boolean canConvert(Class<?> aClass, Class<?> aClass2) {
return aClass == java.sql.Date.class && aClass2 == LocalDate.class;
}
@Override
public boolean canConvert(TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor2) {
return canConvert(typeDescriptor.getType(), typeDescriptor2.getType());
}
@Override
public <T> T convert(Object o, Class<T> tClass) {
if(o instanceof Date && tClass == LocalDate.class)
{
return (T)((Date)o).toLocalDate();
}
return null;
}
@Override
public Object convert(Object o, TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor2) {
return convert(o,typeDescriptor2.getType());
}
});
}
} ;
return namedJdbcTemplate.query(sql,rowMapper);
y ahora todo funciona, pero es bastante complicado.
¿Es la forma más fácil de lograr eso? En términos generales, me gustaría operar en LocalDate en mi código Java, porque es mucho más conveniente y puedo persistir en la base de datos . Esperaría que debería estar habilitado por defecto.
Tenga en cuenta que Java 8 Date and Time API (JSR-310) es compatible, pero la implementación no está completa: https://jdbc.postgresql.org/documentation/head/8-date-time.html Tenga en cuenta que ZonedDateTime, Instant y OffsetTime / TIME [SIN TIMEZONE] no son compatibles.